Block/column timing - 2026-01-13
Analysis of block to column propagation timing on Ethereum mainnet.
Show code
display_sql("block_production_timeline", target_date)
View query
Show code
df = load_parquet("block_production_timeline", target_date)
# Flag MEV vs local blocks
df["has_mev"] = df["winning_bid_value"].notna()
df["block_type"] = df["has_mev"].map({True: "MEV", False: "Local"})
# Filter to slots with blobs
df["has_blobs"] = df["blob_count"] > 0
df_blobs = df[df["has_blobs"]].copy()
# Calculate block to first column delay
df_blobs = df_blobs.dropna(subset=["block_first_seen_ms", "first_column_first_seen_ms"])
df_blobs["block_to_column_ms"] = df_blobs["first_column_first_seen_ms"] - df_blobs["block_first_seen_ms"]
print(f"Total slots: {len(df):,}")
print(f"Slots with blobs: {len(df_blobs):,} ({len(df_blobs)/len(df)*100:.1f}%)")
print(f" MEV: {df_blobs['has_mev'].sum():,} ({df_blobs['has_mev'].mean()*100:.1f}%)")
print(f" Local: {(~df_blobs['has_mev']).sum():,} ({(~df_blobs['has_mev']).mean()*100:.1f}%)")
Block-to-column delayΒΆ
Time from block first seen to first column first seen. Shows how quickly columns start propagating after the block arrives.
Note on negative values: A negative delay means a column was observed before the block. This can happen due to how data propagates through the network: columns may reach certain parts of the network before the block does.
Show code
if len(df_blobs) > 0:
fig = px.histogram(
df_blobs,
x="block_to_column_ms",
color="block_type",
category_orders={"block_type": ["MEV", "Local"]},
nbins=60,
barmode="overlay",
opacity=0.7,
color_discrete_map={"MEV": "#AB63FA", "Local": "#19D3F3"},
)
fig.update_layout(
margin=dict(l=60, r=30, t=30, b=60),
xaxis=dict(title="Block to first column (ms)"),
yaxis=dict(title="Slots"),
legend_title="Block type",
height=400,
)
fig.show(config={"responsive": True})
else:
print("No block-to-column timing data available.")
Show code
# Summary statistics
if len(df_blobs) > 0:
stats = df_blobs["block_to_column_ms"].describe(percentiles=[0.5, 0.9, 0.95, 0.99])
print("Block to first column (ms):")
print(f" Median: {stats['50%']:.0f}")
print(f" P90: {stats['90%']:.0f}")
print(f" P95: {stats['95%']:.0f}")
print(f" P99: {stats['99%']:.0f}")
print(f" Max: {stats['max']:.0f}")
Block-to-column delay by blob countΒΆ
The distribution of the delay by blob count.
Box: 25th-75th percentile. Line: median. Whiskers: min/max excluding outliers.
Show code
df_delay = df[df["blob_count"] > 0].dropna(subset=["block_first_seen_ms", "first_column_first_seen_ms"])
df_delay["block_to_column_ms"] = df_delay["first_column_first_seen_ms"] - df_delay["block_first_seen_ms"]
if len(df_delay) > 0:
fig = px.box(
df_delay,
x="blob_count",
y="block_to_column_ms",
color="block_type",
category_orders={"block_type": ["MEV", "Local"]},
)
fig.update_layout(
margin=dict(l=60, r=30, t=30, b=60),
xaxis=dict(title="Blob count", dtick=1),
yaxis=dict(title="Block to first column (ms)"),
legend_title="Block type",
height=450,
)
fig.show(config={"responsive": True})
Block-to-column delay over timeΒΆ
How the block-to-column delay varied throughout the day.
Show code
if len(df_blobs) > 0:
df_plot = df_blobs.copy()
df_plot["blob_count_f"] = df_plot["blob_count"].astype(float) # Force continuous color
max_blobs = df_plot["blob_count"].max()
fig = px.scatter(
df_plot,
x="slot_start_date_time",
y="block_to_column_ms",
color="blob_count_f",
color_continuous_scale="Plasma",
range_color=[0, max_blobs],
opacity=0.5,
hover_data={"slot": True, "blob_count": True, "block_to_column_ms": ":.0f", "slot_start_date_time": False, "blob_count_f": False},
)
fig.update_layout(
margin=dict(l=60, r=30, t=30, b=60),
xaxis=dict(title="Time (UTC)", tickformat="%H:%M"),
yaxis=dict(title="Block to first column (ms)"),
coloraxis_colorbar=dict(title="Blobs"),
height=400,
)
fig.show(config={"responsive": True})
Column arrival spread by blob count (MEV vs local)ΒΆ
The difference between the first observations of the first seen column and the last seen column, categorized by MEV vs. local block production.
Box: 25th-75th percentile. Line: median. Whiskers: min/max excluding outliers.
Show code
# Filter to slots with blobs (column_spread only exists for blob slots)
df_col_spread = df[df["blob_count"] > 0].dropna(subset=["column_spread_ms"])
if len(df_col_spread) > 0:
fig = px.box(
df_col_spread,
x="blob_count",
y="column_spread_ms",
color="block_type",
category_orders={"block_type": ["MEV", "Local"]},
)
fig.update_layout(
margin=dict(l=60, r=30, t=30, b=60),
xaxis=dict(title="Blob count", dtick=1),
yaxis=dict(title="Column arrival spread (ms)"),
legend_title="Block type",
height=450,
)
fig.show(config={"responsive": True})