Data snapshot Mon, Dec 22, 2025 Latest

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,277,301
Unique clients: 9
Total unique peers: 21,065

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 60,432 1,619 14 v8.0.1-ced49dd
2 prysm 18,637 1,469 7 v7.0.0
3 erigon 11,381 413 2 caplin
4 teku 10,207 466 12 v25.11.1
5 nimbus 6,288 676 1
6 grandine 1,304 37 6 2.0.1-9bfbf793
7 Others 1,286 48 6 0.47.0
8 lodestar 371 104 3 v1.38.0