Skip to content

Commit 02da61a

Browse files
jsignCopilot
andauthored
add partial Amsterdam logic (#43)
Co-authored-by: Copilot <copilot@github.com>
1 parent 3387b8f commit 02da61a

5 files changed

Lines changed: 219 additions & 11 deletions

File tree

crates/stateless-validator-common/src/host.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use crate::{
99
guest::StatelessValidatorOutput,
1010
new_payload_request::{
1111
ConsolidationRequest, DepositRequest, ExecutionPayloadV1, ExecutionPayloadV2,
12-
ExecutionPayloadV3, ExecutionRequests, Hash32, NewPayloadRequest,
13-
NewPayloadRequestBellatrix, NewPayloadRequestCapella, NewPayloadRequestDeneb,
14-
NewPayloadRequestElectraFulu, WithdrawalRequest,
12+
ExecutionPayloadV3, ExecutionPayloadV4, ExecutionRequests, Hash32, NewPayloadRequest,
13+
NewPayloadRequestAmsterdam, NewPayloadRequestBellatrix, NewPayloadRequestCapella,
14+
NewPayloadRequestDeneb, NewPayloadRequestElectraFulu, WithdrawalRequest,
1515
},
1616
};
1717

@@ -66,6 +66,24 @@ impl NewPayloadRequest {
6666
},
6767
))
6868
}
69+
70+
/// Constructs a new [`NewPayloadRequest`] for Amsterdam.
71+
pub fn new_amsterdam(
72+
execution_payload: ExecutionPayloadV4,
73+
versioned_hashes: Vec<Hash32>,
74+
parent_beacon_block_root: Hash32,
75+
execution_requests: &[impl AsRef<[u8]>],
76+
) -> Result<Self> {
77+
let versioned_hashes = bounded_list(versioned_hashes, "versioned hashes")?;
78+
let execution_requests = decode_execution_requests(execution_requests)
79+
.context("Decoding execution requests failed")?;
80+
Ok(NewPayloadRequest::Amsterdam(NewPayloadRequestAmsterdam {
81+
execution_payload,
82+
versioned_hashes,
83+
parent_beacon_block_root,
84+
execution_requests,
85+
}))
86+
}
6987
}
7088

7189
fn bounded_list<T, const N: usize>(values: Vec<T>, label: &str) -> Result<SszList<T, N>> {

crates/stateless-validator-common/src/new_payload_request.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub const MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: usize = 16;
4343
pub const MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: usize = 2;
4444

4545
/// Composite types
46+
pub type BlockAccessList = SszList<u8, MAX_BYTES_PER_TRANSACTION>;
4647
pub type Transaction = SszList<u8, MAX_BYTES_PER_TRANSACTION>;
4748
pub type Transactions = SszList<Transaction, MAX_TRANSACTIONS_PER_PAYLOAD>;
4849
pub type Withdrawals = SszList<Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD>;
@@ -133,6 +134,7 @@ pub enum ForkName {
133134
Deneb,
134135
Electra,
135136
Fulu,
137+
Amsterdam,
136138
}
137139

138140
#[derive(Debug, Clone, HashTreeRoot, SszEncode, SszDecode)]
@@ -236,6 +238,46 @@ pub struct ExecutionPayloadV3 {
236238
pub excess_blob_gas: u64,
237239
}
238240

241+
#[derive(Debug, Clone, HashTreeRoot, SszEncode, SszDecode)]
242+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
243+
#[cfg_attr(
244+
feature = "rkyv",
245+
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
246+
)]
247+
pub struct ExecutionPayloadV4 {
248+
pub parent_hash: Hash32,
249+
pub fee_recipient: Address20,
250+
pub state_root: Hash32,
251+
pub receipts_root: Hash32,
252+
#[cfg_attr(feature = "serde", serde(with = "crate::serde_wrappers::bytes_array"))]
253+
pub logs_bloom: LogsBloom,
254+
pub prev_randao: Hash32,
255+
pub block_number: u64,
256+
pub gas_limit: u64,
257+
pub gas_used: u64,
258+
pub timestamp: u64,
259+
#[cfg_attr(feature = "serde", serde(with = "crate::serde_wrappers::ssz_list"))]
260+
#[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::AsSszList))]
261+
pub extra_data: ExtraData,
262+
pub base_fee_per_gas: Uint256Bytes,
263+
pub block_hash: Hash32,
264+
#[cfg_attr(
265+
feature = "serde",
266+
serde(with = "crate::serde_wrappers::nested_ssz_list")
267+
)]
268+
#[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::AsNestedSszList))]
269+
pub transactions: Transactions,
270+
#[cfg_attr(feature = "serde", serde(with = "crate::serde_wrappers::ssz_list"))]
271+
#[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::AsSszList))]
272+
pub withdrawals: Withdrawals,
273+
pub blob_gas_used: u64,
274+
pub excess_blob_gas: u64,
275+
#[cfg_attr(feature = "serde", serde(with = "crate::serde_wrappers::ssz_list"))]
276+
#[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::AsSszList))]
277+
pub block_access_list: BlockAccessList,
278+
pub slot_number: u64,
279+
}
280+
239281
#[derive(Debug, Clone, HashTreeRoot, SszEncode, SszDecode)]
240282
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
241283
#[cfg_attr(
@@ -285,6 +327,21 @@ pub struct NewPayloadRequestElectraFulu {
285327
pub execution_requests: ExecutionRequests,
286328
}
287329

330+
#[derive(Debug, Clone, HashTreeRoot, SszEncode, SszDecode)]
331+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
332+
#[cfg_attr(
333+
feature = "rkyv",
334+
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
335+
)]
336+
pub struct NewPayloadRequestAmsterdam {
337+
pub execution_payload: ExecutionPayloadV4,
338+
#[cfg_attr(feature = "serde", serde(with = "crate::serde_wrappers::ssz_list"))]
339+
#[cfg_attr(feature = "rkyv", rkyv(with = crate::rkyv_wrappers::AsSszList))]
340+
pub versioned_hashes: VersionedHashes,
341+
pub parent_beacon_block_root: Hash32,
342+
pub execution_requests: ExecutionRequests,
343+
}
344+
288345
#[derive(Debug, Clone)]
289346
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
290347
#[cfg_attr(
@@ -296,6 +353,7 @@ pub enum NewPayloadRequest {
296353
Capella(NewPayloadRequestCapella),
297354
Deneb(NewPayloadRequestDeneb),
298355
ElectraFulu(NewPayloadRequestElectraFulu),
356+
Amsterdam(NewPayloadRequestAmsterdam),
299357
}
300358

301359
impl NewPayloadRequest {
@@ -305,6 +363,7 @@ impl NewPayloadRequest {
305363
NewPayloadRequest::Capella(req) => req.hash_tree_root(hasher),
306364
NewPayloadRequest::Deneb(req) => req.hash_tree_root(hasher),
307365
NewPayloadRequest::ElectraFulu(req) => req.hash_tree_root(hasher),
366+
NewPayloadRequest::Amsterdam(req) => req.hash_tree_root(hasher),
308367
}
309368
}
310369
}

crates/stateless-validator-ethrex/src/host.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ fn from_reth_witness_to_ethrex_witness(
122122
bpo3_time: stateless_input.chain_config.bpo3_time,
123123
bpo4_time: stateless_input.chain_config.bpo4_time,
124124
bpo5_time: stateless_input.chain_config.bpo5_time,
125-
amsterdam_time: None,
125+
amsterdam_time: stateless_input.chain_config.amsterdam_time,
126126
enable_verkle_at_genesis: false,
127127
};
128128

crates/stateless-validator-reth/src/host.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use alloc::{format, vec::Vec};
44
use std::sync::Arc;
55

66
use alloy_consensus::Transaction;
7-
use alloy_eips::{Encodable2718, eip7685::Requests};
7+
use alloy_eips::{Encodable2718, eip7685::Requests, eip7928::BlockAccessList};
88
use alloy_genesis::{ChainConfig, Genesis};
99
use alloy_primitives::U256;
1010
use anyhow::Context;
@@ -17,8 +17,8 @@ pub use stateless::StatelessInput;
1717
use stateless::{UncompressedPublicKey, stateless_validation_with_trie};
1818
pub use stateless_validator_common::guest::StatelessValidatorOutput;
1919
use stateless_validator_common::new_payload_request::{
20-
ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, ExtraData, ForkName,
21-
NewPayloadRequest, Transaction as Tx, Transactions, Withdrawal, Withdrawals,
20+
ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, ExecutionPayloadV4, ExtraData,
21+
ForkName, NewPayloadRequest, Transaction as Tx, Transactions, Withdrawal, Withdrawals,
2222
};
2323
use tries::zeth::SparseState;
2424

@@ -67,6 +67,12 @@ where
6767
/// Determines the fork name based on alloy chain config and block timestamp.
6868
pub fn determine_fork_name(chain_config: &ChainConfig, timestamp: u64) -> ForkName {
6969
// Check forks in reverse chronological order
70+
if chain_config
71+
.amsterdam_time
72+
.is_some_and(|amsterdam_time| timestamp >= amsterdam_time)
73+
{
74+
return ForkName::Amsterdam;
75+
}
7076
if chain_config
7177
.osaka_time
7278
.is_some_and(|osaka_time| timestamp >= osaka_time)
@@ -95,7 +101,7 @@ pub fn determine_fork_name(chain_config: &ChainConfig, timestamp: u64) -> ForkNa
95101
}
96102

97103
/// Converts a [`StatelessInput`] to a [`NewPayloadRequest`].
98-
pub fn to_new_payload_request(
104+
fn to_new_payload_request(
99105
stateless_input: &StatelessInput,
100106
requests: Requests,
101107
) -> anyhow::Result<NewPayloadRequest> {
@@ -272,6 +278,60 @@ pub fn to_new_payload_request(
272278
&requests,
273279
)
274280
}
281+
ForkName::Amsterdam => {
282+
let withdrawals = build_withdrawals()?;
283+
// TODO(Amsterdam): use actual BAL that should be received from parameter.
284+
let block_access_list = Tx::try_from(alloy_rlp::encode(BlockAccessList::default()))
285+
.map_err(|err| {
286+
anyhow::anyhow!(
287+
"default block access list length should be within bounds: {err:?}"
288+
)
289+
})?;
290+
let slot_number = header.slot_number.unwrap_or_default();
291+
292+
let payload = ExecutionPayloadV4 {
293+
parent_hash: header.parent_hash.0,
294+
fee_recipient: header.beneficiary.0.0,
295+
state_root: header.state_root.0,
296+
receipts_root: header.receipts_root.0,
297+
logs_bloom,
298+
prev_randao: header.mix_hash.0,
299+
block_number: header.number,
300+
gas_limit: header.gas_limit,
301+
gas_used: header.gas_used,
302+
timestamp: header.timestamp,
303+
extra_data,
304+
base_fee_per_gas: U256::from(header.base_fee_per_gas.unwrap_or_default())
305+
.to_le_bytes(),
306+
block_hash: stateless_input.block.hash_slow().0,
307+
transactions,
308+
withdrawals,
309+
blob_gas_used: header.blob_gas_used.unwrap_or_default(),
310+
excess_blob_gas: header.excess_blob_gas.unwrap_or_default(),
311+
block_access_list,
312+
slot_number,
313+
};
314+
315+
let versioned_hashes: Vec<[u8; 32]> = body
316+
.transactions()
317+
.filter_map(|tx| tx.blob_versioned_hashes())
318+
.flatten()
319+
.map(|h| h.0)
320+
.collect();
321+
322+
let parent_beacon_block_root = stateless_input
323+
.block
324+
.parent_beacon_block_root
325+
.unwrap_or_default()
326+
.0;
327+
328+
NewPayloadRequest::new_amsterdam(
329+
payload,
330+
versioned_hashes,
331+
parent_beacon_block_root,
332+
&requests,
333+
)
334+
}
275335
}
276336
}
277337

crates/stateless-validator-reth/src/new_payload_request.rs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ use alloy_rpc_types_engine::{
99
CancunPayloadFields, ExecutionData, ExecutionPayload as AlloyExecutionPayload,
1010
ExecutionPayloadSidecar, ExecutionPayloadV1 as AlloyExecutionPayloadV1,
1111
ExecutionPayloadV2 as AlloyExecutionPayloadV2, ExecutionPayloadV3 as AlloyExecutionPayloadV3,
12-
PayloadError,
12+
ExecutionPayloadV4 as AlloyExecutionPayloadV4, PayloadError,
1313
};
1414
use anyhow::{Context, Result};
1515
use reth_chainspec::{ChainSpec, EthereumHardforks};
1616
use reth_payload_validator::{cancun, prague, shanghai};
1717
use reth_primitives_traits::{Block as _, SealedBlock, SignedTransaction};
1818
use stateless_validator_common::new_payload_request::{
19-
ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, NativeSha256Hasher,
20-
NewPayloadRequest, Withdrawal, compute_requests_hash,
19+
ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, ExecutionPayloadV4,
20+
NativeSha256Hasher, NewPayloadRequest, Withdrawal, compute_requests_hash,
2121
};
2222

2323
/// Converts a [`NewPayloadRequest`] into a validated reth [`Block`].
@@ -78,6 +78,8 @@ where
7878
chain_spec.is_prague_active_at_timestamp(sealed_block.timestamp),
7979
)?;
8080

81+
// TODO(Amsterdam): Add Amsterdam-specific validation.
82+
8183
Ok(sealed_block)
8284
}
8385

@@ -151,6 +153,42 @@ fn new_payload_request_to_execution_data(req: NewPayloadRequest) -> ExecutionDat
151153

152154
ExecutionData::new(AlloyExecutionPayload::V3(v3), sidecar)
153155
}
156+
NewPayloadRequest::Amsterdam(a) => {
157+
let execution_payload = a.execution_payload;
158+
let blob_gas_used = execution_payload.blob_gas_used;
159+
let excess_blob_gas = execution_payload.excess_blob_gas;
160+
let slot_number = execution_payload.slot_number;
161+
let block_access_list =
162+
Bytes::from(execution_payload.block_access_list.clone().into_inner());
163+
let (v1, withdrawals) = convert_v2_to_alloy_from_v4(execution_payload);
164+
let v4 = AlloyExecutionPayloadV4 {
165+
payload_inner: AlloyExecutionPayloadV3 {
166+
payload_inner: AlloyExecutionPayloadV2 {
167+
payload_inner: v1,
168+
withdrawals,
169+
},
170+
blob_gas_used,
171+
excess_blob_gas,
172+
},
173+
block_access_list,
174+
slot_number,
175+
};
176+
177+
let versioned_hashes: Vec<B256> =
178+
a.versioned_hashes.into_iter().map(B256::from).collect();
179+
let parent_beacon_block_root = B256::from(a.parent_beacon_block_root);
180+
let cancun_fields =
181+
CancunPayloadFields::new(parent_beacon_block_root, versioned_hashes);
182+
183+
let requests_hash = B256::from(compute_requests_hash(
184+
&a.execution_requests,
185+
&NativeSha256Hasher,
186+
));
187+
let prague_fields = alloy_rpc_types_engine::PraguePayloadFields::new(requests_hash);
188+
let sidecar = ExecutionPayloadSidecar::v4(cancun_fields, prague_fields);
189+
190+
ExecutionData::new(AlloyExecutionPayload::V4(v4), sidecar)
191+
}
154192
}
155193
}
156194

@@ -243,6 +281,39 @@ fn convert_v2_to_alloy_from_v3(
243281
(v1, withdrawals)
244282
}
245283

284+
fn convert_v2_to_alloy_from_v4(
285+
payload: ExecutionPayloadV4,
286+
) -> (AlloyExecutionPayloadV1, Vec<AlloyWithdrawal>) {
287+
let v1 = AlloyExecutionPayloadV1 {
288+
parent_hash: B256::from(payload.parent_hash),
289+
fee_recipient: Address::from(payload.fee_recipient),
290+
state_root: B256::from(payload.state_root),
291+
receipts_root: B256::from(payload.receipts_root),
292+
logs_bloom: Bloom::from_slice(&payload.logs_bloom[..]),
293+
prev_randao: B256::from(payload.prev_randao),
294+
block_number: payload.block_number,
295+
gas_limit: payload.gas_limit,
296+
gas_used: payload.gas_used,
297+
timestamp: payload.timestamp,
298+
extra_data: Bytes::from(payload.extra_data.into_inner()),
299+
base_fee_per_gas: U256::from_le_bytes(payload.base_fee_per_gas),
300+
block_hash: B256::from(payload.block_hash),
301+
transactions: payload
302+
.transactions
303+
.into_iter()
304+
.map(|tx| Bytes::from(tx.into_inner()))
305+
.collect(),
306+
};
307+
308+
let withdrawals = payload
309+
.withdrawals
310+
.into_iter()
311+
.map(convert_withdrawal)
312+
.collect();
313+
314+
(v1, withdrawals)
315+
}
316+
246317
fn convert_withdrawal(w: Withdrawal) -> AlloyWithdrawal {
247318
AlloyWithdrawal {
248319
index: w.index,

0 commit comments

Comments
 (0)