ethrex_rpc/clients/auth/
mod.rs1use crate::{
2 engine::{
3 ExchangeCapabilitiesRequest,
4 fork_choice::ForkChoiceUpdatedV3,
5 payload::{GetPayloadV5Request, NewPayloadV4Request, NewPayloadV5Request},
6 },
7 types::{
8 fork_choice::{ForkChoiceResponse, ForkChoiceState, PayloadAttributesV3},
9 payload::{ExecutionPayload, ExecutionPayloadResponse, PayloadStatus},
10 },
11 utils::{RpcRequest, RpcResponse},
12};
13use bytes::Bytes;
14use errors::{
15 EngineClientError, ExchangeCapabilitiesError, ForkChoiceUpdatedError, GetPayloadError,
16 NewPayloadError,
17};
18use ethrex_common::H256;
19use reqwest::Client;
20use serde_json::json;
21use std::time::{SystemTime, UNIX_EPOCH};
22
23pub mod errors;
24
25#[derive(Debug, Clone)]
26pub struct EngineClient {
27 client: Client,
28 secret: Bytes,
29 execution_client_url: String,
30}
31
32impl EngineClient {
33 pub fn new(execution_client_url: &str, secret: Bytes) -> Self {
34 Self {
35 client: Client::new(),
36 secret,
37 execution_client_url: execution_client_url.to_string(),
38 }
39 }
40
41 async fn send_request(&self, request: RpcRequest) -> Result<RpcResponse, EngineClientError> {
42 self.client
43 .post(&self.execution_client_url)
44 .bearer_auth(self.auth_token()?)
45 .header("content-type", "application/json")
46 .body(serde_json::ser::to_string(&request).map_err(|error| {
47 EngineClientError::FailedToSerializeRequestBody(format!("{error}: {request:?}"))
48 })?)
49 .send()
50 .await?
51 .json::<RpcResponse>()
52 .await
53 .map_err(EngineClientError::from)
54 }
55
56 pub async fn engine_exchange_capabilities(&self) -> Result<Vec<String>, EngineClientError> {
57 let request = ExchangeCapabilitiesRequest::from(Self::capabilities()).into();
58
59 match self.send_request(request).await? {
60 RpcResponse::Success(result) => serde_json::from_value(result.result)
61 .map_err(ExchangeCapabilitiesError::SerdeJSONError)
62 .map_err(EngineClientError::from),
63 RpcResponse::Error(error_response) => {
64 let error_message = if let Some(data) = error_response.error.data {
65 format!("{}: {:?}", error_response.error.message, data)
66 } else {
67 error_response.error.message.to_string()
68 };
69 Err(ExchangeCapabilitiesError::RPCError(error_message).into())
70 }
71 }
72 }
73
74 pub async fn engine_forkchoice_updated_v3(
75 &self,
76 state: ForkChoiceState,
77 payload_attributes: Option<PayloadAttributesV3>,
78 ) -> Result<ForkChoiceResponse, EngineClientError> {
79 let request = RpcRequest::from(ForkChoiceUpdatedV3 {
80 fork_choice_state: state,
81 payload_attributes,
82 });
83
84 match self.send_request(request).await? {
85 RpcResponse::Success(result) => serde_json::from_value(result.result)
86 .map_err(ForkChoiceUpdatedError::SerdeJSONError)
87 .map_err(EngineClientError::from),
88 RpcResponse::Error(error_response) => {
89 let error_message = if let Some(data) = error_response.error.data {
90 format!("{}: {:?}", error_response.error.message, data)
91 } else {
92 error_response.error.message.to_string()
93 };
94 Err(ForkChoiceUpdatedError::RPCError(error_message).into())
95 }
96 }
97 }
98
99 pub async fn engine_get_payload_v5(
100 &self,
101 payload_id: u64,
102 ) -> Result<ExecutionPayloadResponse, EngineClientError> {
103 let request = GetPayloadV5Request { payload_id }.into();
104
105 match self.send_request(request).await? {
106 RpcResponse::Success(result) => serde_json::from_value(result.result)
107 .map_err(GetPayloadError::SerdeJSONError)
108 .map_err(EngineClientError::from),
109 RpcResponse::Error(error_response) => {
110 let error_message = if let Some(data) = error_response.error.data {
111 format!("{}: {:?}", error_response.error.message, data)
112 } else {
113 error_response.error.message.to_string()
114 };
115 Err(GetPayloadError::RPCError(error_message).into())
116 }
117 }
118 }
119
120 pub async fn engine_new_payload_v4(
121 &self,
122 execution_payload: ExecutionPayload,
123 expected_blob_versioned_hashes: Vec<H256>,
124 parent_beacon_block_root: H256,
125 ) -> Result<PayloadStatus, EngineClientError> {
126 let request = NewPayloadV4Request {
127 payload: execution_payload,
128 expected_blob_versioned_hashes,
129 parent_beacon_block_root,
130 execution_requests: vec![],
131 }
132 .into();
133
134 match self.send_request(request).await? {
135 RpcResponse::Success(result) => serde_json::from_value(result.result)
136 .map_err(NewPayloadError::SerdeJSONError)
137 .map_err(EngineClientError::from),
138 RpcResponse::Error(error_response) => {
139 let error_message = if let Some(data) = error_response.error.data {
140 format!("{}: {:?}", error_response.error.message, data)
141 } else {
142 error_response.error.message.to_string()
143 };
144 Err(NewPayloadError::RPCError(error_message).into())
145 }
146 }
147 }
148
149 pub async fn engine_new_payload_v5(
153 &self,
154 execution_payload: ExecutionPayload,
155 expected_blob_versioned_hashes: Vec<H256>,
156 parent_beacon_block_root: H256,
157 ) -> Result<PayloadStatus, EngineClientError> {
158 let request = NewPayloadV5Request {
159 payload: execution_payload,
160 expected_blob_versioned_hashes,
161 parent_beacon_block_root,
162 execution_requests: vec![],
166 raw_bal_hash: None,
167 }
168 .into();
169
170 match self.send_request(request).await? {
171 RpcResponse::Success(result) => serde_json::from_value(result.result)
172 .map_err(NewPayloadError::SerdeJSONError)
173 .map_err(EngineClientError::from),
174 RpcResponse::Error(error_response) => {
175 let error_message = if let Some(data) = error_response.error.data {
176 format!("{}: {:?}", error_response.error.message, data)
177 } else {
178 error_response.error.message.to_string()
179 };
180 Err(NewPayloadError::RPCError(error_message).into())
181 }
182 }
183 }
184
185 fn auth_token(&self) -> Result<String, EngineClientError> {
186 let header = jsonwebtoken::Header::default();
188 let valid_iat = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
190 let claims = json!({"iat": valid_iat});
191 let encoding_key = jsonwebtoken::EncodingKey::from_secret(&self.secret);
192 jsonwebtoken::encode(&header, &claims, &encoding_key).map_err(EngineClientError::from)
194 }
195
196 fn capabilities() -> Vec<String> {
197 vec![
198 "engine_exchangeCapabilities".to_owned(),
199 "engine_forkchoiceUpdatedV3".to_owned(),
200 "engine_getPayloadV4".to_owned(),
201 "engine_getPayloadV5".to_owned(),
202 "engine_newPayloadV4".to_owned(),
203 ]
204 }
205}