Skip to content

Commit d577814

Browse files
authored
fix(engine): align Amsterdam endpoint validation (#23625)
1 parent 8b46f1a commit d577814

4 files changed

Lines changed: 209 additions & 24 deletions

File tree

crates/payload/primitives/src/error.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ pub enum VersionSpecificValidationError {
126126
/// root after Cancun
127127
#[error("no parent beacon block root post-cancun")]
128128
NoParentBeaconBlockRootPostCancun,
129-
/// Thrown if the pre-V6 `PayloadAttributes` or `ExecutionPayload` contains a block access list
130-
#[error("block access list not before V6")]
131-
BlockAccessListNotSupportedBeforeV6,
129+
/// Thrown if the current engine method version does not support a block access list
130+
#[error("block access list not supported in this engine API version")]
131+
BlockAccessListNotSupported,
132132
/// Thrown if `engine_newPayload` contains no block access list
133133
/// after Amsterdam
134134
#[error("no block access list post-Amsterdam")]
@@ -137,9 +137,9 @@ pub enum VersionSpecificValidationError {
137137
/// before Amsterdam
138138
#[error("block access list pre-Amsterdam")]
139139
HasBlockAccessListPreAmsterdam,
140-
/// Thrown if the pre-V6 `PayloadAttributes` or `ExecutionPayload` contains a slot number
141-
#[error("slot number not before V6")]
142-
SlotNumberNotSupportedBeforeV6,
140+
/// Thrown if the current engine method version does not support a slot number
141+
#[error("slot number not supported in this engine API version")]
142+
SlotNumberNotSupported,
143143
/// Thrown if `engine_newPayload` contains no slot number
144144
/// after Amsterdam
145145
#[error("no slot number post-Amsterdam")]

crates/payload/primitives/src/lib.rs

Lines changed: 139 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone + 'static
6262
/// * If V3, this ensures that the payload timestamp is within the Cancun timestamp.
6363
/// * If V4, this ensures that the payload timestamp is within the Prague timestamp.
6464
/// * If V5, this ensures that the payload timestamp is within the Osaka timestamp.
65+
/// * If V6, this ensures that the payload timestamp is within the Amsterdam timestamp.
6566
///
66-
/// Additionally, it ensures that `engine_getPayloadV4` is not used for an Osaka payload.
67+
/// Additionally, it ensures that `engine_getPayloadV4` is not used for an Osaka payload and that
68+
/// staggered endpoint upgrades reject the next fork once a newer method version is required.
6769
///
6870
/// Otherwise, this will return [`EngineObjectValidationError::UnsupportedFork`].
6971
pub fn validate_payload_timestamp(
@@ -151,12 +153,26 @@ pub fn validate_payload_timestamp(
151153
return Err(EngineObjectValidationError::UnsupportedFork)
152154
}
153155

156+
let is_amsterdam = chain_spec.is_amsterdam_active_at_timestamp(timestamp);
157+
158+
// Staggered endpoint upgrades must reject Amsterdam payloads until the Amsterdam-specific
159+
// method version is used.
160+
if is_amsterdam &&
161+
matches!(
162+
(version, kind),
163+
(EngineApiMessageVersion::V3, MessageValidationKind::PayloadAttributes) |
164+
(EngineApiMessageVersion::V4, MessageValidationKind::Payload) |
165+
(EngineApiMessageVersion::V5, MessageValidationKind::GetPayload)
166+
)
167+
{
168+
return Err(EngineObjectValidationError::UnsupportedFork)
169+
}
170+
154171
// `engine_getPayloadV4` MUST reject payloads with a timestamp >= Osaka.
155172
if version.is_v4() && kind == MessageValidationKind::GetPayload && is_osaka {
156173
return Err(EngineObjectValidationError::UnsupportedFork)
157174
}
158175

159-
let is_amsterdam = chain_spec.is_amsterdam_active_at_timestamp(timestamp);
160176
if version.is_v6() && !is_amsterdam {
161177
// From the Engine API spec:
162178
// <https://github.com/ethereum/execution-apis/blob/15399c2e2f16a5f800bf3f285640357e2c245ad9/src/engine/osaka.md#specification>
@@ -183,16 +199,30 @@ pub fn validate_block_access_list_presence<T: EthereumHardforks>(
183199
has_block_access_list: bool,
184200
) -> Result<(), EngineObjectValidationError> {
185201
let is_amsterdam_active = chain_spec.is_amsterdam_active_at_timestamp(timestamp);
186-
187202
match version {
188203
EngineApiMessageVersion::V1 |
189204
EngineApiMessageVersion::V2 |
190205
EngineApiMessageVersion::V3 |
191-
EngineApiMessageVersion::V4 |
192-
EngineApiMessageVersion::V5 => {
206+
EngineApiMessageVersion::V4 => {
193207
if has_block_access_list {
194208
return Err(message_validation_kind
195-
.to_error(VersionSpecificValidationError::BlockAccessListNotSupportedBeforeV6))
209+
.to_error(VersionSpecificValidationError::BlockAccessListNotSupported))
210+
}
211+
}
212+
213+
EngineApiMessageVersion::V5 => {
214+
if message_validation_kind == MessageValidationKind::Payload {
215+
if is_amsterdam_active && !has_block_access_list {
216+
return Err(message_validation_kind
217+
.to_error(VersionSpecificValidationError::NoBlockAccessListPostAmsterdam))
218+
}
219+
if !is_amsterdam_active && has_block_access_list {
220+
return Err(message_validation_kind
221+
.to_error(VersionSpecificValidationError::HasBlockAccessListPreAmsterdam))
222+
}
223+
} else if has_block_access_list {
224+
return Err(message_validation_kind
225+
.to_error(VersionSpecificValidationError::BlockAccessListNotSupported))
196226
}
197227
}
198228

@@ -224,14 +254,42 @@ pub fn validate_slot_number_presence<T: EthereumHardforks>(
224254
let is_amsterdam_active = chain_spec.is_amsterdam_active_at_timestamp(timestamp);
225255

226256
match version {
227-
EngineApiMessageVersion::V1 |
228-
EngineApiMessageVersion::V2 |
229-
EngineApiMessageVersion::V3 |
230-
EngineApiMessageVersion::V4 |
231-
EngineApiMessageVersion::V5 => {
257+
EngineApiMessageVersion::V1 | EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 => {
232258
if has_slot_number {
233259
return Err(message_validation_kind
234-
.to_error(VersionSpecificValidationError::SlotNumberNotSupportedBeforeV6))
260+
.to_error(VersionSpecificValidationError::SlotNumberNotSupported))
261+
}
262+
}
263+
264+
EngineApiMessageVersion::V4 => {
265+
if message_validation_kind == MessageValidationKind::PayloadAttributes {
266+
if is_amsterdam_active && !has_slot_number {
267+
return Err(message_validation_kind
268+
.to_error(VersionSpecificValidationError::NoSlotNumberPostAmsterdam))
269+
}
270+
if !is_amsterdam_active && has_slot_number {
271+
return Err(message_validation_kind
272+
.to_error(VersionSpecificValidationError::HasSlotNumberPreAmsterdam))
273+
}
274+
} else if has_slot_number {
275+
return Err(message_validation_kind
276+
.to_error(VersionSpecificValidationError::SlotNumberNotSupported))
277+
}
278+
}
279+
280+
EngineApiMessageVersion::V5 => {
281+
if message_validation_kind == MessageValidationKind::Payload {
282+
if is_amsterdam_active && !has_slot_number {
283+
return Err(message_validation_kind
284+
.to_error(VersionSpecificValidationError::NoSlotNumberPostAmsterdam))
285+
}
286+
if !is_amsterdam_active && has_slot_number {
287+
return Err(message_validation_kind
288+
.to_error(VersionSpecificValidationError::HasSlotNumberPreAmsterdam))
289+
}
290+
} else if has_slot_number {
291+
return Err(message_validation_kind
292+
.to_error(VersionSpecificValidationError::SlotNumberNotSupported))
235293
}
236294
}
237295

@@ -651,6 +709,75 @@ mod tests {
651709
assert_matches!(res, Ok(()));
652710
}
653711

712+
#[test]
713+
fn validate_amsterdam_staggered_version_restrictions() {
714+
let chain_spec = ChainSpecBuilder::mainnet().amsterdam_activated().build();
715+
716+
let res = validate_payload_timestamp(
717+
&chain_spec,
718+
EngineApiMessageVersion::V3,
719+
0,
720+
MessageValidationKind::PayloadAttributes,
721+
);
722+
assert_matches!(res, Err(EngineObjectValidationError::UnsupportedFork));
723+
724+
let res = validate_payload_timestamp(
725+
&chain_spec,
726+
EngineApiMessageVersion::V4,
727+
0,
728+
MessageValidationKind::Payload,
729+
);
730+
assert_matches!(res, Err(EngineObjectValidationError::UnsupportedFork));
731+
732+
let res = validate_payload_timestamp(
733+
&chain_spec,
734+
EngineApiMessageVersion::V5,
735+
0,
736+
MessageValidationKind::GetPayload,
737+
);
738+
assert_matches!(res, Err(EngineObjectValidationError::UnsupportedFork));
739+
740+
let res = validate_payload_timestamp(
741+
&chain_spec,
742+
EngineApiMessageVersion::V6,
743+
0,
744+
MessageValidationKind::GetPayload,
745+
);
746+
assert_matches!(res, Ok(()));
747+
}
748+
749+
#[test]
750+
fn validate_amsterdam_slot_and_bal_presence() {
751+
let chain_spec = ChainSpecBuilder::mainnet().amsterdam_activated().build();
752+
753+
let res = validate_slot_number_presence(
754+
&chain_spec,
755+
EngineApiMessageVersion::V4,
756+
MessageValidationKind::PayloadAttributes,
757+
0,
758+
true,
759+
);
760+
assert_matches!(res, Ok(()));
761+
762+
let res = validate_slot_number_presence(
763+
&chain_spec,
764+
EngineApiMessageVersion::V5,
765+
MessageValidationKind::Payload,
766+
0,
767+
true,
768+
);
769+
assert_matches!(res, Ok(()));
770+
771+
let res = validate_block_access_list_presence(
772+
&chain_spec,
773+
EngineApiMessageVersion::V5,
774+
MessageValidationKind::Payload,
775+
0,
776+
true,
777+
);
778+
assert_matches!(res, Ok(()));
779+
}
780+
654781
#[test]
655782
fn execution_requests_validation() {
656783
assert_matches!(validate_execution_requests(&[]), Ok(()));

crates/rpc/rpc-engine-api/src/engine_api.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ where
269269
>::from_execution_payload(&payload);
270270
self.inner
271271
.validator
272-
.validate_version_specific_fields(EngineApiMessageVersion::V6, payload_or_attrs)?;
272+
.validate_version_specific_fields(EngineApiMessageVersion::V5, payload_or_attrs)?;
273273
Ok(self.inner.beacon_consensus.new_payload(payload).await?)
274274
}
275275

@@ -386,7 +386,7 @@ where
386386
state: ForkchoiceState,
387387
payload_attrs: Option<EngineT::PayloadAttributes>,
388388
) -> EngineApiResult<ForkchoiceUpdated> {
389-
self.validate_and_execute_forkchoice(EngineApiMessageVersion::V6, state, payload_attrs)
389+
self.validate_and_execute_forkchoice(EngineApiMessageVersion::V4, state, payload_attrs)
390390
.await
391391
}
392392

@@ -1438,9 +1438,10 @@ struct EngineApiInner<Provider, PayloadT: PayloadTypes, Pool, Validator, ChainSp
14381438
#[cfg(test)]
14391439
mod tests {
14401440
use super::*;
1441-
use alloy_primitives::{Address, B256};
1441+
use alloy_eips::eip7685::Requests;
1442+
use alloy_primitives::{Address, Bytes, B256};
14421443
use alloy_rpc_types_engine::{
1443-
ClientCode, ClientVersionV1, PayloadAttributes, PayloadStatusEnum,
1444+
ClientCode, ClientVersionV1, ExecutionPayloadV2, PayloadAttributes, PayloadStatusEnum,
14441445
};
14451446
use assert_matches::assert_matches;
14461447
use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET};
@@ -1532,6 +1533,63 @@ mod tests {
15321533
assert_matches!(handle.from_api.recv().await, Some(BeaconEngineMessage::NewPayload { .. }));
15331534
}
15341535

1536+
#[tokio::test]
1537+
async fn new_payload_v5_accepts_amsterdam_payloads() {
1538+
let chain_spec = Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
1539+
let provider = Arc::new(MockEthProvider::default());
1540+
let payload_store = spawn_test_payload_service::<EthEngineTypes>();
1541+
let (to_engine, mut engine_rx) = unbounded_channel();
1542+
1543+
let api = EngineApi::new(
1544+
provider,
1545+
chain_spec.clone(),
1546+
ConsensusEngineHandle::new(to_engine),
1547+
payload_store.into(),
1548+
NoopTransactionPool::default(),
1549+
Runtime::test(),
1550+
ClientVersionV1 {
1551+
code: ClientCode::RH,
1552+
name: "Reth".to_string(),
1553+
version: "v0.0.0-test".to_string(),
1554+
commit: "test".to_string(),
1555+
},
1556+
EngineCapabilities::default(),
1557+
EthereumEngineValidator::new(chain_spec),
1558+
false,
1559+
NoopNetwork::default(),
1560+
);
1561+
1562+
tokio::spawn(async move {
1563+
let payload_v1 = ExecutionPayloadV1::from_block_slow(&Block::default());
1564+
let payload = ExecutionPayloadV4 {
1565+
payload_inner: ExecutionPayloadV3 {
1566+
payload_inner: ExecutionPayloadV2 {
1567+
payload_inner: payload_v1,
1568+
withdrawals: Vec::new(),
1569+
},
1570+
blob_gas_used: 0,
1571+
excess_blob_gas: 0,
1572+
},
1573+
block_access_list: Bytes::from_static(b"bal"),
1574+
slot_number: 1,
1575+
};
1576+
let execution_data = ExecutionData {
1577+
payload: payload.into(),
1578+
sidecar: ExecutionPayloadSidecar::v4(
1579+
CancunPayloadFields {
1580+
versioned_hashes: Vec::new(),
1581+
parent_beacon_block_root: B256::ZERO,
1582+
},
1583+
PraguePayloadFields { requests: RequestsOrHash::Requests(Requests::default()) },
1584+
),
1585+
};
1586+
1587+
api.new_payload_v5(execution_data).await.unwrap();
1588+
});
1589+
1590+
assert_matches!(engine_rx.recv().await, Some(BeaconEngineMessage::NewPayload { .. }));
1591+
}
1592+
15351593
#[derive(Clone)]
15361594
struct TestNetworkInfo {
15371595
syncing: bool,

crates/rpc/rpc-engine-api/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,12 @@ impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
121121
VersionSpecificValidationError::WithdrawalsNotSupportedInV1 |
122122
VersionSpecificValidationError::NoWithdrawalsPostShanghai |
123123
VersionSpecificValidationError::HasWithdrawalsPreShanghai |
124-
VersionSpecificValidationError::BlockAccessListNotSupportedBeforeV6 |
124+
VersionSpecificValidationError::BlockAccessListNotSupported |
125125
VersionSpecificValidationError::HasBlockAccessListPreAmsterdam |
126126
VersionSpecificValidationError::NoBlockAccessListPostAmsterdam |
127127
VersionSpecificValidationError::HasSlotNumberPreAmsterdam |
128128
VersionSpecificValidationError::NoSlotNumberPostAmsterdam |
129-
VersionSpecificValidationError::SlotNumberNotSupportedBeforeV6,
129+
VersionSpecificValidationError::SlotNumberNotSupported,
130130
),
131131
) |
132132
EngineApiError::UnexpectedRequestsHash => {

0 commit comments

Comments
 (0)