From c76dec55b1da56a41d3c8213c09f07434e546af2 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Thu, 20 Aug 2026 12:13:49 +1000 Subject: [PATCH 01/70] feat: xatu-backed block propagation tab on the slot page (POC) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an optional Xatu ClickHouse data source (clients/xatu) using the typed query builders and scannable row structs generated in the xatu repository — no hand-written SQL and no untyped scanning, so a xatu schema change surfaces as a compile error on dependency bump. The slot page gains a lazy Propagation tab backed by /slot/{slot}/arrival, which aggregates beacon_api_eth_v1_events_block observations per client (first observation wins, duplicates counted), with percentile and per-continent summaries and a 'Data from Xatu' attribution. Query pressure is bounded three ways: responses for slots younger than xatu.settleDelay are never cached (the ingest pipeline may still be receiving events) while settled slots cache for 30s/1h; the frontend cache coalesces concurrent builds; and an optional clickhouseCachedDsn routes settled-slot queries through a response-caching proxy (chproxy) shared across instances. NOTE: go.mod carries a replace directive to a local xatu checkout until ethpandaops/xatu#886 merges and a version containing pkg/proto/clickhouse is tagged. Claude-Session: https://claude.ai/code/session_01PqWvkAjYgCjjhtu83gJeXP --- clients/xatu/client.go | 194 +++++++++++++++++++++++++++ cmd/dora-explorer/main.go | 14 ++ go.mod | 46 ++++--- go.sum | 251 +++++++++-------------------------- handlers/slot.go | 3 + handlers/slot_arrival.go | 208 +++++++++++++++++++++++++++++ static/images/xatu.png | Bin 0 -> 24575 bytes templates/slot/arrival.html | 107 +++++++++++++++ templates/slot/slot.html | 14 ++ types/config.go | 21 +++ types/models/slot.go | 1 + types/models/slot_arrival.go | 40 ++++++ 12 files changed, 693 insertions(+), 206 deletions(-) create mode 100644 clients/xatu/client.go create mode 100644 handlers/slot_arrival.go create mode 100644 static/images/xatu.png create mode 100644 templates/slot/arrival.html create mode 100644 types/models/slot_arrival.go diff --git a/clients/xatu/client.go b/clients/xatu/client.go new file mode 100644 index 000000000..7a3deac79 --- /dev/null +++ b/clients/xatu/client.go @@ -0,0 +1,194 @@ +// Package xatu provides read access to a Xatu ClickHouse instance using the +// typed query builders and row structs generated in the xatu repository. +package xatu + +import ( + "context" + "crypto/tls" + "fmt" + "net/url" + "time" + + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" + + "github.com/ethpandaops/dora/types" + xch "github.com/ethpandaops/xatu/pkg/proto/clickhouse" +) + +const ( + defaultDatabase = "default" + defaultSettleDelay = 30 * time.Second + defaultConcurrencyLimit = 2 +) + +// GlobalClient is the process-wide xatu client. It is nil when xatu is not +// configured; all xatu-backed features must treat that as disabled. +var GlobalClient *Client + +// Client wraps ClickHouse connections to a Xatu instance. Queries for settled +// slots are routed to the cached endpoint when one is configured, so a +// response-caching proxy can absorb repeated queries across instances. +type Client struct { + conn driver.Conn + cachedConn driver.Conn + database string + network string + settleDelay time.Duration + sem chan struct{} +} + +// NewClient connects to the configured ClickHouse endpoints. defaultNetwork is +// used as the meta_network_name filter when the config does not set one. +func NewClient(cfg *types.XatuConfig, defaultNetwork string) (*Client, error) { + if cfg.ClickhouseDsn == "" { + return nil, fmt.Errorf("xatu clickhouse dsn is required") + } + + conn, err := connect(cfg.ClickhouseDsn, cfg.Database) + if err != nil { + return nil, fmt.Errorf("xatu clickhouse: %w", err) + } + + client := &Client{ + conn: conn, + database: cfg.Database, + network: cfg.NetworkName, + settleDelay: cfg.SettleDelay, + } + + if cfg.ClickhouseCachedDsn != "" { + cachedConn, err := connect(cfg.ClickhouseCachedDsn, cfg.Database) + if err != nil { + return nil, fmt.Errorf("xatu cached clickhouse: %w", err) + } + + client.cachedConn = cachedConn + } + + if client.database == "" { + client.database = defaultDatabase + } + + if client.network == "" { + client.network = defaultNetwork + } + + if client.settleDelay <= 0 { + client.settleDelay = defaultSettleDelay + } + + concurrency := cfg.ConcurrencyLimit + if concurrency <= 0 { + concurrency = defaultConcurrencyLimit + } + + client.sem = make(chan struct{}, concurrency) + + return client, nil +} + +// Database returns the ClickHouse database queries run against. +func (c *Client) Database() string { + return c.database +} + +// Network returns the meta_network_name filter value. +func (c *Client) Network() string { + return c.network +} + +// SettleDelay returns how long after slot start the ingest pipeline is assumed +// to still be receiving events for that slot. +func (c *Client) SettleDelay() time.Duration { + return c.settleDelay +} + +// Query runs a built query. Settled queries use the cached endpoint when one +// is configured; unsettled queries always bypass it so a pre-settle response +// never gets frozen into a shared response cache. +func (c *Client) Query(ctx context.Context, settled bool, query xch.SQLQuery) (driver.Rows, error) { + select { + case c.sem <- struct{}{}: + case <-ctx.Done(): + return nil, ctx.Err() + } + defer func() { <-c.sem }() + + conn := c.conn + if settled && c.cachedConn != nil { + conn = c.cachedConn + } + + return conn.Query(ctx, query.Query, query.Args...) +} + +// connect opens a ClickHouse connection from a DSN. https/http DSNs use the +// HTTP protocol (chproxy compatible), clickhouse:// DSNs use the native +// protocol. +func connect(dsn, database string) (driver.Conn, error) { + parsed, err := url.Parse(dsn) + if err != nil { + return nil, fmt.Errorf("invalid dsn: %w", err) + } + + if database == "" { + database = defaultDatabase + } + + options := &clickhouse.Options{ + Auth: clickhouse.Auth{ + Database: database, + Username: parsed.User.Username(), + }, + DialTimeout: 10 * time.Second, + ReadTimeout: 60 * time.Second, + } + + if password, ok := parsed.User.Password(); ok { + options.Auth.Password = password + } + + host := parsed.Hostname() + port := parsed.Port() + + switch parsed.Scheme { + case "https": + if port == "" { + port = "443" + } + + options.Protocol = clickhouse.HTTP + options.TLS = &tls.Config{MinVersion: tls.VersionTLS12} + case "http": + if port == "" { + port = "8123" + } + + options.Protocol = clickhouse.HTTP + case "clickhouse": + if port == "" { + port = "9000" + } + + options.Protocol = clickhouse.Native + default: + return nil, fmt.Errorf("unsupported dsn scheme %q", parsed.Scheme) + } + + options.Addr = []string{host + ":" + port} + + conn, err := clickhouse.Open(options) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + if err := conn.Ping(ctx); err != nil { + return nil, fmt.Errorf("ping failed: %w", err) + } + + return conn, nil +} diff --git a/cmd/dora-explorer/main.go b/cmd/dora-explorer/main.go index c36dabc38..d8916dd19 100644 --- a/cmd/dora-explorer/main.go +++ b/cmd/dora-explorer/main.go @@ -18,6 +18,7 @@ import ( "github.com/sirupsen/logrus" "github.com/urfave/negroni" + "github.com/ethpandaops/dora/clients/xatu" "github.com/ethpandaops/dora/db" "github.com/ethpandaops/dora/handlers" "github.com/ethpandaops/dora/handlers/api" @@ -89,6 +90,18 @@ func main() { logger.Fatalf("error starting tx signature service: %v", err) } + if cfg.Xatu.Enabled { + specs := services.GlobalBeaconService.GetChainState().GetSpecs() + + xatuClient, err := xatu.NewClient(&cfg.Xatu, specs.ConfigName) + if err != nil { + logger.Fatalf("error connecting to xatu clickhouse: %v", err) + } + + xatu.GlobalClient = xatuClient + logger.WithField("module", "xatu").Infof("connected to xatu clickhouse (network: %v)", xatuClient.Network()) + } + if cfg.RateLimit.Enabled { err = services.StartCallRateLimiter(cfg.GetProxyCount(), cfg.RateLimit.Rate, cfg.RateLimit.Burst) if err != nil { @@ -199,6 +212,7 @@ func startFrontend(router *mux.Router) { router.HandleFunc("/slot/{slotOrHash}/tracoor", handlers.SlotTracoor).Methods("GET") router.HandleFunc("/slot/{slotOrHash}/duties", handlers.SlotDuties).Methods("GET") router.HandleFunc("/slot/{slotOrHash}/bidseen", handlers.SlotBidSeen).Methods("GET") + router.HandleFunc("/slot/{slotOrHash}/arrival", handlers.SlotArrival).Methods("GET") router.HandleFunc("/slot/{root}/blob/{index}", handlers.SlotBlob).Methods("GET") router.HandleFunc("/blocks", handlers.Blocks).Methods("GET") router.HandleFunc("/blocks/filtered", handlers.BlocksFiltered).Methods("GET") diff --git a/go.mod b/go.mod index a3a68e6a2..9e96450cf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ethpandaops/dora -go 1.26.1 +go 1.26.2 require ( github.com/Masterminds/sprig/v3 v3.3.0 @@ -19,7 +19,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/lib/pq v1.12.3 - github.com/libp2p/go-libp2p v0.43.0 + github.com/libp2p/go-libp2p v0.47.0 github.com/mashingan/smapping v0.1.19 github.com/mattn/go-sqlite3 v1.14.49 github.com/minio/minio-go/v7 v7.2.1 @@ -49,11 +49,13 @@ require ( ) require ( + github.com/ClickHouse/ch-go v0.73.0 // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/OffchainLabs/go-bitfield v0.0.0-20251031151322-f427d04d8506 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/VictoriaMetrics/fastcache v1.13.0 // indirect + github.com/andybalholm/brotli v1.2.2 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect github.com/cockroachdb/errors v1.12.0 // indirect @@ -64,21 +66,22 @@ require ( github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect + github.com/dunglas/httpsfv v1.1.0 // indirect github.com/emicklei/dot v1.9.1 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.8 // indirect github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/fjl/jsonw v0.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect - github.com/francoispqt/gojay v1.2.13 // indirect github.com/getsentry/sentry-go v0.32.0 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.7.1 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/gofrs/flock v0.13.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gopacket v1.1.19 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/go-cid v0.5.0 // indirect @@ -94,10 +97,10 @@ require ( github.com/libp2p/go-flow-metrics v0.3.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect github.com/libp2p/go-libp2p-mplex v0.11.0 // indirect - github.com/libp2p/go-libp2p-pubsub v0.15.0 // indirect + github.com/libp2p/go-libp2p-pubsub v0.16.1-0.20260611143718-41b11d5cb1a7 // indirect github.com/libp2p/go-mplex v0.7.0 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/libp2p/go-netroute v0.2.2 // indirect + github.com/libp2p/go-netroute v0.4.0 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v5 v5.1.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -106,7 +109,7 @@ require ( github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/crc64nvme v1.1.1 // indirect - github.com/minio/highwayhash v1.0.2 // indirect + github.com/minio/highwayhash v1.0.3 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect @@ -121,8 +124,10 @@ require ( github.com/multiformats/go-varint v0.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/gomega v1.38.2 // indirect + github.com/paulmach/orb v0.13.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/philhofer/fwd v1.2.0 // indirect + github.com/pierrec/lz4/v4 v4.1.27 // indirect github.com/pion/datachannel v1.5.10 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/dtls/v3 v3.1.2 // indirect @@ -145,13 +150,14 @@ require ( github.com/pion/webrtc/v4 v4.1.4 // indirect github.com/pk910/hashtree-bindings v0.2.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516 // indirect + github.com/prysmaticlabs/fastssz v0.0.0-20251103153600-259302269bfc // indirect github.com/prysmaticlabs/gohashtree v0.0.5-beta // indirect - github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.54.0 // indirect - github.com/quic-go/webtransport-go v0.9.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.1 // indirect + github.com/quic-go/webtransport-go v0.10.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -165,24 +171,25 @@ require ( go.uber.org/dig v1.19.0 // indirect go.uber.org/fx v1.24.0 // indirect go.uber.org/mock v0.6.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.38.0 // indirect golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 // indirect golang.org/x/tools v0.48.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a // indirect gopkg.in/ini.v1 v1.67.2 // indirect lukechampine.com/blake3 v1.4.1 // indirect ) require ( - dario.cat/mergo v1.0.1 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.47.0 github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.22.0 // indirect + github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/consensys/gnark-crypto v0.18.1 // indirect github.com/deckarep/golang-set/v2 v2.8.0 // indirect @@ -190,6 +197,7 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ethpandaops/xatu v0.0.0-00010101000000-000000000000 github.com/ferranbt/fastssz v1.0.0 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -197,7 +205,7 @@ require ( github.com/goccy/go-yaml v1.19.2 // indirect github.com/golang/snappy v1.0.1-0.20260716114414-9ae09f520e93 github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/holiman/uint256 v1.3.2 github.com/huandu/go-clone v1.7.3 // indirect github.com/huandu/xstrings v1.5.0 // indirect @@ -232,8 +240,8 @@ require ( github.com/supranational/blst v0.3.16 // indirect github.com/tdewolff/parse v2.3.4+incompatible // indirect github.com/tdewolff/test v1.0.9 // indirect - github.com/tklauser/go-sysconf v0.3.15 // indirect - github.com/tklauser/numcpus v0.10.0 // indirect + github.com/tklauser/go-sysconf v0.4.0 // indirect + github.com/tklauser/numcpus v0.12.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect @@ -249,3 +257,5 @@ require ( ) tool github.com/pk910/dynamic-ssz/dynssz-gen + +replace github.com/ethpandaops/xatu => ../../../../xatu/.claude/worktrees/ch-read-types diff --git a/go.sum b/go.sum index 728c0b758..8c9073ce7 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,13 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= -dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= -dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= -dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ClickHouse/ch-go v0.73.0 h1:jsHiGRbQ3sz+gekvDFJF29LWDo5dzbJm5s1h8TWVP2M= +github.com/ClickHouse/ch-go v0.73.0/go.mod h1:wkFIxrqlXeRJ9cn3r5Fz5Qen9jl5aTMPuGZeuJpANNY= +github.com/ClickHouse/clickhouse-go/v2 v2.47.0 h1:ZDAzrnKSOPTIsm4tdUNfrii2yc8dk4SVRLC77BR7Z5Q= +github.com/ClickHouse/clickhouse-go/v2 v2.47.0/go.mod h1:sPj7C7UYQ2MWHcfX+4eGN6nwnCqwUKfgO6PcwKpd6K8= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= @@ -40,16 +35,14 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM= +github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= -github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4= @@ -57,7 +50,6 @@ github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2w github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/crlib v0.0.0-20241112164430-1264a2edc35b h1:SHlYZ/bMx7frnmeqCu+xm0TCxXLzX3jQIVuFbnFGtFU= @@ -82,11 +74,11 @@ github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI= github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= +github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/crate-crypto/go-eth-kzg v1.5.0 h1:FYRiJMJG2iv+2Dy3fi14SVGjcPteZ5HAAUe4YWlJygc= github.com/crate-crypto/go-eth-kzg v1.5.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -111,7 +103,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54= +github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/dot v1.9.1 h1:SBySmOPaQ6+fpmnqEaG1bCHj5hW65A0jJpcPpz+TG3w= @@ -136,25 +129,23 @@ github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9g github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= github.com/fjl/jsonw v0.1.0 h1:V3MyR79fjLpn/+bMgvegdGUIhoJOzjmqWcKDgcOmY1I= github.com/fjl/jsonw v0.1.0/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= -github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= -github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -191,19 +182,13 @@ github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -215,39 +200,26 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.1-0.20260716114414-9ae09f520e93 h1:GpQQr4L8jsBtJSURCDqQboOdgpVMU6vR9REjc8nR4Qc= github.com/golang/snappy v1.0.1-0.20260716114414-9ae09f520e93/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac= github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc= github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -335,13 +307,10 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= @@ -364,7 +333,6 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -384,33 +352,33 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-flow-metrics v0.3.0 h1:q31zcHUvHnwDO0SHaukewPYgwOBSxtt830uJtUx6784= github.com/libp2p/go-flow-metrics v0.3.0/go.mod h1:nuhlreIwEguM1IvHAew3ij7A8BMlyHQJ279ao24eZZo= -github.com/libp2p/go-libp2p v0.43.0 h1:b2bg2cRNmY4HpLK8VHYQXLX2d3iND95OjodLFymvqXU= -github.com/libp2p/go-libp2p v0.43.0/go.mod h1:IiSqAXDyP2sWH+J2gs43pNmB/y4FOi2XQPbsb+8qvzc= +github.com/libp2p/go-libp2p v0.47.0 h1:qQpBjSCWNQFF0hjBbKirMXE9RHLtSuzTDkTfr1rw0yc= +github.com/libp2p/go-libp2p v0.47.0/go.mod h1:s8HPh7mMV933OtXzONaGFseCg/BE//m1V34p3x4EUOY= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-mplex v0.11.0 h1:0vwpLXRSfkTzshEjETIEgJaVxXvg+orbxYoIb3Ty5qM= github.com/libp2p/go-libp2p-mplex v0.11.0/go.mod h1:QrsdNY3lzjpdo9V1goJfPb0O65Nms0sUR8CDAO18f6k= -github.com/libp2p/go-libp2p-pubsub v0.15.0 h1:cG7Cng2BT82WttmPFMi50gDNV+58K626m/wR00vGL1o= -github.com/libp2p/go-libp2p-pubsub v0.15.0/go.mod h1:lr4oE8bFgQaifRcoc2uWhWWiK6tPdOEKpUuR408GFN4= +github.com/libp2p/go-libp2p-pubsub v0.16.1-0.20260611143718-41b11d5cb1a7 h1:UMiJ408NqO9Sf2ANutEM3An8Em3K+qn78eoIgzY3PIY= +github.com/libp2p/go-libp2p-pubsub v0.16.1-0.20260611143718-41b11d5cb1a7/go.mod h1:l00Tc/MXTM/dK69HFxKWIe0yaNwMy5OuU0dyuZaWJa0= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-mplex v0.7.0 h1:BDhFZdlk5tbr0oyFq/xv/NPGfjbnrsDam1EvutpBDbY= github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8= -github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE= +github.com/libp2p/go-netroute v0.4.0 h1:sZZx9hyANYUx9PZyqcgE/E1GUG3iEtTZHUEvdtXT7/Q= +github.com/libp2p/go-netroute v0.4.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v5 v5.1.0 h1:8Qlxj4E9JGJAQVW6+uj2o7mqkqsIVlSUGmTWhlXzoHE= github.com/libp2p/go-yamux/v5 v5.1.0/go.mod h1:tgIQ07ObtRR/I0IWsFOyQIL9/dR5UXgc2s8xKmNZv1o= -github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marcopolo/simnet v0.0.7 h1:DpH8BMGsF9+1w13L8rvCaAhb6nYJdY+dIXncDrssvUs= +github.com/marcopolo/simnet v0.0.7/go.mod h1:tfQF1u2DmaB6WHODMtQaLtClEf3a296CKQLq5gAsIS0= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mashingan/smapping v0.1.19 h1:SsEtuPn2UcM1croIupPtGLgWgpYRuS0rSQMvKD9g2BQ= @@ -427,10 +395,8 @@ github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.49 h1:B8jBHC3xhxZgxztrgruTuLucebnULQnx4W7cF7SAE9w= github.com/mattn/go-sqlite3 v1.14.49/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= -github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= @@ -442,8 +408,8 @@ github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4S github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= -github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= -github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.2.1 h1:PfBfwvKB/MmqyN8Vb1G9voWisaM9OrLv+WwOvMwS9Dw= @@ -461,8 +427,6 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -494,8 +458,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -512,11 +474,14 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/paulmach/orb v0.13.0 h1:r7n7mQGGF+cj/CbcivEj9J3HGK+XR+yXnvzRdq9saIw= +github.com/paulmach/orb v0.13.0/go.mod h1:6scRWINywA2Jf05dcjOfLfxrUIMECvTSG2MVbRLxu/k= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= +github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= @@ -576,16 +541,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pressly/goose/v3 v3.27.3 h1:pIglVHjw99r4e/hDHHwbl9vfOsDMqUokfkXo6+n/RxA= github.com/pressly/goose/v3 v3.27.3/go.mod h1:Dag+xpV6o20HR2LFY1j0q6MDwc3f7vPUFDA77R+0yGY= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY= github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/protolambda/bls12-381-util v0.1.0 h1:05DU2wJN7DTU7z28+Q+zejXkIsA/MF8JZQGhtBZZiWk= @@ -594,8 +555,8 @@ github.com/protolambda/zrnt v0.34.1 h1:qW55rnhZJDnOb3TwFiFRJZi3yTXFrJdGOFQM7vCwY github.com/protolambda/zrnt v0.34.1/go.mod h1:A0fezkp9Tt3GBLATSPIbuY4ywYESyAuc/FFmPKg8Lqs= github.com/protolambda/ztyp v0.2.2 h1:rVcL3vBu9W/aV646zF6caLS/dyn9BN8NYiuJzicLNyY= github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= -github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516 h1:xuVAdtz5ShYblG2sPyb4gw01DF8InbOI/kBCQjk7NiM= -github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516/go.mod h1:h2OlIZD/M6wFvV3YMZbW16lFgh3Rsye00G44J2cwLyU= +github.com/prysmaticlabs/fastssz v0.0.0-20251103153600-259302269bfc h1:ASmh3y4ALne2OoabF5pPL8OcIpBko8gFMg5018MxkBI= +github.com/prysmaticlabs/fastssz v0.0.0-20251103153600-259302269bfc/go.mod h1:h2OlIZD/M6wFvV3YMZbW16lFgh3Rsye00G44J2cwLyU= github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 h1:lC8kiphgdOBTcbTvo8MwkvpKjO0SlAgjv4xIK5FGJ94= github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15/go.mod h1:8svFBIKKu31YriBG/pNizo9N0Jr9i5PQ+dFkxWg3x5k= github.com/prysmaticlabs/gohashtree v0.0.5-beta h1:ct41mg7HyIZd7uoSM/ud23f+3DxQG9tlMlQG+BVX23c= @@ -604,12 +565,12 @@ github.com/prysmaticlabs/protoc-gen-go-cast v0.0.0-20230228205207-28762a7b9294 h github.com/prysmaticlabs/protoc-gen-go-cast v0.0.0-20230228205207-28762a7b9294/go.mod h1:ZVEbRdnMkGhp/pu35zq4SXxtvUwWK0J1MATtekZpH2Y= github.com/prysmaticlabs/prysm/v5 v5.3.3 h1:nV/XGB0L8sSAL6fQ8EFJukOYmFowWwRRBvk3+Yilhps= github.com/prysmaticlabs/prysm/v5 v5.3.3/go.mod h1:2SaUMpJ+O8r/pcnNDMHbrk0Ki9ObQXvfRc+rQHovzVk= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= -github.com/quic-go/webtransport-go v0.9.0 h1:jgys+7/wm6JarGDrW+lD/r9BGqBAmqY/ssklE09bA70= -github.com/quic-go/webtransport-go v0.9.0/go.mod h1:4FUYIiUc75XSsF6HShcLeXXYZJ9AGwo/xh3L8M/P1ao= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= +github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/webtransport-go v0.10.0 h1:LqXXPOXuETY5Xe8ITdGisBzTYmUOy5eSj+9n4hLTjHI= +github.com/quic-go/webtransport-go v0.10.0/go.mod h1:LeGIXr5BQKE3UsynwVBeQrU1TPrbh73MGoC6jd+V7ow= github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -627,12 +588,11 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sethvargo/go-retry v0.4.0 h1:9qy1OoIAxBL+gBYnkTnTnWle5wlfsXQlwRzIbbpdqPw= github.com/sethvargo/go-retry v0.4.0/go.mod h1:tvsjdKG6xfiCx4LSiUZ06kcv38xvdVQwv8R6/VnnVWg= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= @@ -641,34 +601,10 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= -github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= -github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= -github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= -github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= -github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= -github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= -github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= -github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= -github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= -github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= -github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= -github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= -github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= -github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.10.0 h1:T8MxJJXVZkfcC5zSRMRAg2F8+lxjmUCGGWPzFxO+Msc= github.com/sirupsen/logrus v1.10.0/go.mod h1:FXZFonkDAnFozmO+5hGAFvB0Yg9/j2SIhA/QuIkP180= -github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -709,7 +645,6 @@ github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo= github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs= github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38= @@ -722,23 +657,23 @@ github.com/timandy/routine v1.1.6 h1:cueNRVPutK8O6387LL7dmYPLNyS6aKlPCPi5qWCLdc8 github.com/timandy/routine v1.1.6/go.mod h1:kXslgIosdY8LW0byTyPnenDgn4/azt2euufAq9rK51w= github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY= github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= -github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= -github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= -github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= -github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= -github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= -github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= +github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= +github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= +github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= +github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= +github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= -github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/wealdtech/go-bytesutil v1.2.1 h1:TjuRzcG5KaPwaR5JB7L/OgJqMQWvlrblA1n0GfcXFSY= github.com/wealdtech/go-bytesutil v1.2.1/go.mod h1:RhUDUGT1F4UP4ydqbYp2MWJbAel3M+mKd057Pad7oag= github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= +github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -749,15 +684,14 @@ github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -781,17 +715,13 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -811,14 +741,9 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A= golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -827,15 +752,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -858,15 +776,7 @@ golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -875,14 +785,10 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -914,6 +820,7 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 h1:RJhm5l6Fo4rmEIcndxDllNhhf/fAx8qIm4t6A7vpm2A= @@ -930,7 +837,6 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -942,15 +848,9 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -959,7 +859,6 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -974,26 +873,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= -google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a h1:qI/YMH1ep2qQtqcp00gMQyoU7mjvbhg88GJKCvfoLj0= google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1018,14 +901,12 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss= gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1035,10 +916,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= @@ -1050,5 +927,3 @@ modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog= modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw= -sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/handlers/slot.go b/handlers/slot.go index 88bb38f92..cf3522d29 100644 --- a/handlers/slot.go +++ b/handlers/slot.go @@ -33,6 +33,7 @@ import ( "github.com/ethpandaops/dora/dbtypes" "github.com/ethpandaops/dora/indexer/beacon" "github.com/ethpandaops/dora/indexer/beacon/statetransition" + "github.com/ethpandaops/dora/clients/xatu" "github.com/ethpandaops/dora/services" "github.com/ethpandaops/dora/templates" "github.com/ethpandaops/dora/types" @@ -59,6 +60,7 @@ func Slot(w http.ResponseWriter, r *http.Request) { "slot/builder_deposit_requests.html", "slot/builder_exit_requests.html", "slot/bids.html", + "slot/arrival.html", "slot/ptc_votes.html", "slot/inclusion_lists.html", "slot/block_access_list.html", @@ -266,6 +268,7 @@ func buildSlotPageData(ctx context.Context, blockSlot int64, blockRoot []byte) ( EpochFinalized: finalizedEpoch >= chainState.EpochOfSlot(slot), Badges: []*models.SlotPageBlockBadge{}, TracoorUrl: utils.Config.Frontend.TracoorUrl, + XatuEnabled: xatu.GlobalClient != nil, } var epochStatsValues *beacon.EpochStatsValues diff --git a/handlers/slot_arrival.go b/handlers/slot_arrival.go new file mode 100644 index 000000000..8f5697a41 --- /dev/null +++ b/handlers/slot_arrival.go @@ -0,0 +1,208 @@ +package handlers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sort" + "strconv" + "time" + + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" + + "github.com/ethpandaops/go-eth2-client/spec/phase0" + + "github.com/ethpandaops/dora/clients/xatu" + "github.com/ethpandaops/dora/services" + "github.com/ethpandaops/dora/types/models" + xch "github.com/ethpandaops/xatu/pkg/proto/clickhouse" +) + +// SlotArrival returns block propagation observations for the path slot from +// Xatu, as JSON for the lazy propagation tab on the slot page. +func SlotArrival(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if xatu.GlobalClient == nil { + http.Error(w, "Xatu is not configured", http.StatusNotFound) + return + } + + vars := mux.Vars(r) + slot, err := strconv.ParseUint(vars["slotOrHash"], 10, 64) + if err != nil { + http.Error(w, "Invalid slot", http.StatusBadRequest) + return + } + + cacheKey := fmt.Sprintf("slotarrival:%d", slot) + pageRes, pageErr := services.GlobalFrontendCache.ProcessCachedPage(cacheKey, true, &models.SlotArrivalResponse{}, func(pageCall *services.FrontendCacheProcessingPage) any { + data, cacheTimeout, buildErr := buildSlotArrivalData(pageCall.CallCtx, phase0.Slot(slot)) + if buildErr != nil { + logrus.WithError(buildErr).Error("error loading slot arrival data from xatu") + pageCall.CacheTimeout = -1 + + return &models.SlotArrivalResponse{Slot: slot} + } + + pageCall.CacheTimeout = cacheTimeout + + return data + }) + if pageErr != nil { + logrus.WithError(pageErr).Error("error building slot arrival data") + http.Error(w, "Internal server error", http.StatusServiceUnavailable) + return + } + + result, ok := pageRes.(*models.SlotArrivalResponse) + if !ok { + http.Error(w, "Internal server error", http.StatusServiceUnavailable) + return + } + + if err := json.NewEncoder(w).Encode(result); err != nil { + logrus.WithError(err).Error("error encoding slot arrival data") + http.Error(w, "Internal server error", http.StatusServiceUnavailable) + } +} + +// buildSlotArrivalData queries Xatu for the slot's block events and aggregates +// them per observing client. It returns the response and the cache timeout: +// no caching while the ingest pipeline may still receive events for the slot, +// a short timeout for recent slots and a long one for historic slots. +func buildSlotArrivalData(ctx context.Context, slot phase0.Slot) (*models.SlotArrivalResponse, time.Duration, error) { + client := xatu.GlobalClient + chainState := services.GlobalBeaconService.GetChainState() + slotTime := chainState.SlotToTime(slot) + + settled := time.Now().After(slotTime.Add(client.SettleDelay())) + + req := &xch.ListBeaconApiEthV1EventsBlockRequest{ + MetaNetworkName: &xch.StringFilter{Filter: &xch.StringFilter_Eq{Eq: client.Network()}}, + Slot: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{Eq: uint32(slot)}}, + SlotStartDateTime: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{ + Eq: uint32(slotTime.Unix()), + }}, + OrderBy: "propagation_slot_start_diff", + PageSize: 10000, + } + + query, err := xch.BuildListBeaconApiEthV1EventsBlockQuery(req) + if err != nil { + return nil, -1, fmt.Errorf("build query: %w", err) + } + + queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + rows, err := client.Query(queryCtx, settled, query) + if err != nil { + return nil, -1, fmt.Errorf("query: %w", err) + } + defer rows.Close() + + type clientAgg struct { + row xch.BeaconApiEthV1EventsBlockRow + count int + } + + perClient := map[string]*clientAgg{} + observations := 0 + + for rows.Next() { + var row xch.BeaconApiEthV1EventsBlockRow + if err := rows.ScanStruct(&row); err != nil { + return nil, -1, fmt.Errorf("scan: %w", err) + } + + observations++ + + // Rows are ordered by propagation time, so the first row per client is + // its earliest observation; later rows only bump the duplicate count. + if agg := perClient[row.MetaClientName]; agg != nil { + agg.count++ + } else { + perClient[row.MetaClientName] = &clientAgg{row: row, count: 1} + } + } + + if err := rows.Err(); err != nil { + return nil, -1, fmt.Errorf("rows: %w", err) + } + + response := &models.SlotArrivalResponse{ + Slot: uint64(slot), + Settled: settled, + Observations: observations, + } + + if len(perClient) > 0 { + clients := make([]*models.SlotArrivalClient, 0, len(perClient)) + continents := map[string]*models.SlotArrivalContinent{} + + for _, agg := range perClient { + clients = append(clients, &models.SlotArrivalClient{ + Name: agg.row.MetaClientName, + Implementation: agg.row.MetaConsensusImplementation, + Continent: agg.row.MetaClientGeoContinentCode, + MinMs: agg.row.PropagationSlotStartDiff, + Observations: agg.count, + }) + + continent := continents[agg.row.MetaClientGeoContinentCode] + if continent == nil { + continent = &models.SlotArrivalContinent{ + Code: agg.row.MetaClientGeoContinentCode, + MinMs: agg.row.PropagationSlotStartDiff, + } + continents[agg.row.MetaClientGeoContinentCode] = continent + } + + continent.Clients++ + if agg.row.PropagationSlotStartDiff < continent.MinMs { + continent.MinMs = agg.row.PropagationSlotStartDiff + } + } + + sort.Slice(clients, func(a, b int) bool { + return clients[a].MinMs < clients[b].MinMs + }) + + response.Clients = clients + response.Stats = &models.SlotArrivalStats{ + UniqueClients: len(clients), + MinMs: clients[0].MinMs, + P50Ms: clients[len(clients)/2].MinMs, + P90Ms: clients[len(clients)*9/10].MinMs, + MaxMs: clients[len(clients)-1].MinMs, + } + + continentList := make([]*models.SlotArrivalContinent, 0, len(continents)) + for _, continent := range continents { + continentList = append(continentList, continent) + } + + sort.Slice(continentList, func(a, b int) bool { + return continentList[a].MinMs < continentList[b].MinMs + }) + + response.Continents = continentList + } + + cacheTimeout := time.Duration(-1) + + switch { + case !settled: + // The pipeline may still be receiving events for this slot; never cache. + cacheTimeout = -1 + case time.Since(slotTime) < 30*time.Minute: + cacheTimeout = 30 * time.Second + default: + cacheTimeout = time.Hour + } + + return response, cacheTimeout, nil +} diff --git a/static/images/xatu.png b/static/images/xatu.png new file mode 100644 index 0000000000000000000000000000000000000000..8dd3561867091a57a4e485359837508cfebbefc4 GIT binary patch literal 24575 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
RXUY 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 i{UH7+{~HA~dk+DjK^_w%5*>>G0000 +
+
+ Loading propagation data from Xatu... +
+ +
+ Xatu + Data from Xatu +
+ + +{{ end }} diff --git a/templates/slot/slot.html b/templates/slot/slot.html index 1eb843339..8e404a9fd 100644 --- a/templates/slot/slot.html +++ b/templates/slot/slot.html @@ -74,6 +74,11 @@

Bids {{ .Block.BidsCount }} {{ end }} + {{ if .XatuEnabled }} + + {{ end }} @@ -331,6 +336,15 @@

Showing {{ .Block.BidsCount }} Execution {{ end }} + {{ if .XatuEnabled }} +
+
+
+ {{ template "block_arrival" . }} +
+
+
+ {{ end }} {{ if gt .Block.PtcVotesCount 0 }}
diff --git a/types/config.go b/types/config.go index 1c88da072..060916cba 100644 --- a/types/config.go +++ b/types/config.go @@ -161,6 +161,8 @@ type Config struct { RefreshInterval time.Duration `yaml:"refreshInterval" envconfig:"MEVINDEXER_REFRESH_INTERVAL"` } `yaml:"mevIndexer"` + Xatu XatuConfig `yaml:"xatu"` + ExecutionIndexer struct { Enabled bool `yaml:"enabled" envconfig:"EXECUTIONINDEXER_ENABLED"` ParallelBlocks int `yaml:"parallelBlocks" envconfig:"EXECUTIONINDEXER_PARALLEL_BLOCKS"` @@ -278,6 +280,25 @@ type MevRelayConfig struct { BlockLimit int `yaml:"blockLimit"` } +// XatuConfig configures the optional Xatu ClickHouse data source. +type XatuConfig struct { + Enabled bool `yaml:"enabled" envconfig:"XATU_ENABLED"` + // ClickhouseDsn is the ClickHouse endpoint, e.g. "https://user:pass@clickhouse.example.com" + // (HTTP protocol) or "clickhouse://user:pass@host:9000" (native protocol). + ClickhouseDsn string `yaml:"clickhouseDsn" envconfig:"XATU_CLICKHOUSE_DSN"` + // ClickhouseCachedDsn optionally points at a response-caching proxy (e.g. chproxy). + // Queries for settled slots are routed here so identical queries across + // instances and page loads share one cached response. + ClickhouseCachedDsn string `yaml:"clickhouseCachedDsn" envconfig:"XATU_CLICKHOUSE_CACHED_DSN"` + Database string `yaml:"database" envconfig:"XATU_DATABASE"` + // NetworkName filters rows by meta_network_name; defaults to the chain name. + NetworkName string `yaml:"networkName" envconfig:"XATU_NETWORK_NAME"` + // SettleDelay is how long after slot start the ingest pipeline is assumed to + // still be receiving events. Responses for younger slots are never cached. + SettleDelay time.Duration `yaml:"settleDelay" envconfig:"XATU_SETTLE_DELAY"` + ConcurrencyLimit int `yaml:"concurrencyLimit" envconfig:"XATU_CONCURRENCY_LIMIT"` +} + type DatabaseConfig struct { Engine string `yaml:"engine" envconfig:"DATABASE_ENGINE"` Sqlite *SqliteDatabaseConfig `yaml:"sqlite"` diff --git a/types/models/slot.go b/types/models/slot.go index d0335e0ed..890046cb0 100644 --- a/types/models/slot.go +++ b/types/models/slot.go @@ -23,6 +23,7 @@ type SlotPageData struct { Badges []*SlotPageBlockBadge `json:"badges"` SlotBlocks []*SlotPageSlotBlock `json:"slot_blocks"` TracoorUrl string `json:"tracoor_url"` + XatuEnabled bool `json:"xatu_enabled"` SystemContracts []*types.SystemContract `json:"system_contracts"` TransactionDetails []*SlotPageTransaction `json:"transaction_details"` diff --git a/types/models/slot_arrival.go b/types/models/slot_arrival.go new file mode 100644 index 000000000..92118e75f --- /dev/null +++ b/types/models/slot_arrival.go @@ -0,0 +1,40 @@ +package models + +// SlotArrivalResponse is the JSON payload returned by the lazy block arrival +// callout on the slot page, built from Xatu block event observations. +type SlotArrivalResponse struct { + Slot uint64 `json:"slot"` + // Settled is false while the Xatu ingest pipeline may still be receiving + // events for this slot; such responses are never cached. + Settled bool `json:"settled"` + Observations int `json:"observations"` + Stats *SlotArrivalStats `json:"stats,omitempty"` + Clients []*SlotArrivalClient `json:"clients,omitempty"` + Continents []*SlotArrivalContinent `json:"continents,omitempty"` +} + +// SlotArrivalStats summarizes block propagation across all observing clients, +// using each client's first (deduplicated) observation. +type SlotArrivalStats struct { + UniqueClients int `json:"unique_clients"` + MinMs uint32 `json:"min_ms"` + P50Ms uint32 `json:"p50_ms"` + P90Ms uint32 `json:"p90_ms"` + MaxMs uint32 `json:"max_ms"` +} + +// SlotArrivalClient is one observing client's first block observation. +type SlotArrivalClient struct { + Name string `json:"name"` + Implementation string `json:"implementation"` + Continent string `json:"continent"` + MinMs uint32 `json:"min_ms"` + Observations int `json:"observations"` +} + +// SlotArrivalContinent aggregates first observations per continent. +type SlotArrivalContinent struct { + Code string `json:"code"` + Clients int `json:"clients"` + MinMs uint32 `json:"min_ms"` +} From f72249b8c76249da156c09f6a82ce14c6b958383 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Thu, 20 Aug 2026 12:33:27 +1000 Subject: [PATCH 02/70] fix: arrival tab autoload when preselected via url hash The page header script strips the url fragment before inline scripts run and stashes it in __doraInitialHash; check that too, or the tab shows a permanent spinner when opened via /slot/N#arrival. Claude-Session: https://claude.ai/code/session_01PqWvkAjYgCjjhtu83gJeXP --- templates/slot/arrival.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/templates/slot/arrival.html b/templates/slot/arrival.html index b9bd09f0c..b82df4e18 100644 --- a/templates/slot/arrival.html +++ b/templates/slot/arrival.html @@ -99,8 +99,10 @@ var tab = document.getElementById('arrival-tab'); if (tab) { tab.addEventListener('shown.bs.tab', loadArrival); - // load immediately when the tab is preselected via url hash - if (window.location.hash === '#arrival') loadArrival(); + // Load immediately when the tab is preselected via url hash. The page + // header script strips the fragment before this runs (to avoid the + // browser's scroll-to-fragment) and stashes it in __doraInitialHash. + if (window.location.hash === '#arrival' || window.__doraInitialHash === '#arrival') loadArrival(); } })(); From b70748a8593cbdc056fabd1522f3df525f557076 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Thu, 20 Aug 2026 13:01:33 +1000 Subject: [PATCH 03/70] feat: richer propagation tab Adds head event, libp2p gossip and engine API newPayload series next to the block event series, merged per observing node with a dumbbell track visualizing the p2p -> block -> head -> newPayload sequence on a shared time axis. Sentry names parse into group badges (EF fleet, community, corp) with shortened display names, locations show country flags, and first-seen values colorize by speed. Observations arriving more than a full slot after slot start (syncing or stalled nodes) are flagged late, listed last and excluded from the stats and histogram so a single straggler cannot distort the scale. Claude-Session: https://claude.ai/code/session_01PqWvkAjYgCjjhtu83gJeXP --- handlers/slot_arrival.go | 538 ++++++++++++++++++++++++++++++----- templates/slot/arrival.html | 378 +++++++++++++++++++++--- types/models/slot_arrival.go | 87 ++++-- 3 files changed, 859 insertions(+), 144 deletions(-) diff --git a/handlers/slot_arrival.go b/handlers/slot_arrival.go index 8f5697a41..4b61b1e26 100644 --- a/handlers/slot_arrival.go +++ b/handlers/slot_arrival.go @@ -7,6 +7,7 @@ import ( "net/http" "sort" "strconv" + "strings" "time" "github.com/gorilla/mux" @@ -69,10 +70,27 @@ func SlotArrival(w http.ResponseWriter, r *http.Request) { } } -// buildSlotArrivalData queries Xatu for the slot's block events and aggregates -// them per observing client. It returns the response and the cache timeout: -// no caching while the ingest pipeline may still receive events for the slot, -// a short timeout for recent slots and a long one for historic slots. +// arrivalNode accumulates per-node observations across both series. +type arrivalNode struct { + fullName string + implementation string + continent string + country string + countryCode string + apiMs *uint32 + p2pMs *uint32 + headMs *uint32 + npMs *uint32 + npDurMs *uint32 + npStatus string + observations int +} + +// buildSlotArrivalData queries Xatu for the slot's block observations on the +// beacon API event stream and the libp2p gossip layer, and aggregates them +// per observing node. It returns the response and the cache timeout: no +// caching while the ingest pipeline may still receive events for the slot, a +// short timeout for recent slots and a long one for historic slots. func buildSlotArrivalData(ctx context.Context, slot phase0.Slot) (*models.SlotArrivalResponse, time.Duration, error) { client := xatu.GlobalClient chainState := services.GlobalBeaconService.GetChainState() @@ -80,6 +98,61 @@ func buildSlotArrivalData(ctx context.Context, slot phase0.Slot) (*models.SlotAr settled := time.Now().After(slotTime.Add(client.SettleDelay())) + queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + nodes := map[string]*arrivalNode{} + + apiObservations, err := loadAPIArrivals(queryCtx, client, slot, slotTime, settled, nodes) + if err != nil { + return nil, -1, err + } + + p2pObservations, err := loadP2PArrivals(queryCtx, client, slot, slotTime, settled, nodes) + if err != nil { + return nil, -1, err + } + + headObservations, err := loadHeadArrivals(queryCtx, client, slot, slotTime, settled, nodes) + if err != nil { + return nil, -1, err + } + + npObservations, err := loadEngineTimings(queryCtx, client, slot, slotTime, settled, nodes) + if err != nil { + return nil, -1, err + } + + response := &models.SlotArrivalResponse{ + Slot: uint64(slot), + Settled: settled, + Observations: apiObservations, + P2PObservations: p2pObservations, + HeadObservations: headObservations, + NPObservations: npObservations, + } + + if len(nodes) > 0 { + buildArrivalAggregates(response, nodes) + } + + cacheTimeout := time.Duration(-1) + + switch { + case !settled: + // The pipeline may still be receiving events for this slot; never cache. + cacheTimeout = -1 + case time.Since(slotTime) < 30*time.Minute: + cacheTimeout = 30 * time.Second + default: + cacheTimeout = time.Hour + } + + return response, cacheTimeout, nil +} + +// loadAPIArrivals loads the beacon API block event series into nodes. +func loadAPIArrivals(ctx context.Context, client *xatu.Client, slot phase0.Slot, slotTime time.Time, settled bool, nodes map[string]*arrivalNode) (int, error) { req := &xch.ListBeaconApiEthV1EventsBlockRequest{ MetaNetworkName: &xch.StringFilter{Filter: &xch.StringFilter_Eq{Eq: client.Network()}}, Slot: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{Eq: uint32(slot)}}, @@ -92,117 +165,426 @@ func buildSlotArrivalData(ctx context.Context, slot phase0.Slot) (*models.SlotAr query, err := xch.BuildListBeaconApiEthV1EventsBlockQuery(req) if err != nil { - return nil, -1, fmt.Errorf("build query: %w", err) + return 0, fmt.Errorf("build api query: %w", err) } - queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() + rows, err := client.Query(ctx, settled, query) + if err != nil { + return 0, fmt.Errorf("api query: %w", err) + } + defer rows.Close() + + observations := 0 - rows, err := client.Query(queryCtx, settled, query) + for rows.Next() { + var row xch.BeaconApiEthV1EventsBlockRow + if err := rows.ScanStruct(&row); err != nil { + return 0, fmt.Errorf("api scan: %w", err) + } + + observations++ + + node := nodes[row.MetaClientName] + if node == nil { + node = &arrivalNode{fullName: row.MetaClientName} + nodes[row.MetaClientName] = node + } + + node.observations++ + node.implementation = row.MetaConsensusImplementation + node.continent = row.MetaClientGeoContinentCode + node.country = row.MetaClientGeoCountry + node.countryCode = row.MetaClientGeoCountryCode + + // Rows are ordered by propagation time, so the first row per node is + // its earliest observation. + if node.apiMs == nil { + ms := row.PropagationSlotStartDiff + node.apiMs = &ms + } + } + + return observations, rows.Err() +} + +// loadP2PArrivals loads the libp2p gossipsub block series into nodes. +func loadP2PArrivals(ctx context.Context, client *xatu.Client, slot phase0.Slot, slotTime time.Time, settled bool, nodes map[string]*arrivalNode) (int, error) { + req := &xch.ListLibp2PGossipsubBeaconBlockRequest{ + MetaNetworkName: &xch.StringFilter{Filter: &xch.StringFilter_Eq{Eq: client.Network()}}, + Slot: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{Eq: uint32(slot)}}, + SlotStartDateTime: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{ + Eq: uint32(slotTime.Unix()), + }}, + OrderBy: "propagation_slot_start_diff", + PageSize: 10000, + } + + query, err := xch.BuildListLibp2PGossipsubBeaconBlockQuery(req) + if err != nil { + return 0, fmt.Errorf("build p2p query: %w", err) + } + + rows, err := client.Query(ctx, settled, query) if err != nil { - return nil, -1, fmt.Errorf("query: %w", err) + return 0, fmt.Errorf("p2p query: %w", err) } defer rows.Close() - type clientAgg struct { - row xch.BeaconApiEthV1EventsBlockRow - count int + observations := 0 + + for rows.Next() { + var row xch.Libp2PGossipsubBeaconBlockRow + if err := rows.ScanStruct(&row); err != nil { + return 0, fmt.Errorf("p2p scan: %w", err) + } + + observations++ + + node := nodes[row.MetaClientName] + if node == nil { + node = &arrivalNode{fullName: row.MetaClientName} + nodes[row.MetaClientName] = node + } + + node.observations++ + + if node.continent == "" { + node.continent = row.MetaClientGeoContinentCode + node.country = row.MetaClientGeoCountry + node.countryCode = row.MetaClientGeoCountryCode + } + + if node.p2pMs == nil { + ms := row.PropagationSlotStartDiff + node.p2pMs = &ms + } + } + + return observations, rows.Err() +} + +// loadHeadArrivals loads the beacon API head event series into nodes. A head +// event marks the node adopting the block as its head via fork choice, a +// stronger signal than merely having seen the block. +func loadHeadArrivals(ctx context.Context, client *xatu.Client, slot phase0.Slot, slotTime time.Time, settled bool, nodes map[string]*arrivalNode) (int, error) { + req := &xch.ListBeaconApiEthV1EventsHeadRequest{ + MetaNetworkName: &xch.StringFilter{Filter: &xch.StringFilter_Eq{Eq: client.Network()}}, + Slot: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{Eq: uint32(slot)}}, + SlotStartDateTime: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{ + Eq: uint32(slotTime.Unix()), + }}, + OrderBy: "propagation_slot_start_diff", + PageSize: 10000, + } + + query, err := xch.BuildListBeaconApiEthV1EventsHeadQuery(req) + if err != nil { + return 0, fmt.Errorf("build head query: %w", err) + } + + rows, err := client.Query(ctx, settled, query) + if err != nil { + return 0, fmt.Errorf("head query: %w", err) } + defer rows.Close() - perClient := map[string]*clientAgg{} observations := 0 for rows.Next() { - var row xch.BeaconApiEthV1EventsBlockRow + var row xch.BeaconApiEthV1EventsHeadRow if err := rows.ScanStruct(&row); err != nil { - return nil, -1, fmt.Errorf("scan: %w", err) + return 0, fmt.Errorf("head scan: %w", err) } observations++ - // Rows are ordered by propagation time, so the first row per client is - // its earliest observation; later rows only bump the duplicate count. - if agg := perClient[row.MetaClientName]; agg != nil { - agg.count++ - } else { - perClient[row.MetaClientName] = &clientAgg{row: row, count: 1} + node := nodes[row.MetaClientName] + if node == nil { + node = &arrivalNode{fullName: row.MetaClientName} + nodes[row.MetaClientName] = node } + + node.observations++ + + if node.continent == "" { + node.continent = row.MetaClientGeoContinentCode + node.country = row.MetaClientGeoCountry + node.countryCode = row.MetaClientGeoCountryCode + } + + if node.headMs == nil { + ms := row.PropagationSlotStartDiff + node.headMs = &ms + } + } + + return observations, rows.Err() +} + +// lateThresholdMs marks observations arriving more than a full slot after +// slot start as late: typically syncing or stalled nodes whose event times +// say nothing about propagation. +const lateThresholdMs = 12000 + +// loadEngineTimings loads engine API newPayload calls observed by the +// snooper into nodes: when the consensus client handed the payload to its +// execution client, and how long the execution client took to import it. +func loadEngineTimings(ctx context.Context, client *xatu.Client, slot phase0.Slot, slotTime time.Time, settled bool, nodes map[string]*arrivalNode) (int, error) { + req := &xch.ListConsensusEngineApiNewPayloadRequest{ + MetaNetworkName: &xch.StringFilter{Filter: &xch.StringFilter_Eq{Eq: client.Network()}}, + Slot: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{Eq: uint32(slot)}}, + SlotStartDateTime: &xch.UInt32Filter{Filter: &xch.UInt32Filter_Eq{ + Eq: uint32(slotTime.Unix()), + }}, + OrderBy: "event_date_time", + PageSize: 10000, } - if err := rows.Err(); err != nil { - return nil, -1, fmt.Errorf("rows: %w", err) + query, err := xch.BuildListConsensusEngineApiNewPayloadQuery(req) + if err != nil { + return 0, fmt.Errorf("build newpayload query: %w", err) } - response := &models.SlotArrivalResponse{ - Slot: uint64(slot), - Settled: settled, - Observations: observations, - } - - if len(perClient) > 0 { - clients := make([]*models.SlotArrivalClient, 0, len(perClient)) - continents := map[string]*models.SlotArrivalContinent{} - - for _, agg := range perClient { - clients = append(clients, &models.SlotArrivalClient{ - Name: agg.row.MetaClientName, - Implementation: agg.row.MetaConsensusImplementation, - Continent: agg.row.MetaClientGeoContinentCode, - MinMs: agg.row.PropagationSlotStartDiff, - Observations: agg.count, - }) - - continent := continents[agg.row.MetaClientGeoContinentCode] - if continent == nil { - continent = &models.SlotArrivalContinent{ - Code: agg.row.MetaClientGeoContinentCode, - MinMs: agg.row.PropagationSlotStartDiff, - } - continents[agg.row.MetaClientGeoContinentCode] = continent - } + rows, err := client.Query(ctx, settled, query) + if err != nil { + return 0, fmt.Errorf("newpayload query: %w", err) + } + defer rows.Close() - continent.Clients++ - if agg.row.PropagationSlotStartDiff < continent.MinMs { - continent.MinMs = agg.row.PropagationSlotStartDiff + slotStartMs := slotTime.UnixMilli() + observations := 0 + + for rows.Next() { + var row xch.ConsensusEngineApiNewPayloadRow + if err := rows.ScanStruct(&row); err != nil { + return 0, fmt.Errorf("newpayload scan: %w", err) + } + + observations++ + + node := nodes[row.MetaClientName] + if node == nil { + node = &arrivalNode{fullName: row.MetaClientName} + nodes[row.MetaClientName] = node + } + + node.observations++ + + if node.npMs == nil { + offset := row.EventDateTime/1000 - slotStartMs + if offset < 0 { + offset = 0 } + + ms := uint32(min(offset, int64(^uint32(0)))) //nolint:gosec // clamped above + dur := uint32(min(row.DurationMs, uint64(^uint32(0)))) //nolint:gosec // clamped + + node.npMs = &ms + node.npDurMs = &dur + node.npStatus = row.Status } + } - sort.Slice(clients, func(a, b int) bool { - return clients[a].MinMs < clients[b].MinMs - }) + return observations, rows.Err() +} + +// buildArrivalAggregates fills the response's nodes, stats, histogram and +// per-continent/group summaries from the accumulated per-node observations. +// Late nodes are listed after on-time nodes and excluded from all summaries. +func buildArrivalAggregates(response *models.SlotArrivalResponse, nodes map[string]*arrivalNode) { + nodeList := make([]*models.SlotArrivalNode, 0, len(nodes)) + lateList := make([]*models.SlotArrivalNode, 0) + continents := map[string]*models.SlotArrivalContinent{} + groups := map[string][]uint32{} + + for _, node := range nodes { + // Earliest block arrival: beacon API or gossip layer. Head adoption is + // tracked separately and only used as a fallback for head-only nodes. + var minMs uint32 + + switch { + case node.apiMs != nil && node.p2pMs != nil: + minMs = min(*node.apiMs, *node.p2pMs) + case node.apiMs != nil: + minMs = *node.apiMs + case node.p2pMs != nil: + minMs = *node.p2pMs + case node.headMs != nil: + minMs = *node.headMs + case node.npMs != nil: + minMs = *node.npMs + } + + group, operator, display := parseSentryName(node.fullName) + + entry := &models.SlotArrivalNode{ + Name: display, + FullName: node.fullName, + Group: group, + Operator: operator, + Implementation: node.implementation, + Continent: node.continent, + Country: node.country, + CountryCode: node.countryCode, + MinMs: minMs, + APIMs: node.apiMs, + P2PMs: node.p2pMs, + HeadMs: node.headMs, + NPMs: node.npMs, + NPDurMs: node.npDurMs, + NPStatus: node.npStatus, + Observations: node.observations, + } - response.Clients = clients - response.Stats = &models.SlotArrivalStats{ - UniqueClients: len(clients), - MinMs: clients[0].MinMs, - P50Ms: clients[len(clients)/2].MinMs, - P90Ms: clients[len(clients)*9/10].MinMs, - MaxMs: clients[len(clients)-1].MinMs, + if minMs > lateThresholdMs { + entry.Late = true + + lateList = append(lateList, entry) + + continue + } + + nodeList = append(nodeList, entry) + + continent := continents[node.continent] + if continent == nil { + continent = &models.SlotArrivalContinent{Code: node.continent, MinMs: minMs} + continents[node.continent] = continent } - continentList := make([]*models.SlotArrivalContinent, 0, len(continents)) - for _, continent := range continents { - continentList = append(continentList, continent) + continent.Nodes++ + if minMs < continent.MinMs { + continent.MinMs = minMs } - sort.Slice(continentList, func(a, b int) bool { - return continentList[a].MinMs < continentList[b].MinMs + groups[group] = append(groups[group], minMs) + } + + sort.Slice(nodeList, func(a, b int) bool { + return nodeList[a].MinMs < nodeList[b].MinMs + }) + sort.Slice(lateList, func(a, b int) bool { + return lateList[a].MinMs < lateList[b].MinMs + }) + + if len(nodeList) == 0 { + response.Nodes = lateList + response.Stats = &models.SlotArrivalStats{LateNodes: len(lateList)} + + return + } + + response.Stats = &models.SlotArrivalStats{ + UniqueNodes: len(nodeList), + MinMs: nodeList[0].MinMs, + P50Ms: nodeList[len(nodeList)/2].MinMs, + P90Ms: nodeList[len(nodeList)*9/10].MinMs, + MaxMs: nodeList[len(nodeList)-1].MinMs, + LateNodes: len(lateList), + } + response.Histogram = buildArrivalHistogram(nodeList) + response.Nodes = append(nodeList, lateList...) + + continentList := make([]*models.SlotArrivalContinent, 0, len(continents)) + for _, continent := range continents { + continentList = append(continentList, continent) + } + + sort.Slice(continentList, func(a, b int) bool { + return continentList[a].MinMs < continentList[b].MinMs + }) + + response.Continents = continentList + + groupList := make([]*models.SlotArrivalGroup, 0, len(groups)) + + for name, values := range groups { + sort.Slice(values, func(a, b int) bool { return values[a] < values[b] }) + groupList = append(groupList, &models.SlotArrivalGroup{ + Name: name, + Nodes: len(values), + P50Ms: values[len(values)/2], }) + } + + sort.Slice(groupList, func(a, b int) bool { + return groupList[a].Nodes > groupList[b].Nodes + }) - response.Continents = continentList + response.Groups = groupList +} + +// buildArrivalHistogram buckets each node's earliest observation into a +// small fixed number of bars with a friendly bucket width. +func buildArrivalHistogram(nodes []*models.SlotArrivalNode) []*models.SlotArrivalBucket { + if len(nodes) == 0 { + return nil } - cacheTimeout := time.Duration(-1) + maxMs := nodes[len(nodes)-1].MinMs + if maxMs == 0 { + maxMs = 1 + } + + // Pick the smallest friendly bucket width that keeps the bar count <= 24. + widths := []uint32{50, 100, 200, 250, 500, 1000, 2000, 5000, 10000} + width := widths[len(widths)-1] + + for _, w := range widths { + if maxMs/w < 24 { + width = w + break + } + } + + buckets := make([]*models.SlotArrivalBucket, maxMs/width+1) + + for i := range buckets { + buckets[i] = &models.SlotArrivalBucket{ + FromMs: uint32(i) * width, //nolint:gosec // bounded by bucket count + ToMs: uint32(i+1)*width - 1, //nolint:gosec // bounded by bucket count + } + } + + for _, node := range nodes { + buckets[node.MinMs/width].Count++ + } + + return buckets +} + +// parseSentryName splits a xatu sentry name of the form +// // into a display group, operator and short +// display name. Community and corp contributoor nodes carry a hashed suffix +// that is shortened for display. +func parseSentryName(name string) (group, operator, display string) { + parts := strings.Split(name, "/") + if len(parts) != 3 { + return "other", "", name + } + + classifier, mid, node := parts[0], parts[1], parts[2] + + shortHash := func(s string) string { + h := strings.TrimPrefix(s, "hashed-") + if len(h) > 8 { + h = h[:8] + } + + return h + } switch { - case !settled: - // The pipeline may still be receiving events for this slot; never cache. - cacheTimeout = -1 - case time.Since(slotTime) < 30*time.Minute: - cacheTimeout = 30 * time.Second + case classifier == "ethpandaops": + display = strings.Replace(node, "utility-"+mid+"-", "", 1) + display = strings.TrimPrefix(display, mid+"-") + + return "ethpandaops", "ethpandaops", display + case strings.HasPrefix(classifier, "pub-"): + return "community", mid, mid + " #" + shortHash(node) + case strings.HasPrefix(classifier, "corp-"): + return "corp", mid, mid + " #" + shortHash(node) default: - cacheTimeout = time.Hour + return "other", mid, node } - - return response, cacheTimeout, nil } diff --git a/templates/slot/arrival.html b/templates/slot/arrival.html index b82df4e18..3281555e5 100644 --- a/templates/slot/arrival.html +++ b/templates/slot/arrival.html @@ -1,20 +1,207 @@ {{ define "block_arrival" }} + +
-
-
+
+
Loading propagation data from Xatu...