Blob Inclusion

Analysis of blob inclusion patterns in Ethereum mainnet blocks.

Show code
target_date = None  # Set via papermill, or auto-detect from manifest
Show code
# Injected Parameters
target_date = "2025-12-07"
Show code
import altair as alt
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go

from loaders import load_parquet

alt.theme.enable("carbonwhite")
ThemeRegistry.enable('carbonwhite')

Blobs included per slot

Each dot represents a slot, colored by the number of blobs included (0–9). This shows the temporal distribution of blob activity—gaps indicate missed slots or blocks without blobs.

Show code
df_blobs_per_slot = load_parquet("blobs_per_slot", target_date)

fig = px.scatter(
    df_blobs_per_slot,
    x="time",
    y="blob_count",
    color="blob_count",
    color_continuous_scale="YlOrRd",
    title="Blobs per slot",
    labels={"time": "Time", "blob_count": "Blob Count"},
    template="plotly",
)
fig.update_layout(
    showlegend=False,
    yaxis=dict(dtick=1, range=[-0.5, 15.5]),
    coloraxis_colorbar=dict(title="Blobs"),
    height=600,
)
fig.update_traces(marker=dict(size=3))
fig.show()

Blob count breakdown per epoch

Stacked bar chart showing how blocks within each epoch are distributed by blob count. Each bar represents one epoch (32 slots), with colors indicating the number of blobs in each block.

Show code
df_blocks_blob_epoch = load_parquet("blocks_blob_epoch", target_date)

chart = (
    alt.Chart(df_blocks_blob_epoch)
    .mark_bar()
    .encode(
        x=alt.X("time:T", title="Epoch Start Time"),
        y=alt.Y("block_count:Q", title="Block Count", stack="zero", scale=alt.Scale(domain=[0, 32]), axis=alt.Axis(tickCount=32, labelAngle=-45)),
        color=alt.Color(
            "series:N",
            title="Blob Count",
            sort="ascending",
            scale=alt.Scale(scheme="inferno"),
        ),
        order=alt.Order("series:N", sort="ascending"),
        tooltip=["time:T", "series:N", "block_count:Q"],
    )
    .properties(title="Blocks with blob counts per epoch", width=1000, height=600)
)
chart

Blob count popularity per epoch

Heatmap showing which blob counts are most common over time. Brighter cells indicate more blocks with that blob count during the epoch. Useful for spotting trends in blob usage patterns.

Show code
df_blob_popularity = load_parquet("blob_popularity", target_date)

# Pivot for heatmap
df_pivot = df_blob_popularity.pivot(index="blob_count", columns="time", values="count").fillna(0)

fig = go.Figure(
    data=go.Heatmap(
        z=df_pivot.values,
        x=df_pivot.columns,
        y=[str(int(b)) for b in df_pivot.index],
        colorscale="inferno",
        reversescale=False,
        colorbar=dict(title="Block Count"),
    ),
)
fig.update_layout(
    template="plotly_white",
    title="Blob count popularity by epoch",
    xaxis_title="Epoch Start Time",
    yaxis_title="Blob Count",
    height=500,
)
fig.show()

Blob count per slot (vertical layout)

Detailed view of blob counts for each slot position (0–31) within epochs. Each row is an epoch, each column is a slot position. Reveals patterns like whether certain slot positions consistently have more or fewer blobs.

Show code
df_slot_in_epoch = load_parquet("slot_in_epoch", target_date)

df_pivot = df_slot_in_epoch.pivot(index="slot_in_epoch", columns="time", values="blob_count").fillna(0)

fig = go.Figure(
    data=go.Heatmap(
        z=df_pivot.values.T,
        x=[str(int(s)) for s in df_pivot.index],
        y=df_pivot.columns,
        colorscale="thermal",
        reversescale=True,
        colorbar=dict(
            orientation="h",
            y=-0.075,
            yanchor="top",
            x=0,
            xanchor="left",
            len=1,
        ),
    )
)
fig.update_layout(
    title="Blob count per slot within epoch",
    xaxis_title="Slot in Epoch",
    yaxis_title="Epoch Start Time",
    yaxis=dict(autorange="reversed"),
    height=1500,
    width=500,
)
fig.show()

Blob count per slot (horizontal layout)

Same data as above but arranged horizontally for easier comparison across longer time ranges. Epochs are stacked in columns, making it easier to see temporal evolution.

Show code
df_pivot = df_slot_in_epoch.pivot(index="slot_in_epoch", columns="time", values="blob_count").fillna(0)

# Parameters
n_columns = 4
n_rows = len(df_pivot.columns)
rows_per_chunk = n_rows // n_columns

# Reshape: stack chunks horizontally
chunks = []
for i in range(n_columns):
    chunk = df_pivot.T.iloc[i*rows_per_chunk:(i+1)*rows_per_chunk, :]
    chunk = chunk.reset_index(drop=True)
    chunks.append(chunk)

# Concatenate horizontally (side by side)
df_combined = pd.concat(chunks, axis=1, ignore_index=True)

# Create x-axis labels with dividers
n_slots = len(df_pivot.index)
x_labels = list(range(n_slots)) * n_columns

y_labels = []
for row_idx in range(rows_per_chunk):
    time_val = df_pivot.columns[row_idx]
    y_labels.append(str(time_val))

fig = go.Figure(
    data=go.Heatmap(
        z=df_combined.values,
        x=list(range(len(df_combined.columns))),
        y=y_labels,
        colorscale="thermal",
        reversescale=True,
        colorbar=dict(
            orientation="h",
            y=-0.15,
            yanchor="top",
            x=0.5,
            xanchor="center",
            len=0.8,
        ),
    )
)

# Add vertical dividers between chunks
for i in range(1, n_columns):
    fig.add_vline(
        x=i * n_slots - 0.5,
        line_width=2,
        line_color="white",
    )

fig.update_layout(
    title="Blob count per slot within epoch",
    xaxis_title="Slot in Epoch",
    yaxis_title="Epoch",
    yaxis=dict(autorange="reversed"),
    xaxis=dict(
        tickvals=list(range(len(df_combined.columns))),
        ticktext=[str(i % n_slots) for i in range(len(df_combined.columns))],
        tickangle=90,
        tickfont=dict(size=6),
    ),
    height=600,
    width=1200,
)

fig.show()