Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c60ab3f
append string
chong-he May 27, 2025
067b68d
Add GraffitiPolicy
chong-he Jun 3, 2025
c879763
add server side
chong-he Jun 4, 2025
054a56d
add Option
chong-he Jun 4, 2025
9f59208
implement flag and config
chong-he Jun 4, 2025
7d18f7f
final graffiti
chong-he Jun 4, 2025
060b7bf
cli
chong-he Jun 4, 2025
a14e7d3
graffiti settings
chong-he Jun 10, 2025
7cf8b11
cli
chong-he Jun 11, 2025
4a336e9
update cli
chong-he Jun 11, 2025
6ad919f
pass graffiti policy to service builder
chong-he Jun 13, 2025
c0c6efa
add log
chong-he Jun 13, 2025
9035834
fix logging
chong-he Jun 13, 2025
efe855d
add Some() to graffiti policy
chong-he Jun 16, 2025
761f9bb
add url info log
chong-he Jun 16, 2025
fd89131
revise GraffitiPolicy display
chong-he Jun 16, 2025
27b9ae5
only client version info
chong-he Jun 16, 2025
9f9d9dd
http url request
chong-he Jun 17, 2025
af8f01e
append graffiti
chong-he Jun 18, 2025
32cb2ea
append graffiti
chong-he Jun 18, 2025
11d7f9e
remove public method calculate_graffiti
chong-he Jun 24, 2025
985cbcb
add todo
chong-he Jun 24, 2025
8aa31b4
simplify calcualte_graffiti function
chong-he Jun 24, 2025
e5bb42e
make test compile
chong-he Jun 24, 2025
7cdfbf4
cli check
chong-he Jun 24, 2025
5f91558
append graffiti according to length
chong-he Jun 24, 2025
e1b1ffc
remove logging
chong-he Jun 24, 2025
96a666a
Merge branch 'unstable' into graffiti
chong-he Jun 25, 2025
9da5479
add basic test
chong-he Jun 25, 2025
72f3d29
Merge branch 'graffiti' of https://github.com/chong-he/lighthouse int…
chong-he Jun 25, 2025
f108d60
Add graffiti tests
chong-he Jun 26, 2025
aabeb97
lint
chong-he Jun 26, 2025
74b288d
Add test http query path
chong-he Jun 26, 2025
74705d4
Add none in test
chong-he Jun 26, 2025
6676e58
add assert_eq
chong-he Jul 2, 2025
4d16480
Merge remote-tracking branch 'origin/unstable' into graffiti
chong-he Aug 13, 2025
d53beab
merge unstable
chong-he Aug 13, 2025
e535607
Merge branch 'unstable' into graffiti
jimmygchen Aug 15, 2025
f8fc8a4
Merge branch 'unstable' into graffiti
chong-he Dec 11, 2025
1adc81a
fmt
chong-he Dec 11, 2025
d0c1b35
fix lint
chong-he Dec 11, 2025
5d94d0e
revise comments
chong-he Dec 15, 2025
c3c163c
simplify test
chong-he Dec 15, 2025
00e0d87
Merge branch 'unstable' into graffiti
chong-he Dec 15, 2025
6e1c563
revise
chong-he Dec 15, 2025
e40b49d
Update beacon_node/beacon_chain/src/graffiti_calculator.rs
chong-he Dec 15, 2025
63746d8
revise
chong-he Dec 16, 2025
45a1328
minor
chong-he Dec 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::events::ServerSentEventHandler;
use crate::execution_payload::{NotifyExecutionLayer, PreparePayloadHandle, get_execution_payload};
use crate::fetch_blobs::EngineGetBlobsOutput;
use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult};
use crate::graffiti_calculator::GraffitiCalculator;
use crate::graffiti_calculator::{GraffitiCalculator, GraffitiSettings};
use crate::kzg_utils::reconstruct_blobs;
use crate::light_client_finality_update_verification::{
Error as LightClientFinalityUpdateError, VerifiedLightClientFinalityUpdate,
Expand Down Expand Up @@ -4493,7 +4493,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self: &Arc<Self>,
randao_reveal: Signature,
slot: Slot,
validator_graffiti: Option<Graffiti>,
graffiti_settings: GraffitiSettings,
verification: ProduceBlockVerification,
builder_boost_factor: Option<u64>,
block_production_version: BlockProductionVersion,
Expand Down Expand Up @@ -4527,7 +4527,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
state_root_opt,
slot,
randao_reveal,
validator_graffiti,
graffiti_settings,
verification,
builder_boost_factor,
block_production_version,
Expand Down Expand Up @@ -5060,7 +5060,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
state_root_opt: Option<Hash256>,
produce_at_slot: Slot,
randao_reveal: Signature,
validator_graffiti: Option<Graffiti>,
graffiti_settings: GraffitiSettings,
verification: ProduceBlockVerification,
builder_boost_factor: Option<u64>,
block_production_version: BlockProductionVersion,
Expand All @@ -5071,7 +5071,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let chain = self.clone();
let graffiti = self
.graffiti_calculator
.get_graffiti(validator_graffiti)
.get_graffiti(graffiti_settings)
.await;
let span = Span::current();
let mut partial_beacon_block = self
Expand Down
152 changes: 144 additions & 8 deletions beacon_node/beacon_chain/src/graffiti_calculator.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::BeaconChain;
use crate::BeaconChainTypes;
use eth2::types::GraffitiPolicy;
use execution_layer::{CommitPrefix, ExecutionLayer, http::ENGINE_GET_CLIENT_VERSION_V1};
use logging::crit;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -48,6 +49,25 @@ impl Debug for GraffitiOrigin {
}
}

pub enum GraffitiSettings {
Unspecified,
Specified {
graffiti: Graffiti,
policy: GraffitiPolicy,
},
}

impl GraffitiSettings {
pub fn new(validator_graffiti: Option<Graffiti>, policy: Option<GraffitiPolicy>) -> Self {
validator_graffiti
.map(|graffiti| Self::Specified {
graffiti,
policy: policy.unwrap_or(GraffitiPolicy::PreserveUserGraffiti),
})
.unwrap_or(Self::Unspecified)
}
}

pub struct GraffitiCalculator<T: BeaconChainTypes> {
pub beacon_graffiti: GraffitiOrigin,
execution_layer: Option<ExecutionLayer<T::EthSpec>>,
Expand All @@ -73,11 +93,19 @@ impl<T: BeaconChainTypes> GraffitiCalculator<T> {
/// 2. Graffiti specified by the user via beacon node CLI options.
/// 3. The EL & CL client version string, applicable when the EL supports version specification.
/// 4. The default lighthouse version string, used if the EL lacks version specification support.
pub async fn get_graffiti(&self, validator_graffiti: Option<Graffiti>) -> Graffiti {
if let Some(graffiti) = validator_graffiti {
return graffiti;
pub async fn get_graffiti(&self, graffiti_settings: GraffitiSettings) -> Graffiti {
match graffiti_settings {
GraffitiSettings::Specified { graffiti, policy } => match policy {
GraffitiPolicy::PreserveUserGraffiti => graffiti,
GraffitiPolicy::AppendClientVersions => {
self.calculate_combined_graffiti(Some(graffiti)).await
}
},
GraffitiSettings::Unspecified => self.calculate_combined_graffiti(None).await,
}
}

async fn calculate_combined_graffiti(&self, validator_graffiti: Option<Graffiti>) -> Graffiti {
match self.beacon_graffiti {
GraffitiOrigin::UserSpecified(graffiti) => graffiti,
GraffitiOrigin::Calculated(default_graffiti) => {
Expand Down Expand Up @@ -133,7 +161,7 @@ impl<T: BeaconChainTypes> GraffitiCalculator<T> {
CommitPrefix("00000000".to_string())
});

engine_version.calculate_graffiti(lighthouse_commit_prefix)
engine_version.calculate_graffiti(lighthouse_commit_prefix, validator_graffiti)
}
}
}
Expand Down Expand Up @@ -224,8 +252,10 @@ async fn engine_version_cache_refresh_service<T: BeaconChainTypes>(
#[cfg(test)]
mod tests {
use crate::ChainConfig;
use crate::graffiti_calculator::GraffitiSettings;
use crate::test_utils::{BeaconChainHarness, EphemeralHarnessType, test_spec};
use bls::Keypair;
use eth2::types::GraffitiPolicy;
use execution_layer::EngineCapabilities;
use execution_layer::test_utils::{DEFAULT_CLIENT_VERSION, DEFAULT_ENGINE_CAPABILITIES};
use std::sync::Arc;
Expand Down Expand Up @@ -281,8 +311,12 @@ mod tests {

let version_bytes = std::cmp::min(lighthouse_version::VERSION.len(), GRAFFITI_BYTES_LEN);
// grab the slice of the graffiti that corresponds to the lighthouse version
let graffiti_slice =
&harness.chain.graffiti_calculator.get_graffiti(None).await.0[..version_bytes];
let graffiti_slice = &harness
.chain
.graffiti_calculator
.get_graffiti(GraffitiSettings::Unspecified)
.await
.0[..version_bytes];

// convert graffiti bytes slice to ascii for easy debugging if this test should fail
let graffiti_str =
Expand All @@ -303,7 +337,12 @@ mod tests {
let spec = Arc::new(test_spec::<MinimalEthSpec>());
let harness = get_harness(VALIDATOR_COUNT, spec, None);

let found_graffiti_bytes = harness.chain.graffiti_calculator.get_graffiti(None).await.0;
let found_graffiti_bytes = harness
.chain
.graffiti_calculator
.get_graffiti(GraffitiSettings::Unspecified)
.await
.0;

let mock_commit = DEFAULT_CLIENT_VERSION.commit.clone();
let expected_graffiti_string = format!(
Expand Down Expand Up @@ -352,12 +391,109 @@ mod tests {
let found_graffiti = harness
.chain
.graffiti_calculator
.get_graffiti(Some(Graffiti::from(graffiti_bytes)))
.get_graffiti(GraffitiSettings::new(
Some(Graffiti::from(graffiti_bytes)),
Some(GraffitiPolicy::PreserveUserGraffiti),
))
.await;

assert_eq!(
found_graffiti.to_string(),
"0x6e6963652067726166666974692062726f000000000000000000000000000000"
);
}

#[tokio::test]
async fn check_append_el_version_graffiti_various_length() {
let spec = Arc::new(test_spec::<MinimalEthSpec>());
let harness = get_harness(VALIDATOR_COUNT, spec, None);

let graffiti_vec = vec![
// less than 20 characters, example below is 19 characters
"This is my graffiti",
// 20-23 characters, example below is 22 characters
"This is my graffiti yo",
// 24-27 characters, example below is 26 characters
"This is my graffiti string",
// 28-29 characters, example below is 29 characters
"This is my graffiti string yo",
// 30-32 characters, example below is 32 characters
"This is my graffiti string yo yo",
];

for graffiti in graffiti_vec {
let mut graffiti_bytes = [0; GRAFFITI_BYTES_LEN];
graffiti_bytes[..graffiti.len()].copy_from_slice(graffiti.as_bytes());

// To test appending client version info with user specified graffiti
let policy = GraffitiPolicy::AppendClientVersions;
let found_graffiti_bytes = harness
.chain
.graffiti_calculator
.get_graffiti(GraffitiSettings::Specified {
graffiti: Graffiti::from(graffiti_bytes),
policy,
})
.await
.0;

let mock_commit = DEFAULT_CLIENT_VERSION.commit.clone();

let graffiti_length = graffiti.len();
let append_graffiti_string = match graffiti_length {
0..=19 => format!(
"{}{}{}{}",
DEFAULT_CLIENT_VERSION.code,
mock_commit
.strip_prefix("0x")
.unwrap_or("&mock_commit")
.get(0..4)
.expect("should get first 2 bytes in hex"),
"LH",
lighthouse_version::COMMIT_PREFIX
.get(0..4)
.expect("should get first 2 bytes in hex")
),
20..=23 => format!(
"{}{}{}{}",
DEFAULT_CLIENT_VERSION.code,
mock_commit
.strip_prefix("0x")
.unwrap_or("&mock_commit")
.get(0..2)
.expect("should get first 2 bytes in hex"),
"LH",
lighthouse_version::COMMIT_PREFIX
.get(0..2)
.expect("should get first 2 bytes in hex")
),
24..=27 => format!("{}{}", DEFAULT_CLIENT_VERSION.code, "LH",),
28..=29 => DEFAULT_CLIENT_VERSION.code.to_string(),
// when user graffiti length is 30-32 characters, append nothing
30..=32 => String::new(),
_ => panic!(
"graffiti length should be less than or equal to GRAFFITI_BYTES_LEN (32 characters)"
),
};

let expected_graffiti_string = if append_graffiti_string.is_empty() {
// for the case of empty append_graffiti_string, i.e., user-specified graffiti is 30-32 characters
graffiti.to_string()
} else {
// There is a space between the client version info and user graffiti
// as defined in calculate_graffiti function in engine_api.rs
format!("{} {}", append_graffiti_string, graffiti)
};

let expected_graffiti_prefix_bytes = expected_graffiti_string.as_bytes();
let expected_graffiti_prefix_len =
std::cmp::min(expected_graffiti_prefix_bytes.len(), GRAFFITI_BYTES_LEN);

let found_graffiti_string =
std::str::from_utf8(&found_graffiti_bytes[..expected_graffiti_prefix_len])
.expect("bytes should convert nicely to ascii");

assert_eq!(expected_graffiti_string, found_graffiti_string);
}
}
}
15 changes: 11 additions & 4 deletions beacon_node/beacon_chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::blob_verification::GossipVerifiedBlob;
use crate::block_verification_types::{AsBlock, RpcBlock};
use crate::custody_context::NodeCustodyType;
use crate::data_column_verification::CustodyDataColumn;
use crate::graffiti_calculator::GraffitiSettings;
use crate::kzg_utils::build_data_column_sidecars;
use crate::observed_operations::ObservationOutcome;
pub use crate::persisted_beacon_chain::PersistedBeaconChain;
Expand All @@ -23,7 +24,7 @@ use bls::get_withdrawal_credentials;
use bls::{
AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, Signature, SignatureBytes,
};
use eth2::types::SignedBlockContentsTuple;
use eth2::types::{GraffitiPolicy, SignedBlockContentsTuple};
use execution_layer::test_utils::generate_genesis_header;
use execution_layer::{
ExecutionLayer,
Expand Down Expand Up @@ -943,6 +944,8 @@ where
// BeaconChain errors out with `DuplicateFullyImported`. Vary the graffiti so that we produce
// different blocks each time.
let graffiti = Graffiti::from(self.rng.lock().random::<[u8; 32]>());
let graffiti_settings =
GraffitiSettings::new(Some(graffiti), Some(GraffitiPolicy::PreserveUserGraffiti));

let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot);

Expand All @@ -956,7 +959,7 @@ where
None,
slot,
randao_reveal,
Some(graffiti),
graffiti_settings,
ProduceBlockVerification::VerifyRandao,
builder_boost_factor,
BlockProductionVersion::V3,
Expand Down Expand Up @@ -1000,6 +1003,8 @@ where
// BeaconChain errors out with `DuplicateFullyImported`. Vary the graffiti so that we produce
// different blocks each time.
let graffiti = Graffiti::from(self.rng.lock().random::<[u8; 32]>());
let graffiti_settings =
GraffitiSettings::new(Some(graffiti), Some(GraffitiPolicy::PreserveUserGraffiti));

let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot);

Expand All @@ -1010,7 +1015,7 @@ where
None,
slot,
randao_reveal,
Some(graffiti),
graffiti_settings,
ProduceBlockVerification::VerifyRandao,
None,
BlockProductionVersion::FullV2,
Expand Down Expand Up @@ -1059,6 +1064,8 @@ where
// BeaconChain errors out with `DuplicateFullyImported`. Vary the graffiti so that we produce
// different blocks each time.
let graffiti = Graffiti::from(self.rng.lock().random::<[u8; 32]>());
let graffiti_settings =
GraffitiSettings::new(Some(graffiti), Some(GraffitiPolicy::PreserveUserGraffiti));

let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot);

Expand All @@ -1071,7 +1078,7 @@ where
None,
slot,
randao_reveal,
Some(graffiti),
graffiti_settings,
ProduceBlockVerification::VerifyRandao,
None,
BlockProductionVersion::FullV2,
Expand Down
Loading