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::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
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)]
35pub enum RethNewPayloadInput<ExecutionData> {
36 ExecutionData(ExecutionData),
38 BlockRlp {
40 block: Bytes,
42 bal: Option<Bytes>,
44 },
45}
46
47impl<ExecutionData> Serialize for RethNewPayloadInput<ExecutionData>
48where
49 ExecutionData: Serialize,
50{
51 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52 where
53 S: Serializer,
54 {
55 match self {
56 Self::ExecutionData(data) => data.serialize(serializer),
57 Self::BlockRlp { block, bal: None } => block.serialize(serializer),
58 Self::BlockRlp { block, bal: Some(bal) } => {
59 let mut state = serializer.serialize_struct("BlockRlp", 2)?;
60 state.serialize_field("block", block)?;
61 state.serialize_field("bal", bal)?;
62 state.end()
63 }
64 }
65 }
66}
67
68impl<'de, ExecutionData> Deserialize<'de> for RethNewPayloadInput<ExecutionData>
69where
70 ExecutionData: Deserialize<'de>,
71{
72 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73 where
74 D: Deserializer<'de>,
75 {
76 #[derive(Deserialize)]
77 #[serde(untagged)]
78 enum RethNewPayloadInputSerde<ExecutionData> {
79 ExecutionData(ExecutionData),
80 BlockRlp {
81 block: Bytes,
82 #[serde(default)]
83 bal: Option<Bytes>,
84 },
85 LegacyBlockRlp(Bytes),
86 }
87
88 Ok(match RethNewPayloadInputSerde::deserialize(deserializer)? {
89 RethNewPayloadInputSerde::ExecutionData(data) => Self::ExecutionData(data),
90 RethNewPayloadInputSerde::BlockRlp { block, bal } => Self::BlockRlp { block, bal },
91 RethNewPayloadInputSerde::LegacyBlockRlp(block) => Self::BlockRlp { block, bal: None },
92 })
93 }
94}
95
96#[cfg_attr(not(feature = "client"), rpc(server, namespace = "reth"))]
109#[cfg_attr(feature = "client", rpc(server, client, namespace = "reth"))]
110pub trait RethEngineApi<ExecutionData> {
111 #[method(name = "newPayload")]
117 async fn reth_new_payload(
118 &self,
119 payload: RethNewPayloadInput<ExecutionData>,
120 wait_for_persistence: Option<bool>,
121 wait_for_caches: Option<bool>,
122 ) -> RpcResult<RethPayloadStatus>;
123
124 #[method(name = "forkchoiceUpdated")]
127 async fn reth_forkchoice_updated(
128 &self,
129 forkchoice_state: ForkchoiceState,
130 ) -> RpcResult<ForkchoiceUpdated>;
131}
132
133#[cfg(test)]
134mod tests {
135 use super::RethNewPayloadInput;
136 use alloy_primitives::Bytes;
137 use serde::{Deserialize, Serialize};
138 use serde_json::json;
139
140 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141 struct TestExecutionData {
142 payload: Bytes,
143 sidecar: Bytes,
144 }
145
146 #[test]
147 fn block_rlp_serializes_named_fields() {
148 let input = RethNewPayloadInput::<TestExecutionData>::BlockRlp {
149 block: Bytes::from_static(&[1]),
150 bal: Some(Bytes::from_static(&[2])),
151 };
152
153 assert_eq!(serde_json::to_string(&input).unwrap(), r#"{"block":"0x01","bal":"0x02"}"#);
154 assert_eq!(serde_json::to_value(input).unwrap(), json!({ "block": "0x01", "bal": "0x02" }));
155 }
156
157 #[test]
158 fn block_rlp_without_bal_serializes_legacy_bytes_roundtrip() {
159 let input = RethNewPayloadInput::<TestExecutionData>::BlockRlp {
160 block: Bytes::from_static(&[1]),
161 bal: None,
162 };
163
164 let serialized = serde_json::to_string(&input).unwrap();
165 assert_eq!(serialized, r#""0x01""#);
166
167 let input =
168 serde_json::from_str::<RethNewPayloadInput<TestExecutionData>>(&serialized).unwrap();
169
170 let RethNewPayloadInput::BlockRlp { block, bal } = input else {
171 panic!("expected block rlp input")
172 };
173
174 assert_eq!(block, Bytes::from_static(&[1]));
175 assert_eq!(bal, None);
176 }
177
178 #[test]
179 fn block_rlp_deserializes_without_bal() {
180 let input =
181 serde_json::from_str::<RethNewPayloadInput<TestExecutionData>>(r#"{"block":"0x01"}"#)
182 .unwrap();
183
184 let RethNewPayloadInput::BlockRlp { block, bal } = input else {
185 panic!("expected block rlp input")
186 };
187
188 assert_eq!(block, Bytes::from_static(&[1]));
189 assert_eq!(bal, None);
190 }
191
192 #[test]
193 fn block_rlp_deserializes_legacy_bytes() {
194 let input = serde_json::from_value::<RethNewPayloadInput<TestExecutionData>>(json!("0x01"))
195 .unwrap();
196
197 let RethNewPayloadInput::BlockRlp { block, bal } = input else {
198 panic!("expected block rlp input")
199 };
200
201 assert_eq!(block, Bytes::from_static(&[1]));
202 assert_eq!(bal, None);
203 }
204}