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"))]
108#[cfg_attr(feature = "client", rpc(server, client, namespace = "reth"))]
109pub trait RethEngineApi<ExecutionData> {
110 #[method(name = "newPayload")]
116 async fn reth_new_payload(
117 &self,
118 payload: RethNewPayloadInput<ExecutionData>,
119 wait_for_persistence: Option<bool>,
120 wait_for_caches: Option<bool>,
121 ) -> RpcResult<RethPayloadStatus>;
122
123 #[method(name = "forkchoiceUpdated")]
126 async fn reth_forkchoice_updated(
127 &self,
128 forkchoice_state: ForkchoiceState,
129 ) -> RpcResult<ForkchoiceUpdated>;
130}
131
132#[cfg(test)]
133mod tests {
134 use super::RethNewPayloadInput;
135 use alloy_primitives::Bytes;
136 use serde::{Deserialize, Serialize};
137 use serde_json::json;
138
139 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140 struct TestExecutionData {
141 payload: Bytes,
142 sidecar: Bytes,
143 }
144
145 #[test]
146 fn block_rlp_serializes_named_fields() {
147 let input = RethNewPayloadInput::<TestExecutionData>::BlockRlp {
148 block: Bytes::from_static(&[1]),
149 bal: Some(Bytes::from_static(&[2])),
150 };
151
152 assert_eq!(serde_json::to_string(&input).unwrap(), r#"{"block":"0x01","bal":"0x02"}"#);
153 assert_eq!(serde_json::to_value(input).unwrap(), json!({ "block": "0x01", "bal": "0x02" }));
154 }
155
156 #[test]
157 fn block_rlp_without_bal_serializes_legacy_bytes_roundtrip() {
158 let input = RethNewPayloadInput::<TestExecutionData>::BlockRlp {
159 block: Bytes::from_static(&[1]),
160 bal: None,
161 };
162
163 let serialized = serde_json::to_string(&input).unwrap();
164 assert_eq!(serialized, r#""0x01""#);
165
166 let input =
167 serde_json::from_str::<RethNewPayloadInput<TestExecutionData>>(&serialized).unwrap();
168
169 let RethNewPayloadInput::BlockRlp { block, bal } = input else {
170 panic!("expected block rlp input")
171 };
172
173 assert_eq!(block, Bytes::from_static(&[1]));
174 assert_eq!(bal, None);
175 }
176
177 #[test]
178 fn block_rlp_deserializes_without_bal() {
179 let input =
180 serde_json::from_str::<RethNewPayloadInput<TestExecutionData>>(r#"{"block":"0x01"}"#)
181 .unwrap();
182
183 let RethNewPayloadInput::BlockRlp { block, bal } = input else {
184 panic!("expected block rlp input")
185 };
186
187 assert_eq!(block, Bytes::from_static(&[1]));
188 assert_eq!(bal, None);
189 }
190
191 #[test]
192 fn block_rlp_deserializes_legacy_bytes() {
193 let input = serde_json::from_value::<RethNewPayloadInput<TestExecutionData>>(json!("0x01"))
194 .unwrap();
195
196 let RethNewPayloadInput::BlockRlp { block, bal } = input else {
197 panic!("expected block rlp input")
198 };
199
200 assert_eq!(block, Bytes::from_static(&[1]));
201 assert_eq!(bal, None);
202 }
203}