Qlean P2P Propagation Analysis (64 Nodes)¶

Analysis of attestation and block propagation across a simulated 64-node network using the Qlean consensus client.

Key Metrics:

  • Attestation Propagation: Cumulative Distribution Function (CDF) showing how quickly an attestation reaches all nodes.
  • Block Propagation: CDF for block reception across the network.
  • Block Size vs. Slot: Evolution of block sizes over the course of the simulation.
  • P90 Latency: The time required for 90% of the nodes to receive a message.
In [1]:
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
import json

# Set default renderer for static HTML inclusion
pio.renderers.default = "notebook_connected"

# Load pre-extracted data
df_blocks = pd.read_json('block_data.json').sort_values('slot')
df_atts = pd.read_json('att_data.json').sort_values('slot')
with open('network_stats.json', 'r') as f:
    net_stats = json.load(f)

print(f"Loaded {len(df_blocks)} blocks and {len(df_atts)} attestation samples.")
Loaded 14 blocks and 13 attestation samples.

Network Topology Analysis (GML)¶

Visualizing the distribution of latency and bandwidth from the atlas_v201801.shadow_v2.gml file used in the simulation.

In [2]:
fig = px.histogram(
    x=net_stats['latency_ms'],
    nbins=100,
    title="Network Latency Distribution (Edges)",
    labels={'x': 'Latency (ms)', 'y': 'Count'}
)
fig.update_layout(template="plotly_white")
fig.show()
In [3]:
df_bw = pd.DataFrame({
    'Downlink': net_stats['bw_down_kibit'],
    'Uplink': net_stats['bw_up_kibit']
})

fig = go.Figure()
fig.add_trace(go.Box(y=df_bw['Downlink'] / 1024, name='Downlink (Mbit)'))
fig.add_trace(go.Box(y=df_bw['Uplink'] / 1024, name='Uplink (Mbit)'))

fig.update_layout(
    title="Node Bandwidth Distribution",
    yaxis_title="Bandwidth (Mbit)",
    template="plotly_white"
)
fig.show()

Block Size Distribution¶

Uncompressed block sizes per slot as recorded by the nodes.

In [4]:
fig = px.bar(
    df_blocks,
    x="slot",
    y="size_kb",
    labels={'size_kb': 'Size (KB)', 'slot': 'Slot Number'},
    title="Uncompressed Block Size per Slot"
)
fig.update_layout(template="plotly_white")
fig.show()

Attestation Propagation CDF¶

Showing the propagation profile for a representative attestation sample.

In [5]:
if not df_atts.empty:
    sample = df_atts.iloc[len(df_atts)//2]
    delays = sorted(sample['delays'])
    y = np.arange(1, len(delays) + 1) / len(delays) * 100
    
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=delays, y=y, mode='lines+markers', name=f"Slot {sample['slot']}"))
    fig.update_layout(
        title=f"Attestation Propagation CDF (Slot {sample['slot']})",
        xaxis_title="Time Since Publication (ms)",
        yaxis_title="% of Nodes Received",
        template="plotly_white"
    )
    fig.show()

Block Propagation CDF¶

Showing the propagation profile for a representative block.

In [6]:
if not df_blocks.empty:
    sample = df_blocks.iloc[len(df_blocks)//2]
    delays = sorted(sample['delays'])
    y = np.arange(1, len(delays) + 1) / len(delays) * 100
    
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=delays, y=y, mode='lines+markers', name=f"Slot {sample['slot']}"))
    fig.update_layout(
        title=f"Block Propagation CDF (Slot {sample['slot']})",
        xaxis_title="Time Since Publication (ms)",
        yaxis_title="% of Nodes Received",
        template="plotly_white"
    )
    fig.show()

Block P90 Latency¶

The 90th percentile reception time for blocks across all slots.

In [7]:
fig = px.bar(
    df_blocks, 
    x='slot', 
    y='p90_ms', 
    title="P90 Block Propagation Latency per Slot",
    labels={'p90_ms': 'P90 Latency (ms)', 'slot': 'Slot'}
)
fig.update_layout(template="plotly_white")
fig.show()

Attestation P90 Latency (Node 0)¶

The 90th percentile reception time for attestations produced by Node 0.

In [8]:
fig = px.bar(
    df_atts, 
    x='slot', 
    y='p90_ms', 
    title="P90 Attestation Propagation Latency (Node 0) per Slot",
    labels={'p90_ms': 'P90 Latency (ms)', 'slot': 'Slot'}
)
fig.update_layout(template="plotly_white")
fig.show()