1use crate::valid_payload::call_forkchoice_updated;
4
5pub(crate) fn parse_gas_limit(s: &str) -> eyre::Result<u64> {
9 let s = s.trim();
10 if s.is_empty() {
11 return Err(eyre::eyre!("empty value"));
12 }
13
14 let (num_str, multiplier) = if let Some(prefix) = s.strip_suffix(['G', 'g']) {
15 (prefix, 1_000_000_000u64)
16 } else if let Some(prefix) = s.strip_suffix(['M', 'm']) {
17 (prefix, 1_000_000u64)
18 } else if let Some(prefix) = s.strip_suffix(['K', 'k']) {
19 (prefix, 1_000u64)
20 } else {
21 (s, 1u64)
22 };
23
24 let base: u64 = num_str.trim().parse()?;
25 base.checked_mul(multiplier).ok_or_else(|| eyre::eyre!("value overflow"))
26}
27use alloy_consensus::Header;
28use alloy_eips::eip4844::kzg_to_versioned_hash;
29use alloy_primitives::{Address, B256};
30use alloy_provider::{ext::EngineApi, network::AnyNetwork, RootProvider};
31use alloy_rpc_types_engine::{
32 CancunPayloadFields, ExecutionPayload, ExecutionPayloadSidecar, ForkchoiceState,
33 PayloadAttributes, PayloadId, PraguePayloadFields,
34};
35use eyre::OptionExt;
36use reth_chainspec::{ChainSpec, EthereumHardforks};
37use reth_node_api::EngineApiMessageVersion;
38use tracing::debug;
39
40pub(crate) struct PayloadRequest {
42 pub(crate) attributes: PayloadAttributes,
44 pub(crate) forkchoice_state: ForkchoiceState,
46 pub(crate) fcu_version: EngineApiMessageVersion,
48 pub(crate) get_payload_version: u8,
50 pub(crate) new_payload_version: EngineApiMessageVersion,
52}
53
54pub(crate) fn prepare_payload_request(
56 chain_spec: &ChainSpec,
57 timestamp: u64,
58 parent_hash: B256,
59) -> PayloadRequest {
60 let shanghai_active = chain_spec.is_shanghai_active_at_timestamp(timestamp);
61 let cancun_active = chain_spec.is_cancun_active_at_timestamp(timestamp);
62 let prague_active = chain_spec.is_prague_active_at_timestamp(timestamp);
63 let osaka_active = chain_spec.is_osaka_active_at_timestamp(timestamp);
64
65 let fcu_version = if cancun_active {
67 EngineApiMessageVersion::V3
68 } else if shanghai_active {
69 EngineApiMessageVersion::V2
70 } else {
71 EngineApiMessageVersion::V1
72 };
73
74 let (get_payload_version, new_payload_version) = if osaka_active {
77 (5, EngineApiMessageVersion::V4) } else if prague_active {
79 (4, EngineApiMessageVersion::V4)
80 } else if cancun_active {
81 (3, EngineApiMessageVersion::V3)
82 } else if shanghai_active {
83 (2, EngineApiMessageVersion::V2)
84 } else {
85 (1, EngineApiMessageVersion::V1)
86 };
87
88 PayloadRequest {
89 attributes: PayloadAttributes {
90 timestamp,
91 prev_randao: B256::ZERO,
92 suggested_fee_recipient: Address::ZERO,
93 withdrawals: shanghai_active.then(Vec::new),
94 parent_beacon_block_root: cancun_active.then_some(B256::ZERO),
95 },
96 forkchoice_state: ForkchoiceState {
97 head_block_hash: parent_hash,
98 safe_block_hash: parent_hash,
99 finalized_block_hash: parent_hash,
100 },
101 fcu_version,
102 get_payload_version,
103 new_payload_version,
104 }
105}
106
107pub(crate) async fn build_payload(
112 provider: &RootProvider<AnyNetwork>,
113 request: PayloadRequest,
114) -> eyre::Result<(ExecutionPayload, ExecutionPayloadSidecar)> {
115 let fcu_result = call_forkchoice_updated(
116 provider,
117 request.fcu_version,
118 request.forkchoice_state,
119 Some(request.attributes.clone()),
120 )
121 .await?;
122
123 let payload_id =
124 fcu_result.payload_id.ok_or_eyre("Payload builder did not return a payload id")?;
125
126 get_payload_with_sidecar(
127 provider,
128 request.get_payload_version,
129 payload_id,
130 request.attributes.parent_beacon_block_root,
131 )
132 .await
133}
134
135pub(crate) fn rpc_block_to_header(block: alloy_provider::network::AnyRpcBlock) -> (Header, B256) {
137 let block_hash = block.header.hash;
138 let header = block.header.inner.clone().into_header_with_defaults();
139 (header, block_hash)
140}
141
142fn versioned_hashes_from_commitments(
144 commitments: &[alloy_primitives::FixedBytes<48>],
145) -> Vec<B256> {
146 commitments.iter().map(|c| kzg_to_versioned_hash(c.as_ref())).collect()
147}
148
149pub(crate) async fn get_payload_with_sidecar(
151 provider: &RootProvider<AnyNetwork>,
152 version: u8,
153 payload_id: PayloadId,
154 parent_beacon_block_root: Option<B256>,
155) -> eyre::Result<(ExecutionPayload, ExecutionPayloadSidecar)> {
156 debug!(get_payload_version = ?version, ?payload_id, "Sending getPayload");
157
158 match version {
159 1 => {
160 let payload = provider.get_payload_v1(payload_id).await?;
161 Ok((ExecutionPayload::V1(payload), ExecutionPayloadSidecar::none()))
162 }
163 2 => {
164 let envelope = provider.get_payload_v2(payload_id).await?;
165 let payload = match envelope.execution_payload {
166 alloy_rpc_types_engine::ExecutionPayloadFieldV2::V1(p) => ExecutionPayload::V1(p),
167 alloy_rpc_types_engine::ExecutionPayloadFieldV2::V2(p) => ExecutionPayload::V2(p),
168 };
169 Ok((payload, ExecutionPayloadSidecar::none()))
170 }
171 3 => {
172 let envelope = provider.get_payload_v3(payload_id).await?;
173 let versioned_hashes =
174 versioned_hashes_from_commitments(&envelope.blobs_bundle.commitments);
175 let cancun_fields = CancunPayloadFields {
176 parent_beacon_block_root: parent_beacon_block_root
177 .ok_or_eyre("parent_beacon_block_root required for V3")?,
178 versioned_hashes,
179 };
180 Ok((
181 ExecutionPayload::V3(envelope.execution_payload),
182 ExecutionPayloadSidecar::v3(cancun_fields),
183 ))
184 }
185 4 => {
186 let envelope = provider.get_payload_v4(payload_id).await?;
187 let versioned_hashes = versioned_hashes_from_commitments(
188 &envelope.envelope_inner.blobs_bundle.commitments,
189 );
190 let cancun_fields = CancunPayloadFields {
191 parent_beacon_block_root: parent_beacon_block_root
192 .ok_or_eyre("parent_beacon_block_root required for V4")?,
193 versioned_hashes,
194 };
195 let prague_fields = PraguePayloadFields::new(envelope.execution_requests);
196 Ok((
197 ExecutionPayload::V3(envelope.envelope_inner.execution_payload),
198 ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
199 ))
200 }
201 5 => {
202 let envelope = provider.get_payload_v5(payload_id).await?;
204 let versioned_hashes =
205 versioned_hashes_from_commitments(&envelope.blobs_bundle.commitments);
206 let cancun_fields = CancunPayloadFields {
207 parent_beacon_block_root: parent_beacon_block_root
208 .ok_or_eyre("parent_beacon_block_root required for V5")?,
209 versioned_hashes,
210 };
211 let prague_fields = PraguePayloadFields::new(envelope.execution_requests);
212 Ok((
213 ExecutionPayload::V3(envelope.execution_payload),
214 ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
215 ))
216 }
217 _ => panic!("This tool does not support getPayload versions past v5"),
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_parse_gas_limit_plain_number() {
227 assert_eq!(parse_gas_limit("30000000").unwrap(), 30_000_000);
228 assert_eq!(parse_gas_limit("1").unwrap(), 1);
229 assert_eq!(parse_gas_limit("0").unwrap(), 0);
230 }
231
232 #[test]
233 fn test_parse_gas_limit_k_suffix() {
234 assert_eq!(parse_gas_limit("1K").unwrap(), 1_000);
235 assert_eq!(parse_gas_limit("30k").unwrap(), 30_000);
236 assert_eq!(parse_gas_limit("100K").unwrap(), 100_000);
237 }
238
239 #[test]
240 fn test_parse_gas_limit_m_suffix() {
241 assert_eq!(parse_gas_limit("1M").unwrap(), 1_000_000);
242 assert_eq!(parse_gas_limit("30m").unwrap(), 30_000_000);
243 assert_eq!(parse_gas_limit("100M").unwrap(), 100_000_000);
244 }
245
246 #[test]
247 fn test_parse_gas_limit_g_suffix() {
248 assert_eq!(parse_gas_limit("1G").unwrap(), 1_000_000_000);
249 assert_eq!(parse_gas_limit("2g").unwrap(), 2_000_000_000);
250 assert_eq!(parse_gas_limit("10G").unwrap(), 10_000_000_000);
251 }
252
253 #[test]
254 fn test_parse_gas_limit_with_whitespace() {
255 assert_eq!(parse_gas_limit(" 1G ").unwrap(), 1_000_000_000);
256 assert_eq!(parse_gas_limit("2 M").unwrap(), 2_000_000);
257 }
258
259 #[test]
260 fn test_parse_gas_limit_errors() {
261 assert!(parse_gas_limit("").is_err());
262 assert!(parse_gas_limit("abc").is_err());
263 assert!(parse_gas_limit("G").is_err());
264 assert!(parse_gas_limit("-1G").is_err());
265 }
266}