Skip to main content

ethrex_rpc/engine/
client_version.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use tracing::debug;
4
5use crate::{
6    rpc::{ClientVersion, RpcApiContext, RpcHandler},
7    utils::RpcErr,
8};
9
10/// Client version information as defined in the Engine API specification.
11///
12/// This structure identifies a client implementation with a standardized format
13/// that includes both human-readable and machine-readable fields.
14///
15/// See: https://github.com/ethereum/execution-apis/blob/main/src/engine/identification.md
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17pub struct ClientVersionV1 {
18    /// Two-letter client code (e.g., "GE" for go-ethereum, "NM" for nethermind).
19    /// Ethrex uses "EX".
20    pub code: String,
21    /// Human-readable name of the client (e.g., "ethrex", "go-ethereum").
22    pub name: String,
23    /// Version string of the client (e.g., "v0.1.0", "1.0.0-alpha.1").
24    pub version: String,
25    /// First four bytes of the latest commit hash, hex-encoded (e.g., "fa4ff922").
26    pub commit: String,
27}
28
29impl ClientVersionV1 {
30    /// Creates a new ClientVersionV1 for the ethrex client from the ClientVersion struct.
31    pub fn from_client_version(cv: &ClientVersion) -> Self {
32        // Take up to the first 8 characters (4 bytes) of the full commit hash.
33        // Using chars().take() avoids panicking if the commit string is shorter.
34        let commit = cv.commit.chars().take(8).collect::<String>();
35
36        Self {
37            code: "EX".to_string(),
38            name: cv.name.clone(),
39            version: format!("v{}", cv.version),
40            commit,
41        }
42    }
43}
44
45/// Request handler for `engine_getClientVersionV1`.
46///
47/// This method allows consensus and execution layer clients to exchange version
48/// information. The execution client returns its own version information in response.
49#[derive(Debug)]
50pub struct GetClientVersionV1Request {
51    /// The consensus client's version information (provided as input parameter).
52    #[allow(dead_code)]
53    consensus_client: ClientVersionV1,
54}
55
56impl std::fmt::Display for GetClientVersionV1Request {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(
59            f,
60            "GetClientVersionV1Request {{ consensus_client: {} {} {} }}",
61            self.consensus_client.code, self.consensus_client.name, self.consensus_client.version
62        )
63    }
64}
65
66impl RpcHandler for GetClientVersionV1Request {
67    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
68        let params = params
69            .as_ref()
70            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
71        if params.len() != 1 {
72            return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
73        }
74        let consensus_client: ClientVersionV1 = serde_json::from_value(params[0].clone())?;
75        Ok(GetClientVersionV1Request { consensus_client })
76    }
77
78    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
79        debug!("Requested engine_getClientVersionV1: {self}");
80
81        // Return an array with a single ClientVersionV1 for this execution client.
82        // When connected to multiple execution clients via a multiplexer, the multiplexer
83        // would concatenate responses, but ethrex is a single client.
84        let client_version =
85            ClientVersionV1::from_client_version(&context.node_data.client_version);
86
87        serde_json::to_value(vec![client_version])
88            .map_err(|error| RpcErr::Internal(error.to_string()))
89    }
90}