reth_rpc_api/
reth_engine.rs1use alloy_primitives::Bytes;
4use alloy_rpc_types_engine::{ForkchoiceState, ForkchoiceUpdated, PayloadStatus};
5use jsonrpsee::{core::RpcResult, proc_macros::rpc};
6use serde::{Deserialize, Deserializer, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct RethPayloadStatus {
11 #[serde(flatten)]
13 pub status: PayloadStatus,
14 pub latency_us: u64,
16 pub persistence_wait_us: u64,
20 #[serde(skip_serializing_if = "Option::is_none")]
24 pub execution_cache_wait_us: Option<u64>,
25 #[serde(skip_serializing_if = "Option::is_none")]
29 pub sparse_trie_wait_us: Option<u64>,
30}
31
32#[derive(Debug, Clone, Serialize)]
35#[serde(untagged)]
36pub enum RethNewPayloadInput<ExecutionData> {
37 ExecutionData(ExecutionData),
39 BlockRlp {
41 block: Bytes,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 bal: Option<Bytes>,
46 },
47}
48
49impl<'de, ExecutionData> Deserialize<'de> for RethNewPayloadInput<ExecutionData>
50where
51 ExecutionData: Deserialize<'de>,
52{
53 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54 where
55 D: Deserializer<'de>,
56 {
57 #[derive(Deserialize)]
58 #[serde(untagged)]
59 enum RethNewPayloadInputSerde<ExecutionData> {
60 ExecutionData(ExecutionData),
61 BlockRlp {
62 block: Bytes,
63 #[serde(default)]
64 bal: Option<Bytes>,
65 },
66 LegacyBlockRlp(Bytes),
67 }
68
69 Ok(match RethNewPayloadInputSerde::deserialize(deserializer)? {
70 RethNewPayloadInputSerde::ExecutionData(data) => Self::ExecutionData(data),
71 RethNewPayloadInputSerde::BlockRlp { block, bal } => Self::BlockRlp { block, bal },
72 RethNewPayloadInputSerde::LegacyBlockRlp(block) => Self::BlockRlp { block, bal: None },
73 })
74 }
75}
76
77#[cfg_attr(not(feature = "client"), rpc(server, namespace = "reth"))]
89#[cfg_attr(feature = "client", rpc(server, client, namespace = "reth"))]
90pub trait RethEngineApi<ExecutionData> {
91 #[method(name = "newPayload")]
97 async fn reth_new_payload(
98 &self,
99 payload: RethNewPayloadInput<ExecutionData>,
100 wait_for_persistence: Option<bool>,
101 wait_for_caches: Option<bool>,
102 ) -> RpcResult<RethPayloadStatus>;
103
104 #[method(name = "forkchoiceUpdated")]
107 async fn reth_forkchoice_updated(
108 &self,
109 forkchoice_state: ForkchoiceState,
110 ) -> RpcResult<ForkchoiceUpdated>;
111}
112
113#[cfg(test)]
114mod tests {
115 use super::RethNewPayloadInput;
116 use alloy_primitives::Bytes;
117 use serde::{Deserialize, Serialize};
118 use serde_json::json;
119
120 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121 struct TestExecutionData {
122 payload: Bytes,
123 sidecar: Bytes,
124 }
125
126 #[test]
127 fn block_rlp_serializes_named_fields() {
128 let input = RethNewPayloadInput::<TestExecutionData>::BlockRlp {
129 block: Bytes::from_static(&[1]),
130 bal: Some(Bytes::from_static(&[2])),
131 };
132
133 assert_eq!(serde_json::to_value(input).unwrap(), json!({ "block": "0x01", "bal": "0x02" }));
134 }
135
136 #[test]
137 fn block_rlp_deserializes_without_bal() {
138 let input = serde_json::from_value::<RethNewPayloadInput<TestExecutionData>>(json!({
139 "block": "0x01"
140 }))
141 .unwrap();
142
143 let RethNewPayloadInput::BlockRlp { block, bal } = input else {
144 panic!("expected block rlp input")
145 };
146
147 assert_eq!(block, Bytes::from_static(&[1]));
148 assert_eq!(bal, None);
149 }
150
151 #[test]
152 fn block_rlp_deserializes_legacy_bytes() {
153 let input = serde_json::from_value::<RethNewPayloadInput<TestExecutionData>>(json!("0x01"))
154 .unwrap();
155
156 let RethNewPayloadInput::BlockRlp { block, bal } = input else {
157 panic!("expected block rlp input")
158 };
159
160 assert_eq!(block, Bytes::from_static(&[1]));
161 assert_eq!(bal, None);
162 }
163}