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()