Data snapshot Fri, Dec 12, 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,133,332
Unique clients: 9
Total unique peers: 21,446

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 24,076 1,668 14 v8.0.1-ced49dd
2 prysm 9,958 1,329 4 v7.0.0
3 teku 8,421 488 11 v25.11.1
4 erigon 6,932 350 1 caplin
5 nimbus 4,381 466 1
6 Others 2,129 73 6
7 grandine 985 49 4 2.0.1-9bfbf793
8 lodestar 308 134 4 v1.37.0