Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
c76dec5
feat: xatu-backed block propagation tab on the slot page (POC)
samcm Aug 20, 2026
f72249b
fix: arrival tab autoload when preselected via url hash
samcm Aug 20, 2026
b70748a
feat: richer propagation tab
samcm Aug 20, 2026
08c7dfc
polish propagation tab
samcm Aug 20, 2026
c8eb4cd
feat: timeline/table toggle for the propagation tab
samcm Aug 20, 2026
2d82017
default the propagation tab to the table view
samcm Aug 20, 2026
5f308ed
single table view with source-layer column groups
samcm Aug 20, 2026
6ceec12
match dora's native style above the propagation table
samcm Aug 20, 2026
a630e2c
proportional distribution bars for sources and regions
samcm Aug 20, 2026
7926add
per-region breakdown lines and corrected newPayload timing
samcm Aug 20, 2026
8812af0
encode source layer visually
samcm Aug 20, 2026
717c930
quieter layer separation
samcm Aug 20, 2026
6d79788
drop layer prefixes from the marker legend
samcm Aug 20, 2026
45d660c
inline marker glyphs in column headers, drop the legend row
samcm Aug 20, 2026
f73ee02
separate column groups with whitespace and label the timeline
samcm Aug 20, 2026
dd782cd
polish: timeline header, sentry client dash, richer regions
samcm Aug 20, 2026
22f9346
client type for gossip listeners, drop delta head, align header glyphs
samcm Aug 20, 2026
f2d413e
sortable propagation table columns
samcm Aug 20, 2026
03c2e4c
per-slot arrival min/p90 columns on the epoch page
samcm Aug 20, 2026
6cfc7c2
single arrival column on the epoch page and mobile behavior
samcm Aug 20, 2026
2a61325
fix epoch row alignment for not-indexed rows with the arrival column
samcm Aug 20, 2026
795b6e1
nicer timeline column
samcm Aug 20, 2026
4203509
readable timeline: pipeline bars on a tight axis
samcm Aug 20, 2026
63ab833
de-scuff the timeline bars
samcm Aug 20, 2026
bd59825
timeline parseability: p50 axis, lead-ins, wider lane, overflow chevrons
samcm Aug 20, 2026
4e47dc4
render epoch arrival columns server-side
samcm Aug 21, 2026
683a5a0
readable timeline: zero-anchored lane with shared gridlines
samcm Aug 21, 2026
9510f4c
xatu: survive an unreachable clickhouse, fix env binding, ship a defa…
samcm Aug 24, 2026
a885f04
xatu: stop losing rows silently, drop dead code, cover the name parsers
samcm Aug 24, 2026
ded6484
xatu: page the slot readers too, and close out the review leftovers
samcm Aug 24, 2026
1be4251
xatu: scope propagation to the block being viewed
samcm Aug 24, 2026
81046ce
xatu: keep env var tags namespaced
samcm Aug 24, 2026
55dc4b3
depend on the released xatu clickhouse read package
samcm Aug 24, 2026
2c69d48
fix typo flagged by check-typos
samcm Aug 24, 2026
0ba7966
xatu: bound pre-settle results to one slot instead of never caching
samcm Aug 24, 2026
6831b19
xatu: size the count fields so the page cache can use SSZ
samcm Aug 24, 2026
36be258
xatu: stop the page cache turning absent arrivals into 0ms
samcm Aug 24, 2026
f5e8b79
xatu: exclude late observations in clickhouse, not after the fact
samcm Aug 24, 2026
97b6121
Merge remote-tracking branch 'origin/master' into feat/xatu-block-arr…
pk910 Aug 25, 2026
1bec750
readable timeline: zoom to the data, drop the missing-gossip stripe
samcm Aug 25, 2026
53e97f6
pin the arrival header and stop the axis labels colliding
samcm Aug 25, 2026
444b829
close the seams in the pinned arrival header
samcm Aug 25, 2026
d9515b7
measure the header bar rather than assuming its height
samcm Aug 25, 2026
1760d6b
scale the timeline to what it draws, and lift the header off the shadow
samcm Aug 25, 2026
7141fc1
stick the arrival header as one unit
samcm Aug 25, 2026
d35e1b0
add xatu-cbt slot panels: attestation wave and data columns
samcm Aug 25, 2026
8a1d7bb
fold the wave panels into the summary rows
samcm Aug 25, 2026
e46838e
draw the wave as one area silhouette and the columns as a pill
samcm Aug 25, 2026
539663d
draw the columns as a seen-over-time curve
samcm Aug 25, 2026
b804321
commit to a real histogram block for the wave rows
samcm Aug 25, 2026
0229251
span the whole slot at full width
samcm Aug 25, 2026
e401b27
data columns as a per-column heatmap on the arrival-time scale
samcm Aug 25, 2026
ed95591
collapse the column heatmap into a slim strip
samcm Aug 25, 2026
78993b4
port the lab's data column propagation chart
samcm Aug 25, 2026
aa48fe5
move the propagation tab right and mark the proposer's node
samcm Aug 26, 2026
28e2b3a
merge feat/xatu-block-arrival: tab placement and proposer badge
samcm Aug 26, 2026
5bdccad
show the gloas payload arrival per node
samcm Aug 26, 2026
60dc12b
merge feat/xatu-block-arrival: gloas payload arrival series
samcm Aug 26, 2026
6f42add
mark the 60% vote threshold and payload arrival on the wave
samcm Aug 26, 2026
bb0c878
split the ptc votes into their own wave
samcm Aug 26, 2026
3149d70
fork-aware deadlines: 3s attestations and 9s ptc on gloas
samcm Aug 26, 2026
34ffa3a
fill the engine column from the execution-side captures
samcm Aug 26, 2026
c25bc37
merge feat/xatu-block-arrival: execution-side engine timings
samcm Aug 26, 2026
fed96b4
derive every marker from the chain spec
samcm Aug 26, 2026
908b9cd
amalgamate the slot phases into one timeline of cdf curves
samcm Aug 26, 2026
cd04099
grow the timeline and add the execution series
samcm Aug 26, 2026
5e28743
size the clickhouse connection pool explicitly
samcm Aug 26, 2026
cd53ac7
merge feat/xatu-block-arrival: connection pool sizing
samcm Aug 26, 2026
15c62be
polish the timeline chart
samcm Aug 26, 2026
ba5af2a
color the quorum marker like the series it belongs to
samcm Aug 26, 2026
85365e1
Add timeline series isolation and a regions header row
samcm Aug 26, 2026
93a91f3
Name the metric in the regions header
samcm Aug 26, 2026
e32e079
Split regions into head and payload arrival, fix axis label overlap
samcm Aug 26, 2026
073826f
Render the arrival and wave panels in one pass
samcm Aug 26, 2026
f36f295
Add block/payload sort toggles to the timeline column
samcm Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions cache/large_payload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cache

import (
"encoding/json"
"fmt"
"testing"
"time"

"github.com/ethpandaops/dora/types"
"github.com/ethpandaops/dora/types/models"
"github.com/ethpandaops/dora/utils"
"github.com/sirupsen/logrus"
)

// A mainnet-scale slot arrival payload runs to hundreds of kilobytes, well past
// the 100KB MaxEntrySize. That setting only sizes the initial allocation; the
// real ceiling is HardMaxCacheSize/Shards, so these must still round trip. If
// they ever stop, every request rebuilds and the page cache silently stops
// absorbing load.
func TestLargeArrivalPayloadRoundTrips(t *testing.T) {
utils.Config = &types.Config{}

tc, err := NewTieredCache(100, "", "test", logrus.New())
if err != nil {
t.Fatalf("cache init: %v", err)
}

for _, nodes := range []int{500, 1000, 2000} {
resp := &models.SlotArrivalResponse{Slot: 15060285, Settled: true}
for i := 0; i < nodes; i++ {
resp.Nodes = append(resp.Nodes, &models.SlotArrivalNode{
Name: fmt.Sprintf("pub-contributoor/operator-%04d/hashed-abcdef01", i),
FullName: fmt.Sprintf("pub-contributoor/operator-%04d/hashed-abcdef0123456789", i),
Group: "community",
Implementation: "lighthouse",
Continent: "EU",
Country: "Germany",
CountryCode: "de",
MinMs: uint32(700 + i%800),
})
}

raw, err := json.Marshal(resp)
if err != nil {
t.Fatalf("marshal: %v", err)
}

key := fmt.Sprintf("slotarrival:15060285:%d", nodes)
if err := tc.Set(key, resp, time.Hour); err != nil {
t.Errorf("%d nodes (%d KB payload): Set failed: %v", nodes, len(raw)/1024, err)

continue
}

out := &models.SlotArrivalResponse{}
if _, err := tc.Get(key, out); err != nil {
t.Errorf("%d nodes (%d KB): Get failed: %v", nodes, len(raw)/1024, err)

continue
}

if len(out.Nodes) != nodes {
t.Errorf("%d nodes: round-tripped %d", nodes, len(out.Nodes))

continue
}

t.Logf("%4d nodes, %4d KB payload: cached and retrieved OK", nodes, len(raw)/1024)
}
}
51 changes: 51 additions & 0 deletions cache/nil_fidelity_epoch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cache

import (
"testing"
"time"

"github.com/ethpandaops/dora/types"
"github.com/ethpandaops/dora/types/models"
"github.com/ethpandaops/dora/utils"
"github.com/sirupsen/logrus"
)

func TestEpochPageNilArrivalThroughCache(t *testing.T) {
utils.Config = &types.Config{}

tc, err := NewTieredCache(100, "", "test", logrus.New())
if err != nil {
t.Fatal(err)
}

// no arrival data for this slot: ArrivalNodes is the presence signal
in := &models.EpochPageData{
Epoch: 7,
Slots: []*models.EpochPageDataSlot{
{Slot: 224},
{Slot: 225, ArrivalNodes: 12, ArrivalMinMs: 742, ArrivalP90Ms: 1180},
},
}

if err := tc.Set("epoch:7", in, time.Hour); err != nil {
t.Fatalf("set: %v", err)
}

out := &models.EpochPageData{}
if _, err := tc.Get("epoch:7", out); err != nil {
t.Fatalf("get: %v", err)
}

absent, present := out.Slots[0], out.Slots[1]
t.Logf("absent slot: nodes=%d min=%d p90=%d", absent.ArrivalNodes, absent.ArrivalMinMs, absent.ArrivalP90Ms)
t.Logf("present slot: nodes=%d min=%d p90=%d", present.ArrivalNodes, present.ArrivalMinMs, present.ArrivalP90Ms)

if absent.ArrivalNodes != 0 {
t.Errorf("slot with no data came back with %d nodes", absent.ArrivalNodes)
}

if present.ArrivalNodes != 12 || present.ArrivalMinMs != 742 || present.ArrivalP90Ms != 1180 {
t.Errorf("real arrival data did not survive: nodes=%d min=%d p90=%d",
present.ArrivalNodes, present.ArrivalMinMs, present.ArrivalP90Ms)
}
}
68 changes: 68 additions & 0 deletions cache/nil_fidelity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cache

import (
"testing"
"time"

"github.com/ethpandaops/dora/types"
"github.com/ethpandaops/dora/types/models"
"github.com/ethpandaops/dora/utils"
"github.com/sirupsen/logrus"
)

func TestNilPointerFidelityThroughCache(t *testing.T) {
utils.Config = &types.Config{}

tc, err := NewTieredCache(100, "", "test", logrus.New())
if err != nil {
t.Fatal(err)
}

// a node that only reported gossip: every other series must stay nil
p2p := uint32(742)
in := &models.SlotArrivalResponse{
Slot: 1, Settled: true,
Nodes: []*models.SlotArrivalNode{{Name: "n", P2PMs: &p2p}},
}

if err := tc.Set("slotarrival:1:x", in, time.Hour); err != nil {
t.Fatalf("set: %v", err)
}

out := &models.SlotArrivalResponse{}
if _, err := tc.Get("slotarrival:1:x", out); err != nil {
t.Fatalf("get: %v", err)
}

n := out.Nodes[0]
t.Logf("after round trip: P2PMs=%v APIMs=%v HeadMs=%v NPMs=%v",
deref(n.P2PMs), deref(n.APIMs), deref(n.HeadMs), deref(n.NPMs))

for name, ptr := range map[string]*uint32{"APIMs": n.APIMs, "HeadMs": n.HeadMs, "NPMs": n.NPMs} {
if ptr != nil {
t.Errorf("%s should be nil after round trip, got pointer to %d", name, *ptr)
}
}
}

func deref(p *uint32) string {
if p == nil {
return "nil"
}

return "&" + itoa(*p)
}

func itoa(v uint32) string {
if v == 0 {
return "0"
}

var b []byte
for v > 0 {
b = append([]byte{byte('0' + v%10)}, b...)
v /= 10
}

return string(b)
}
78 changes: 78 additions & 0 deletions cache/nil_fidelity_waves_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cache

import (
"testing"
"time"

"github.com/ethpandaops/dora/types"
"github.com/ethpandaops/dora/types/models"
"github.com/ethpandaops/dora/utils"
"github.com/sirupsen/logrus"
)

// The waves response leans on nil to mean "no data": a nil section hides its
// panel, a nil FirstSeenMs renders "not seen". Like the arrival response, it
// must stay on the cache's JSON path, where nil survives a round trip.
func TestWavesNilFidelityThroughCache(t *testing.T) {
utils.Config = &types.Config{}

tc, err := NewTieredCache(100, "", "test", logrus.New())
if err != nil {
t.Fatal(err)
}

pct := 87.5
in := &models.SlotWavesResponse{
Slot: 9, Settled: true,
// Attestations deliberately nil: a network without the cbt attestation
// model must not come back as an empty wave.
Columns: &models.SlotColumnWave{
BlobCount: 3,
Columns: []*models.SlotColumn{
// probed but never seen
{Index: 0, AvailabilityPct: &pct, Probes: 4},
// seen but never probed
{Index: 1, FirstSeenMs: ptr(uint32(1300))},
},
},
}

if err := tc.Set("slotwaves:9:x", in, time.Hour); err != nil {
t.Fatalf("set: %v", err)
}

out := &models.SlotWavesResponse{}
if _, err := tc.Get("slotwaves:9:x", out); err != nil {
t.Fatalf("get: %v", err)
}

if out.Attestations != nil {
t.Errorf("Attestations should stay nil, got %+v", out.Attestations)
}

if out.Columns == nil {
t.Fatal("Columns section lost")
}

unseen, unprobed := out.Columns.Columns[0], out.Columns.Columns[1]

if unseen.FirstSeenMs != nil {
t.Errorf("unseen column got FirstSeenMs %d", *unseen.FirstSeenMs)
}

if unseen.AvailabilityPct == nil || *unseen.AvailabilityPct != pct {
t.Errorf("availability lost: %v", unseen.AvailabilityPct)
}

if unprobed.AvailabilityPct != nil {
t.Errorf("unprobed column got AvailabilityPct %v", *unprobed.AvailabilityPct)
}

if unprobed.FirstSeenMs == nil || *unprobed.FirstSeenMs != 1300 {
t.Errorf("first seen lost: %v", unprobed.FirstSeenMs)
}
}

func ptr[T any](v T) *T {
return &v
}
2 changes: 2 additions & 0 deletions clients/consensus/chainspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ type ChainSpecConfig struct {
ContributionDueBpsGloas uint64 `yaml:"CONTRIBUTION_DUE_BPS_GLOAS" check-if-fork:"GloasForkEpoch"`
SyncMessageDueBpsGloas uint64 `yaml:"SYNC_MESSAGE_DUE_BPS_GLOAS" check-if-fork:"GloasForkEpoch"`
PayloadAttestationDueBps uint64 `yaml:"PAYLOAD_ATTESTATION_DUE_BPS" check-if-fork:"GloasForkEpoch"`
BuilderPaymentThresholdNumerator uint64 `yaml:"BUILDER_PAYMENT_THRESHOLD_NUMERATOR" check-if-fork:"GloasForkEpoch"`
BuilderPaymentThresholdDenominator uint64 `yaml:"BUILDER_PAYMENT_THRESHOLD_DENOMINATOR" check-if-fork:"GloasForkEpoch"`
PayloadDueBps uint64 `yaml:"PAYLOAD_DUE_BPS" check-if-fork:"GloasForkEpoch"`
MaxRequestPayloads uint64 `yaml:"MAX_REQUEST_PAYLOADS" check-if-fork:"GloasForkEpoch"`

Expand Down
Loading