From ac5fcdd06713a6e6323f84993ca9f3189fae5340 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Sun, 26 Jul 2026 02:35:18 +0200 Subject: [PATCH] xchain fairness v3: unify maxLatencyMs=2h + buckets to 90min across 4 e2e harnesses; document Axelar finality wait alongside CCIP --- benchmarks/axelar-gmp-latency.yml | 1 + benchmarks/cross-chain-messaging-latency.yml | 8 ++++---- benchmarks/layerzero-message-latency.yml | 2 +- harnesses/axelar-gmp-latency/cmd/script/main.go | 10 +++++++--- .../axelar-gmp-latency/cmd/script/metrics.go | 13 ++++++++----- .../cmd/script/main.go | 10 +++++++--- .../cmd/script/metrics.go | 9 +++++++-- .../cmd/script/main.go | 10 ++++++---- .../cmd/script/metrics.go | 12 ++++++++---- paraswap.png | Bin 0 -> 13741 bytes 10 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 paraswap.png diff --git a/benchmarks/axelar-gmp-latency.yml b/benchmarks/axelar-gmp-latency.yml index d95c2628..f8de3a2b 100644 --- a/benchmarks/axelar-gmp-latency.yml +++ b/benchmarks/axelar-gmp-latency.yml @@ -53,6 +53,7 @@ methodology: - "Cosmos block-time quantisation: Axelar itself has 6-second Tendermint blocks, so sub-6s latencies always quantise to 0 or 6 seconds. This shows up as a visible floor on `call_confirm` for fast source chains and is a property of Axelar's own consensus, not our measurement." - "No canary transactions: this bench observes organic Axelar GMP traffic (Squid Router, ITS, Interchain Amplifier apps). No wallet is funded, no gas is paid." - "Scope: Axelar-supported source chains with mainnet traffic in the observation window. Axelar covers all major EVM L1s and L2s plus a large Cosmos set (Osmosis, Injective, Sei, Celestia, Kava) that no other bench in our cross-chain messaging cluster observes. Notable gap: Axelar mainnet does not support Solana yet." + - "Fair-comparison caveat: Axelar validators wait for source-chain confirmations before signing (about 96 blocks / 20 minutes on Ethereum), so Ethereum-source lanes are structurally slower than pre-finality signers (LayerZero, Hyperlane) but still faster than Chainlink CCIP which waits for full Casper FFG finality. The meta-bench (cross-chain-messaging-latency) documents this ranking." - "Coverage caveat: This is Axelar-exclusive Cosmos coverage. Comparing raw numbers to networks that only cover EVM (LayerZero, CCIP) is only meaningful on shared EVM corridors; the meta-bench view (cross-chain-messaging-comparison) enforces this by only showing cells where both protocols support the corridor." findings: diff --git a/benchmarks/cross-chain-messaging-latency.yml b/benchmarks/cross-chain-messaging-latency.yml index 2cd9ccb7..f8cf4e86 100644 --- a/benchmarks/cross-chain-messaging-latency.yml +++ b/benchmarks/cross-chain-messaging-latency.yml @@ -48,7 +48,7 @@ abstract: | methodology: - "Data sources: each protocol's own public message tracker, polled every 60 seconds. LayerZero via scan.layerzero-api.com/v1/messages/latest, Chainlink CCIP via api.ccip.chain.link/v2/messages, Hyperlane via api.hyperlane.xyz/v1/graphql, Axelar via api.axelarscan.io/gmp/searchGMP." - "Latency definition: wall-clock seconds from source-chain transaction submission to destination-chain execution, computed from each tracker's own source and destination block timestamps. All four protocols expose the same shape (source_ts, destination_ts); the harness converts to milliseconds and records a per-source-chain histogram." - - "Fairness caveat: Chainlink CCIP waits for source-chain finality (12-15 min on Ethereum) before its DON commits, so ETH-source lanes look structurally slower on CCIP for security reasons, not performance. The other three (LayerZero, Hyperlane, Axelar) sign pre-finality by default." + - "Fairness caveat on Ethereum-source lanes: CCIP AND Axelar both wait for source-chain finality before signing (CCIP via Casper FFG, Axelar via ~96 confirmations) so ETH-source lanes structurally add 12-20 min. LayerZero and Hyperlane sign pre-finality by default. L2 and non-EVM lanes are more directly comparable because they finalize quickly." - "Wormhole excluded from this side-by-side: wormholescan only exposes source-tx-to-Guardian-quorum (signing) latency, not destination-side execution. Including Wormhole would compare signing time (about 18s) against end-to-end delivery time (about 30-55s) and make Wormhole look 3x faster than it actually is. Wormhole gets its own dedicated bench at wormhole-vaa-latency with the same signing-time methodology." - "Aggregation: the number on this page is the p50/p90/p99 across ALL corridors each protocol observes over the last 24 hours. Per-corridor breakdown is on each dedicated bench (layerzero-message-latency, chainlink-ccip-latency, hyperlane-message-latency, axelar-gmp-latency)." - "Coverage: LayerZero covers 30+ chains including Solana. Chainlink CCIP and Hyperlane cover most EVMs; CCIP added Solana Oct 2025. Axelar covers all major EVMs plus a Cosmos set (Osmosis, Injective, Sei, Celestia) but not Solana. Aggregation is honest because each protocol is aggregated over its own footprint; per-corridor drill-down lives on each dedicated bench." @@ -57,14 +57,14 @@ methodology: findings: - "{{best_name}} currently leads end-to-end cross-chain delivery at {{best_p50}} (p50 aggregate across all corridors, 24h) across the four measured protocols." - - "Pre-finality signing (LayerZero, Hyperlane, Axelar) delivers in the seconds-to-tens-of-seconds range. Chainlink CCIP sits in the minutes range because it waits for source-chain finality before its DON commits. That is a security tradeoff visible directly in the data." + - "Pre-finality signers (LayerZero, Hyperlane) deliver in the seconds-to-tens-of-seconds range. Chainlink CCIP and Axelar both add source-chain finality wait on Ethereum-source lanes (CCIP waits for Casper FFG epoch, Axelar waits for ~96 confirmations), so their aggregate p50 sits higher when Ethereum-source volume dominates their footprint. That is a security tradeoff visible directly in the data." - "Per-corridor variance is large. The aggregate here is a headline; the individual bench for each protocol shows the per-source-chain breakdown that answers what you would feel in production." faq: - q: "Why isn't Wormhole in this comparison?" a: "Wormhole's public tracker (wormholescan) only exposes signing time, source-tx-to-Guardian-quorum. It does not consistently expose destination-side execution timestamps. Including Wormhole here would compare its signing time (about 18s aggregate) against LayerZero/CCIP/Hyperlane/Axelar end-to-end delivery (about 30s-20min) and make Wormhole look 3x faster than it actually is at delivery. Wormhole gets its own dedicated bench at wormhole-vaa-latency with correct signing-only methodology and full disclosure." - - q: "Why is Chainlink CCIP so much slower?" - a: "CCIP intentionally waits for source-chain finality before its DON commits a merkle root. On Ethereum, that means Casper FFG finalization (12-15 minutes typical, up to 25 minutes in adverse epochs). LayerZero, Hyperlane and Axelar all sign pre-finality by default, so their end-to-end delivery excludes that wait. This is a security tradeoff, not a performance bug." + - q: "Why is Chainlink CCIP so much slower on Ethereum-source?" + a: "CCIP waits for source-chain finality before its DON commits a merkle root. On Ethereum, that means Casper FFG finalization (12-15 minutes typical, up to 25 minutes in adverse epochs). LayerZero and Hyperlane both sign pre-finality by default, so their end-to-end delivery excludes that wait. Axelar validators DO wait for source-chain confirmations on Ethereum (about 96 blocks, roughly 20 minutes), so Axelar shows a similar ETH-source slowdown, though smaller than CCIP because Axelar counts source blocks rather than Casper FFG epochs." - q: "Where is Circle CCTP?" a: "Circle's Iris API does not expose a list-recent endpoint, so passive polling requires maintaining nonce state via on-chain event scanning on all ~10 CCTP source chains plus Solana. That is on the roadmap but structurally more expensive than the four protocols measured here (5-7 days of work vs 1-2 days each)." - q: "Is comparing these four raw numbers actually fair?" diff --git a/benchmarks/layerzero-message-latency.yml b/benchmarks/layerzero-message-latency.yml index 5d3e16b7..6f9d8b0b 100644 --- a/benchmarks/layerzero-message-latency.yml +++ b/benchmarks/layerzero-message-latency.yml @@ -54,7 +54,7 @@ methodology: - "Guard: samples with delta `< 0` or `> 30 min` are dropped (clock skew, stuck messages re-executed later)." - "No canary transactions: this bench observes organic LayerZero traffic (~250 messages/hour network-wide). No wallet is funded, no gas is paid." - "Scope: LayerZero-supported source chains with mainnet traffic in the observation window. LayerZero exposes many exotic chains (orderly, flare, ape, robinhood) that show up as separate rows once volume is non-trivial; low-traffic sources will have wide error bars." - - "Fair-comparison caveat: LayerZero DVNs sign pre-finality by default, so end-to-end delivery excludes the source-chain finality wait that finality-aware networks (Chainlink CCIP) impose. Comparing raw numbers to CCIP on ETH-source lanes is not apples-to-apples; the meta-bench view (cross-chain-messaging-comparison) exposes a finality-normalized column." + - "Fair-comparison caveat: LayerZero DVNs sign pre-finality by default, so end-to-end delivery excludes the source-chain finality wait that finality-aware networks (Chainlink CCIP, Axelar on Ethereum-source) impose. Comparing raw numbers to those on ETH-source lanes is not apples-to-apples; the meta-bench (cross-chain-messaging-latency) documents the tradeoff." findings: - "{{best_name}} currently leads LayerZero end-to-end delivery at {{best_p50}} (source-tx to destination execution p50, 24h) across measured source chains." diff --git a/harnesses/axelar-gmp-latency/cmd/script/main.go b/harnesses/axelar-gmp-latency/cmd/script/main.go index 2faffb35..a3652e99 100644 --- a/harnesses/axelar-gmp-latency/cmd/script/main.go +++ b/harnesses/axelar-gmp-latency/cmd/script/main.go @@ -49,9 +49,13 @@ const ( requestTimeout = 15 * time.Second dedupeCacheMaxLen = 10_000 metricsListenAddr = ":2112" - // 30 min hard ceiling — Axelar GMP typically < 60s e2e; > 30 min - // is either a stuck message or clock skew. - maxLatencyMs = 1_800_000 + // 2h hard ceiling. Aligned with the other e2e cross-chain messaging + // benches (CCIP, LayerZero, Hyperlane) so the drop threshold is + // consistent across the cross-chain-messaging-latency meta-bench. + // Axelar validators wait for source-chain finality on EVM (Ethereum + // ~20 min), so the tail legitimately reaches 30-60 min on ETH-source + // lanes and must not be trimmed. + maxLatencyMs = 7_200_000 // 2 hours ) type axelarCall struct { diff --git a/harnesses/axelar-gmp-latency/cmd/script/metrics.go b/harnesses/axelar-gmp-latency/cmd/script/metrics.go index 69794222..2c8bfe0b 100644 --- a/harnesses/axelar-gmp-latency/cmd/script/metrics.go +++ b/harnesses/axelar-gmp-latency/cmd/script/metrics.go @@ -13,15 +13,18 @@ import ( // executed (`time_spent.total`). Analogue of LayerZero/CCIP/Hyperlane // end-to-end delivery. // -// Both bucketed to 30 min ceiling; confirm typically finishes in -// 10-30s, e2e in 20-60s. The tail catches Cosmos block-time quantisation -// (~6s floor) and validator downtime spikes. +// Buckets aligned with the other three e2e cross-chain messaging +// benches (CCIP, LayerZero, Hyperlane) up to 90 min so the meta-bench +// (cross-chain-messaging-latency) compares equivalent bucket +// resolution across all four protocols. Axelar validators wait for +// source-chain finality on Ethereum (~20 min), so ETH-source lanes +// legitimately reach 30-40 min end-to-end and must not be capped. var ( axelarConfirmLatencyMs = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "axelar_gmp_confirm_latency_milliseconds", Help: "Axelar GMP source tx to validator quorum confirmation latency (ms), from time_spent.call_confirm.", - Buckets: []float64{2_000, 5_000, 10_000, 15_000, 20_000, 30_000, 45_000, 60_000, 90_000, 120_000, 180_000, 300_000, 600_000, 900_000, 1_200_000, 1_800_000}, + Buckets: []float64{5_000, 15_000, 30_000, 60_000, 120_000, 180_000, 300_000, 600_000, 900_000, 1_200_000, 1_800_000, 2_400_000, 3_000_000, 3_600_000, 4_500_000, 5_400_000}, }, []string{"source_chain"}, ) @@ -30,7 +33,7 @@ var ( prometheus.HistogramOpts{ Name: "axelar_gmp_e2e_latency_milliseconds", Help: "Axelar GMP end-to-end delivery latency (ms), from source tx to destination execution, via time_spent.total.", - Buckets: []float64{2_000, 5_000, 10_000, 15_000, 20_000, 30_000, 45_000, 60_000, 90_000, 120_000, 180_000, 300_000, 600_000, 900_000, 1_200_000, 1_800_000}, + Buckets: []float64{5_000, 15_000, 30_000, 60_000, 120_000, 180_000, 300_000, 600_000, 900_000, 1_200_000, 1_800_000, 2_400_000, 3_000_000, 3_600_000, 4_500_000, 5_400_000}, }, []string{"source_chain", "dest_chain"}, ) diff --git a/harnesses/hyperlane-message-latency/cmd/script/main.go b/harnesses/hyperlane-message-latency/cmd/script/main.go index 83f8eda6..56ff7dfd 100644 --- a/harnesses/hyperlane-message-latency/cmd/script/main.go +++ b/harnesses/hyperlane-message-latency/cmd/script/main.go @@ -49,9 +49,13 @@ const ( requestTimeout = 15 * time.Second dedupeCacheMaxLen = 10_000 metricsListenAddr = ":2112" - // 30 min hard ceiling — Hyperlane relayers are typically < 2 min - // end-to-end; > 30 min is either a stuck message or clock skew. - maxLatencyMs = 1_800_000 + // 2h hard ceiling. Aligned with the other e2e cross-chain messaging + // benches (CCIP, LayerZero, Axelar) so the drop threshold is + // consistent across the cross-chain-messaging-latency meta-bench. + // Hyperlane relayers are typically < 2 min but real stuck deliveries + // have been observed at 30-90 min; we want the tail preserved for + // meaningful p99 comparison. + maxLatencyMs = 7_200_000 // 2 hours ) type hyperlaneMessage struct { diff --git a/harnesses/hyperlane-message-latency/cmd/script/metrics.go b/harnesses/hyperlane-message-latency/cmd/script/metrics.go index 578fe5c8..f008cd2b 100644 --- a/harnesses/hyperlane-message-latency/cmd/script/metrics.go +++ b/harnesses/hyperlane-message-latency/cmd/script/metrics.go @@ -14,13 +14,18 @@ import ( // theoretical protocol floor. // // Emitted in MILLISECONDS to match the site's `unit: ms` display -// convention. Buckets 2s-30min. +// convention. Buckets aligned with the other three e2e cross-chain +// messaging benches (CCIP, LayerZero, Axelar) so the meta-bench +// (cross-chain-messaging-latency) compares equivalent bucket +// resolution. Hyperlane relayers are typically < 2 min but the +// permissionless relayer model produces real 30-90 min stalls on +// under-served corridors, and the tail must be preserved for honest p99. var ( hyperlaneLatencyMs = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "hyperlane_message_latency_milliseconds", Help: "End-to-end delivery latency (ms) for a Hyperlane message, from source send to destination delivery, labeled by source + destination chain.", - Buckets: []float64{2_000, 5_000, 10_000, 15_000, 20_000, 30_000, 45_000, 60_000, 90_000, 120_000, 180_000, 300_000, 600_000, 900_000, 1_200_000, 1_800_000}, + Buckets: []float64{5_000, 15_000, 30_000, 60_000, 120_000, 180_000, 300_000, 600_000, 900_000, 1_200_000, 1_800_000, 2_400_000, 3_000_000, 3_600_000, 4_500_000, 5_400_000}, }, []string{"source_chain", "dest_chain"}, ) diff --git a/harnesses/layerzero-message-latency/cmd/script/main.go b/harnesses/layerzero-message-latency/cmd/script/main.go index 11a1c7ec..5f7a4f6d 100644 --- a/harnesses/layerzero-message-latency/cmd/script/main.go +++ b/harnesses/layerzero-message-latency/cmd/script/main.go @@ -54,10 +54,12 @@ const ( requestTimeout = 15 * time.Second dedupeCacheMaxLen = 10_000 metricsListenAddr = ":2112" - // Guard against clock skew and stuck messages. LayerZero rarely - // exceeds 15 min end-to-end; anything beyond 30 min is almost - // certainly bad data. - maxLatencyMs = 1_800_000 // 30 min + // Guard against clock skew and stuck messages. Aligned with the + // other e2e cross-chain messaging benches (CCIP, Hyperlane, Axelar) + // at 2 hours so the drop threshold is consistent across the + // cross-chain-messaging-latency meta-bench. Any single message + // beyond 2h is almost certainly a stuck/re-delivered artifact. + maxLatencyMs = 7_200_000 // 2 hours ) type lzTx struct { diff --git a/harnesses/layerzero-message-latency/cmd/script/metrics.go b/harnesses/layerzero-message-latency/cmd/script/metrics.go index 734bfbf4..e719e381 100644 --- a/harnesses/layerzero-message-latency/cmd/script/metrics.go +++ b/harnesses/layerzero-message-latency/cmd/script/metrics.go @@ -13,15 +13,19 @@ import ( // + executor delivery), not just protocol signing latency. // // Emitted in MILLISECONDS to match the site's `unit: ms` display -// convention. Buckets extend to 30 min: LayerZero rarely runs longer -// than a few minutes end-to-end (fewer finality waits than CCIP), but -// the tail catches DVN misconfig or destination-chain congestion. +// convention. Buckets aligned with the other three e2e cross-chain +// messaging benches (CCIP, Hyperlane, Axelar) — up to 90 min — so +// the cross-chain-messaging-latency meta-bench compares equivalent +// bucket resolution across all four protocols. LayerZero typically +// runs a few minutes end-to-end but real stuck deliveries at 30-60 +// min have been observed on low-liquidity DVN configurations, and +// the tail must be preserved for honest p99. var ( lzLatencyMs = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "lz_message_latency_milliseconds", Help: "End-to-end delivery latency (ms) for a LayerZero message, from source-chain block to destination-chain block, labeled by source + destination chain.", - Buckets: []float64{2_000, 5_000, 10_000, 15_000, 20_000, 30_000, 45_000, 60_000, 90_000, 120_000, 180_000, 300_000, 600_000, 900_000, 1_200_000, 1_800_000}, + Buckets: []float64{5_000, 15_000, 30_000, 60_000, 120_000, 180_000, 300_000, 600_000, 900_000, 1_200_000, 1_800_000, 2_400_000, 3_000_000, 3_600_000, 4_500_000, 5_400_000}, }, []string{"source_chain", "dest_chain"}, ) diff --git a/paraswap.png b/paraswap.png new file mode 100644 index 0000000000000000000000000000000000000000..849a29f7445c3d85014f84956af25c999db15b5b GIT binary patch literal 13741 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