Encode Execution Engine Client Version In Graffiti (#5290)

* Add `engine_clientVersionV1` structs

* Implement `engine_clientVersionV1`

* Update to latest spec changes

* Implement GraffitiCalculator Service

* Added Unit Tests for GraffitiCalculator

* Address Mac's Comments

* Remove need to use clap in beacon chain

* Merge remote-tracking branch 'upstream/unstable' into el_client_version_graffiti

* Merge branch 'unstable' into el_client_version_graffiti

# Conflicts:
#	beacon_node/beacon_chain/Cargo.toml
This commit is contained in:
ethDreamer
2024-04-24 01:02:48 -05:00
committed by GitHub
parent c38b05d640
commit 4a48d7b546
20 changed files with 847 additions and 81 deletions

View File

@@ -1,9 +1,9 @@
use crate::engines::ForkchoiceState;
use crate::http::{
ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3,
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1,
ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_GET_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V1,
ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3,
ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2,
ENGINE_GET_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3,
};
use eth2::types::{
BlobsBundle, SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2,
@@ -24,11 +24,11 @@ pub use types::{
ExecutionPayloadRef, FixedVector, ForkName, Hash256, Transactions, Uint256, VariableList,
Withdrawal, Withdrawals,
};
use types::{
ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadMerge,
KzgProofs,
};
use types::{Graffiti, GRAFFITI_BYTES_LEN};
pub mod auth;
pub mod http;
@@ -61,13 +61,13 @@ pub enum Error {
ParentHashEqualsBlockHash(ExecutionBlockHash),
PayloadIdUnavailable,
TransitionConfigurationMismatch,
PayloadConversionLogicFlaw,
SszError(ssz_types::Error),
DeserializeWithdrawals(ssz_types::Error),
BuilderApi(builder_client::Error),
IncorrectStateVariant,
RequiredMethodUnsupported(&'static str),
UnsupportedForkVariant(String),
InvalidClientVersion(String),
RlpDecoderError(rlp::DecoderError),
}
@@ -652,6 +652,7 @@ pub struct EngineCapabilities {
pub get_payload_v1: bool,
pub get_payload_v2: bool,
pub get_payload_v3: bool,
pub get_client_version_v1: bool,
}
impl EngineCapabilities {
@@ -690,7 +691,141 @@ impl EngineCapabilities {
if self.get_payload_v3 {
response.push(ENGINE_GET_PAYLOAD_V3);
}
if self.get_client_version_v1 {
response.push(ENGINE_GET_CLIENT_VERSION_V1);
}
response
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ClientCode {
Besu,
EtherumJS,
Erigon,
GoEthereum,
Grandine,
Lighthouse,
Lodestar,
Nethermind,
Nimbus,
Teku,
Prysm,
Reth,
Unknown(String),
}
impl std::fmt::Display for ClientCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ClientCode::Besu => "BU",
ClientCode::EtherumJS => "EJ",
ClientCode::Erigon => "EG",
ClientCode::GoEthereum => "GE",
ClientCode::Grandine => "GR",
ClientCode::Lighthouse => "LH",
ClientCode::Lodestar => "LS",
ClientCode::Nethermind => "NM",
ClientCode::Nimbus => "NB",
ClientCode::Teku => "TK",
ClientCode::Prysm => "PM",
ClientCode::Reth => "RH",
ClientCode::Unknown(code) => code,
};
write!(f, "{}", s)
}
}
impl TryFrom<String> for ClientCode {
type Error = String;
fn try_from(code: String) -> Result<Self, Self::Error> {
match code.as_str() {
"BU" => Ok(Self::Besu),
"EJ" => Ok(Self::EtherumJS),
"EG" => Ok(Self::Erigon),
"GE" => Ok(Self::GoEthereum),
"GR" => Ok(Self::Grandine),
"LH" => Ok(Self::Lighthouse),
"LS" => Ok(Self::Lodestar),
"NM" => Ok(Self::Nethermind),
"NB" => Ok(Self::Nimbus),
"TK" => Ok(Self::Teku),
"PM" => Ok(Self::Prysm),
"RH" => Ok(Self::Reth),
string => {
if string.len() == 2 {
Ok(Self::Unknown(code))
} else {
Err(format!("Invalid client code: {}", code))
}
}
}
}
}
#[derive(Clone, Debug)]
pub struct CommitPrefix(pub String);
impl TryFrom<String> for CommitPrefix {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
// Check if the input starts with '0x' and strip it if it does
let commit_prefix = value.strip_prefix("0x").unwrap_or(&value);
// Ensure length is exactly 8 characters after '0x' removal
if commit_prefix.len() != 8 {
return Err(
"Input must be exactly 8 characters long (excluding any '0x' prefix)".to_string(),
);
}
// Ensure all characters are valid hex digits
if commit_prefix.chars().all(|c| c.is_ascii_hexdigit()) {
Ok(CommitPrefix(commit_prefix.to_lowercase()))
} else {
Err("Input must contain only hexadecimal characters".to_string())
}
}
}
impl std::fmt::Display for CommitPrefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug)]
pub struct ClientVersionV1 {
pub code: ClientCode,
pub name: String,
pub version: String,
pub commit: CommitPrefix,
}
impl ClientVersionV1 {
pub fn calculate_graffiti(&self, lighthouse_commit_prefix: CommitPrefix) -> Graffiti {
let graffiti_string = format!(
"{}{}LH{}",
self.code,
self.commit
.0
.get(..4)
.map_or_else(|| self.commit.0.as_str(), |s| s)
.to_lowercase(),
lighthouse_commit_prefix
.0
.get(..4)
.unwrap_or("0000")
.to_lowercase(),
);
let mut graffiti_bytes = [0u8; GRAFFITI_BYTES_LEN];
let bytes_to_copy = std::cmp::min(graffiti_string.len(), GRAFFITI_BYTES_LEN);
graffiti_bytes[..bytes_to_copy]
.copy_from_slice(&graffiti_string.as_bytes()[..bytes_to_copy]);
Graffiti::from(graffiti_bytes)
}
}