1use alloy_eips::BlockId;
4use alloy_primitives::{Address, B256, Bytes, U64, U256, map::HashMap};
5use alloy_rpc_types_engine::PayloadStatus;
6use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeStruct};
7
8pub type BalanceChangesInBlock = HashMap<Address, U256>;
12
13#[allow(clippy::large_enum_variant)]
61#[derive(Debug, Clone)]
62pub enum RethNewPayloadInput<ExecutionData> {
63 ExecutionData(ExecutionData),
65 BigBlockData(Box<BigBlockData<ExecutionData>>),
67 BlockRlp {
69 block: Bytes,
71 bal: Option<Bytes>,
73 },
74}
75
76impl<E> Serialize for RethNewPayloadInput<E>
77where
78 E: Serialize,
79{
80 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81 where
82 S: Serializer,
83 {
84 match self {
85 Self::ExecutionData(data) => data.serialize(serializer),
86 Self::BigBlockData(data) => data.serialize(serializer),
87 Self::BlockRlp { block, bal: None } => block.serialize(serializer),
88 Self::BlockRlp {
89 block,
90 bal: Some(bal),
91 } => {
92 let mut object = serializer.serialize_struct("RethNewPayloadBlockRlp", 2)?;
93 object.serialize_field("block", block)?;
94 object.serialize_field("bal", bal)?;
95 object.end()
96 }
97 }
98 }
99}
100
101impl<'de, E> Deserialize<'de> for RethNewPayloadInput<E>
102where
103 E: Deserialize<'de>,
104{
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: Deserializer<'de>,
108 {
109 #[derive(Deserialize)]
110 #[serde(untagged)]
111 enum Repr<E> {
112 BlockRlp {
113 block: Bytes,
114 #[serde(default)]
115 bal: Option<Bytes>,
116 },
117 LegacyBlockRlp(Bytes),
118 BigBlockData(Box<BigBlockData<E>>),
119 ExecutionData(E),
120 }
121
122 Ok(match Repr::deserialize(deserializer)? {
123 Repr::BlockRlp { block, bal } => Self::BlockRlp { block, bal },
124 Repr::LegacyBlockRlp(block) => Self::BlockRlp { block, bal: None },
125 Repr::BigBlockData(data) => Self::BigBlockData(data),
126 Repr::ExecutionData(data) => Self::ExecutionData(data),
127 })
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub struct BigBlockData<ExecutionData> {
134 pub env_switches: Vec<ExecutionData>,
136 pub prior_block_hashes: Vec<(u64, B256)>,
138 pub block_number: u64,
140 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub merged_block_access_list: Option<Bytes>,
143}
144
145pub type RethBigBlockData<ExecutionData> = BigBlockData<ExecutionData>;
147
148impl<ExecutionData> BigBlockData<ExecutionData> {
149 pub const fn new(
151 env_switches: Vec<ExecutionData>,
152 prior_block_hashes: Vec<(u64, B256)>,
153 block_number: u64,
154 ) -> Self {
155 Self {
156 env_switches,
157 prior_block_hashes,
158 block_number,
159 merged_block_access_list: None,
160 }
161 }
162
163 pub fn with_merged_block_access_list(mut self, bal: impl Into<Bytes>) -> Self {
165 self.merged_block_access_list = Some(bal.into());
166 self
167 }
168}
169
170impl<E> RethNewPayloadInput<E> {
171 pub const fn execution_data(data: E) -> Self {
173 Self::ExecutionData(data)
174 }
175
176 pub fn big_block_data(data: BigBlockData<E>) -> Self {
178 Self::BigBlockData(Box::new(data))
179 }
180
181 pub fn block_rlp(bytes: impl Into<Bytes>) -> Self {
183 Self::BlockRlp {
184 block: bytes.into(),
185 bal: None,
186 }
187 }
188
189 pub fn block_rlp_with_bal(block: impl Into<Bytes>, bal: impl Into<Bytes>) -> Self {
191 Self::BlockRlp {
192 block: block.into(),
193 bal: Some(bal.into()),
194 }
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200#[non_exhaustive]
201pub struct RethNewPayloadParams<E = serde_json::Value> {
202 pub payload: RethNewPayloadInput<E>,
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub wait_for_persistence: Option<bool>,
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub wait_for_caches: Option<bool>,
210}
211
212impl<E> RethNewPayloadParams<E> {
213 pub const fn new(payload: RethNewPayloadInput<E>) -> Self {
215 Self {
216 payload,
217 wait_for_persistence: None,
218 wait_for_caches: None,
219 }
220 }
221
222 pub const fn with_wait_for_persistence(mut self, wait: bool) -> Self {
224 self.wait_for_persistence = Some(wait);
225 self
226 }
227
228 pub const fn with_wait_for_caches(mut self, wait: bool) -> Self {
230 self.wait_for_caches = Some(wait);
231 self
232 }
233}
234
235impl<E> From<RethNewPayloadInput<E>> for RethNewPayloadParams<E> {
236 fn from(payload: RethNewPayloadInput<E>) -> Self {
237 Self::new(payload)
238 }
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
245#[non_exhaustive]
246pub struct RethPayloadStatus {
247 #[serde(flatten)]
249 pub status: PayloadStatus,
250 #[serde(default)]
252 pub latency_us: u64,
253 #[serde(skip_serializing_if = "Option::is_none")]
255 pub persistence_wait_us: Option<u64>,
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub execution_cache_wait_us: Option<u64>,
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub sparse_trie_wait_us: Option<u64>,
262}
263
264impl RethPayloadStatus {
265 pub const fn new(status: PayloadStatus, latency_us: u64) -> Self {
267 Self {
268 status,
269 latency_us,
270 persistence_wait_us: None,
271 execution_cache_wait_us: None,
272 sparse_trie_wait_us: None,
273 }
274 }
275
276 pub const fn with_persistence_wait_us(mut self, us: u64) -> Self {
278 self.persistence_wait_us = Some(us);
279 self
280 }
281
282 pub const fn with_execution_cache_wait_us(mut self, us: u64) -> Self {
284 self.execution_cache_wait_us = Some(us);
285 self
286 }
287
288 pub const fn with_sparse_trie_wait_us(mut self, us: u64) -> Self {
290 self.sparse_trie_wait_us = Some(us);
291 self
292 }
293}
294
295impl AsRef<PayloadStatus> for RethPayloadStatus {
296 fn as_ref(&self) -> &PayloadStatus {
297 &self.status
298 }
299}
300
301#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
303#[non_exhaustive]
304pub struct GetBlockExecutionOutcomeParams {
305 pub block_id: BlockId,
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub count: Option<U64>,
310}
311
312impl GetBlockExecutionOutcomeParams {
313 pub const fn new(block_id: BlockId) -> Self {
315 Self {
316 block_id,
317 count: None,
318 }
319 }
320
321 pub fn with_count(mut self, count: impl Into<U64>) -> Self {
323 self.count = Some(count.into());
324 self
325 }
326}
327
328impl From<BlockId> for GetBlockExecutionOutcomeParams {
329 fn from(block_id: BlockId) -> Self {
330 Self::new(block_id)
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
336#[serde(tag = "type", rename_all = "lowercase")]
337#[non_exhaustive]
338pub enum CanonStateNotification {
339 Commit {
341 new: serde_json::Value,
343 },
344 Reorg {
346 old: serde_json::Value,
348 new: serde_json::Value,
350 },
351}
352
353impl CanonStateNotification {
354 pub const fn commit(new: serde_json::Value) -> Self {
356 Self::Commit { new }
357 }
358
359 pub const fn reorg(old: serde_json::Value, new: serde_json::Value) -> Self {
361 Self::Reorg { old, new }
362 }
363
364 pub const fn is_commit(&self) -> bool {
366 matches!(self, Self::Commit { .. })
367 }
368
369 pub const fn is_reorg(&self) -> bool {
371 matches!(self, Self::Reorg { .. })
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use alloy_rpc_types_engine::PayloadStatusEnum;
379 use serde_json::json;
380
381 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
382 struct TestExecutionData {
383 payload: Bytes,
384 sidecar: Bytes,
385 }
386
387 fn execution_data() -> TestExecutionData {
388 TestExecutionData {
389 payload: Bytes::from_static(&[0x01]),
390 sidecar: Bytes::from_static(&[0x02]),
391 }
392 }
393
394 #[test]
395 fn execution_data_round_trips() {
396 let input = RethNewPayloadInput::execution_data(execution_data());
397 let value = serde_json::to_value(&input).unwrap();
398 assert_eq!(value, json!({"payload": "0x01", "sidecar": "0x02"}));
399
400 let decoded: RethNewPayloadInput<TestExecutionData> =
401 serde_json::from_value(value).unwrap();
402 assert!(
403 matches!(decoded, RethNewPayloadInput::ExecutionData(data) if data == execution_data())
404 );
405 }
406
407 #[test]
408 fn big_block_data_round_trips_with_and_without_bal() {
409 let hash = B256::from([0x11; 32]);
410 let data = BigBlockData {
411 env_switches: vec![execution_data()],
412 prior_block_hashes: vec![(7, hash)],
413 block_number: 8,
414 merged_block_access_list: None,
415 };
416 let input = RethNewPayloadInput::big_block_data(data.clone());
417 let value = serde_json::to_value(&input).unwrap();
418 assert_eq!(
419 value,
420 json!({
421 "env_switches": [{"payload": "0x01", "sidecar": "0x02"}],
422 "prior_block_hashes": [[7, hash]],
423 "block_number": 8,
424 })
425 );
426
427 let decoded: RethNewPayloadInput<TestExecutionData> =
428 serde_json::from_value(value).unwrap();
429 assert!(matches!(decoded, RethNewPayloadInput::BigBlockData(decoded) if *decoded == data));
430
431 let with_bal = data.with_merged_block_access_list(Bytes::from_static(&[0x03]));
432 let input = RethNewPayloadInput::big_block_data(with_bal.clone());
433 let value = serde_json::to_value(&input).unwrap();
434 assert_eq!(value["merged_block_access_list"], "0x03");
435 let decoded: RethNewPayloadInput<TestExecutionData> =
436 serde_json::from_value(value).unwrap();
437 assert!(
438 matches!(decoded, RethNewPayloadInput::BigBlockData(decoded) if *decoded == with_bal)
439 );
440 }
441
442 #[test]
443 fn raw_rlp_serializes_as_legacy_bytes_without_bal() {
444 let input =
445 RethNewPayloadInput::<TestExecutionData>::block_rlp(Bytes::from_static(&[0x01, 0x02]));
446 assert_eq!(serde_json::to_value(&input).unwrap(), json!("0x0102"));
447
448 let decoded: RethNewPayloadInput<TestExecutionData> =
449 serde_json::from_value(json!("0x0102")).unwrap();
450 assert!(
451 matches!(decoded, RethNewPayloadInput::BlockRlp { block, bal: None } if block == Bytes::from_static(&[0x01, 0x02]))
452 );
453 }
454
455 #[test]
456 fn raw_rlp_serializes_as_object_with_bal_and_decodes_legacy_object() {
457 let input = RethNewPayloadInput::<TestExecutionData>::block_rlp_with_bal(
458 Bytes::from_static(&[0x01]),
459 Bytes::from_static(&[0x02]),
460 );
461 assert_eq!(
462 serde_json::to_value(&input).unwrap(),
463 json!({"block": "0x01", "bal": "0x02"})
464 );
465
466 for value in [
467 json!({"block": "0x01", "bal": "0x02"}),
468 json!({"block": "0x01"}),
469 ] {
470 let decoded: RethNewPayloadInput<TestExecutionData> =
471 serde_json::from_value(value).unwrap();
472 assert!(
473 matches!(decoded, RethNewPayloadInput::BlockRlp { block, .. } if block == Bytes::from_static(&[0x01]))
474 );
475 }
476 }
477
478 #[test]
479 fn new_payload_params_omit_unset_wait_flags() {
480 let params =
481 RethNewPayloadParams::new(RethNewPayloadInput::execution_data(execution_data()));
482 assert_eq!(
483 serde_json::to_value(params).unwrap(),
484 json!({"payload": {"payload": "0x01", "sidecar": "0x02"}})
485 );
486
487 let params =
488 RethNewPayloadParams::new(RethNewPayloadInput::execution_data(execution_data()))
489 .with_wait_for_persistence(true)
490 .with_wait_for_caches(false);
491 assert_eq!(
492 serde_json::to_value(params).unwrap(),
493 json!({
494 "payload": {"payload": "0x01", "sidecar": "0x02"},
495 "wait_for_persistence": true,
496 "wait_for_caches": false,
497 })
498 );
499 }
500
501 #[test]
502 fn payload_status_preserves_all_timing_fields() {
503 let status: RethPayloadStatus = serde_json::from_value(json!({
504 "status": "VALID",
505 "latestValidHash": null,
506 "latency_us": 12,
507 "persistence_wait_us": 3,
508 "execution_cache_wait_us": 4,
509 "sparse_trie_wait_us": 5,
510 }))
511 .unwrap();
512
513 assert_eq!(status.status.status, PayloadStatusEnum::Valid);
514 assert_eq!(status.latency_us, 12);
515 assert_eq!(status.persistence_wait_us, Some(3));
516 assert_eq!(status.execution_cache_wait_us, Some(4));
517 assert_eq!(status.sparse_trie_wait_us, Some(5));
518 }
519
520 #[test]
521 fn payload_status_accepts_missing_timing_fields() {
522 let status: RethPayloadStatus = serde_json::from_value(json!({
523 "status": "VALID",
524 "latestValidHash": null,
525 }))
526 .unwrap();
527
528 assert_eq!(status.status.status, PayloadStatusEnum::Valid);
529 assert_eq!(status.latency_us, 0);
530 assert_eq!(status.persistence_wait_us, None);
531 assert_eq!(status.execution_cache_wait_us, None);
532 assert_eq!(status.sparse_trie_wait_us, None);
533 }
534}