Data snapshot Sun, Dec 21, 2025

Client Versions

Analysis of consensus client versions connected to Xatu nodes on Ethereum mainnet.

Show code
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go

from loaders import load_parquet
Show code
# Load pre-aggregated data
df_hourly = load_parquet("client_hourly", target_date)
df_versions = load_parquet("client_version_dist", target_date)
df_summary = load_parquet("client_summary", target_date)

# Filter out unknown clients for visualizations
df_hourly_known = df_hourly[df_hourly["client"] != "unknown"]
df_versions_known = df_versions[df_versions["client"] != "unknown"]
df_summary_known = df_summary[df_summary["client"] != "unknown"]

# Compute totals from hourly data
total_connections = df_hourly["connections"].sum()
impl_counts = df_hourly_known.groupby("client")["connections"].sum().reset_index()
impl_counts = impl_counts.sort_values("connections", ascending=False)

print(f"Total connections: {total_connections:,}")
print(f"Unique clients: {df_summary['client'].nunique()}")
print(f"Total unique peers: {df_summary['unique_peers'].sum():,}")
Total connections: 1,304,741
Unique clients: 9
Total unique peers: 20,608

Client Implementation Distribution

Distribution of consensus client implementations observed across all connections. This shows the diversity of the Ethereum validator client ecosystem.

Show code
fig = px.pie(
    impl_counts,
    values="connections",
    names="client",
    title="Client Implementation Distribution",
    color_discrete_sequence=px.colors.qualitative.Set2,
)
fig.update_traces(textposition="inside", textinfo="percent+label")
fig.update_layout(height=500)
fig.show()

Connections by Client Implementation

Bar chart showing the number of connections per client implementation.

Show code
fig = px.bar(
    impl_counts,
    x="client",
    y="connections",
    title="Connections by Client Implementation",
    labels={"client": "Client", "connections": "Connections"},
    color="client",
    color_discrete_sequence=px.colors.qualitative.Set2,
)
fig.update_layout(
    showlegend=False,
    height=500,
    xaxis_tickangle=-45,
)
fig.show()

Client Connections Over Time

Stacked area chart showing how client connections are distributed across implementations over time.

Show code
# Pivot hourly data for stacked area chart
hourly_pivot = df_hourly_known.pivot(index="hour", columns="client", values="connections").fillna(0)

fig = go.Figure()
for col in hourly_pivot.columns:
    fig.add_trace(go.Scatter(
        x=hourly_pivot.index,
        y=hourly_pivot[col],
        mode="lines",
        stackgroup="one",
        name=col,
    ))

fig.update_layout(
    title="Client Implementation Connections Over Time",
    xaxis_title="Time",
    yaxis_title="Connections",
    height=500,
    legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()

Version Distribution by Client

Detailed breakdown of version distribution for each major client implementation (top 15 versions per client).

Show code
# Get clients sorted by total connections (exclude "Others" and "unknown")
top_clients = impl_counts[~impl_counts["client"].isin(["Others", "unknown"])]["client"].tolist()

for i, client in enumerate(top_clients):
    df_client = df_versions_known[df_versions_known["client"] == client]
    
    if len(df_client) == 0:
        continue
    
    fig = px.bar(
        df_client,
        x="version",
        y="connections",
        title=f"{client.capitalize()} Version Distribution",
        labels={"version": "Version", "connections": "Connections"},
        color_discrete_sequence=[px.colors.qualitative.Set2[i % len(px.colors.qualitative.Set2)]],
    )
    fig.update_layout(
        height=400,
        xaxis_tickangle=-45,
    )
    fig.show()

Summary Statistics

Show code
# Format summary table
summary_display = df_summary_known[["client", "connections", "unique_peers", "version_count", "top_version"]].copy()
summary_display.columns = ["Client", "Connections", "Unique Peers", "Versions", "Top Version"]
summary_display = summary_display.sort_values("Connections", ascending=False)
summary_display["Connections"] = summary_display["Connections"].apply(lambda x: f"{x:,}")
summary_display["Unique Peers"] = summary_display["Unique Peers"].apply(lambda x: f"{x:,}")
summary_display
Client Connections Unique Peers Versions Top Version
1 lighthouse 62,232 1,633 12 v8.0.1-ced49dd
2 prysm 17,923 1,515 6 v7.0.0
3 teku 10,812 485 12 v25.11.1
4 erigon 9,241 389 2 caplin
5 nimbus 6,280 592 1
6 grandine 1,530 43 6 2.0.1-9bfbf793
7 Others 1,134 45 6 0.47.0
8 lodestar 306 78 3 v1.38.0