Lahza, specified.
The engineering description of the observatory: what is observed, how it is stored, how it is scored, how it is delivered. The site describes the promise; this document describes the machine that keeps it.
Overview
Lahza observes the reliability of Avalanche L1s from inside their networks. Three observatories — Frankfurt, Virginia, Singapore — each run full-capability nodes that join every covered chain as peers. They send nothing. They record, with their own clocks, when every user transaction is first seen, when it is included, and when it becomes final; they follow every cross-chain message leg by leg; they watch validator sets and block production. From that record the system derives a public, deterministic score per chain, an ecosystem index and an incident stream.
- Listen — nodes as peers: transactions as they arrive, blocks, validator sets, ICM traffic.
- Measure — three timestamps per transaction from our own clock; percentiles with confidence intervals; three-leg message timing.
- Tell — the public sees the dashboard, the public stream and the X feed; partners receive the evidence layer.
Architecture
| Component | Role | Notes |
|---|---|---|
observer | Full-capability AvalancheGo node per observatory, tracking covered L1s as a peer; stamps t₀/t₁/t₂ for every transaction, records blocks, validator changes and ICM messages; ships signed observations to the collector | No wallet, no keys for any chain. Partial primary sync, state sync, pruning: full capability, no history. |
collector | Ingests observations from the three observatories, validates observer signatures, writes the raw store | Idempotent by observation id |
scorer | Computes percentiles and intervals, components, score, rank bands, index, incidents | Pure function of the raw store and the methodology version |
delivery | REST API, public and evidence WebSocket streams, webhook dispatcher, X publisher, hourly on-chain commitment | All read from one event log |
dashboard | Public console; a subscriber of the public stream like anyone else | Static files; no private view |
Two invariants: every published number is a deterministic function of the raw store and a methodology version — no component may write a score by hand; and no component ever sends a transaction to, or runs on, an observed chain.
Observatories & coverage
| Observatory | Code | Role |
|---|---|---|
| Frankfurt | fra | Europe; transit corridor for western CIS traffic |
| Virginia | iad | North America |
| Singapore | sin | Asia-Pacific |
Roadmap: São Paulo, Johannesburg, Dubai, Tokyo. Coverage: every mainnet L1 is in scope; a chain is observed (our nodes are its peers), listed (registered on the P-Chain, not yet covered), private (does not admit outside nodes; observed the day it grants peering) or dormant (no user transactions in 30 days). Coverage grows in order of activity and is stated on the dashboard. There is one class of measurement; nothing is measured second-hand.
Observation protocol
t₀— a user transaction is first seen by the observer node in the chain's network; wall-clock and monotonic time recorded.t₁— the transaction is observed included in a block.t₂— the block is observed irreversibly accepted by the validator set.- Emit an
observation:{{id, chain, observatory, tx, t0, t1, t2, observer_sig, methodology}}. A transaction seen but never included within the window emitsobservation.dropped.
Finality is t₂ − t₀. Per chain, per observatory and per window the scorer reports p50, p95 and p99 with bootstrap 95% intervals. There is no cadence and no sampling: every transaction is an observation.
Data model
observation { id, chain, observatory, tx, t0, t1, t2, observer_sig, methodology } visibility: evidence
icm_trace { id, route:{from,to}, message_id, leg1_ms, leg2_ms, leg3_ms, delivered, evidence_tx } visibility: evidence
validator_obs { chain, observed_at, count, hhi, churn_24h }
block_obs { chain, observatory, height, seen_at, tx_count }
incident { id, chain, type, leg?, started, ended, duration_ms, evidence_tx, public_post } visibility: public
score { chain, computed_at, F, U, M, V, score, ci_low, ci_high, rank_band, coverage, methodology } visibility: public
index { period, median_finality_ms, chains, methodology } visibility: public
commitment { period, merkle_root, methodology, tx } visibility: public
Raw observations are immutable. Aggregates are derived and can be rebuilt from raw at any time.
Score specification
| Component | Definition | Normalisation |
|---|---|---|
| F · finality | p99 of t₂ − t₀ over 24 h, cross-observatory median | 1 at ≤ 1 s, 0 at ≥ 10 s, log-scaled between |
| U · uptime | share of 90-day windows with blocks on schedule and arriving transactions included | 1 at 100 %, 0 at 95 %, linear |
| M · message delivery | share of observed cross-chain messages delivered within 60 s, 24 h; not applicable where no relayer serves a route (weights renormalised) | 1 at 100 %, 0 at 80 %, linear |
| V · validator health | set size, stake concentration (HHI), 24 h churn | composite 0–1 against public thresholds; set size published beside the score |
Scores carry a 95% interval; chains with overlapping intervals share a rank band. Weights and thresholds are part of the methodology version; a change is a new version, announced before it takes effect. The dashboard shows a chain as healthy at score ≥ 93, degraded ≥ 87, incident below.
Three-leg ICM
Every cross-chain message observed on a covered chain is timed in three legs: leg 1 source finality, leg 2 relayer pickup, leg 3 destination delivery. A message undelivered after 90 s opens icm.stalled attributed to the leg that broke. Lahza never operates a relayer.
Incident model
| Type | Opens when | Closes when |
|---|---|---|
finality.degraded | p99 above 5× the 30-day baseline, sustained across a window | 5 clean minutes |
uptime.incident | block production stalls, or arriving transactions are not included for 60 s as seen from ≥ 2 observatories | 5 clean minutes |
icm.stalled | observed message undelivered after 90 s | delivery, or 24 h expiry |
validators.changed | set size or HHI shifts beyond threshold | informational; closes immediately |
region.gap | one observatory's p50 drifts > 25 % from the others | 5 clean minutes |
Every incident carries an evidence transaction — a real user transaction on the chain — and, on open and on recovery, a public post on X.
Proof of observation
Every observation names a real transaction; that proves what happened. Every hour, the Merkle root of that hour's raw observations is written to the chain in one transaction, together with the methodology version in force; that proves the record was never altered. Each monthly index value is committed the same way. This is the only thing Lahza ever writes on-chain.
REST API
Base https://api.lahza.xyz/v1. JSON, UTC, milliseconds. Public, no key: /chains, /chains/{{id}}, /chains/{{id}}/incidents, /badge/{{id}}.svg, /index, /methodology. Evidence, partnership key (Authorization: Bearer): /chains/{{id}}/finality, /icm/routes. See the API page for samples.
WebSocket
Two streams from one event log. wss://…/v1/public — no key: score.updated, incident.opened, incident.recovered, index.updated; the dashboard is a subscriber. wss://…/v1/stream?chain={{id}} — partnership key, one subscription per chain: every observation and trace for that chain. Heartbeat every 15 s; reconnect with ?since=<event id> to replay a gap.
Webhook
JSON POST per event. Headers X-Lahza-Event, X-Lahza-Signature: sha256=HMAC(body, secret). Non-2xx responses retried 5 times with exponential backoff (1 m → 30 m). Events are idempotent by id; stream and webhook share ids.
Public layer
Two layers. Public, for everyone, no key: the dashboard, the public stream and API (scores, incidents with evidence transactions, the index, badges) and the incident feed on X. Evidence, for an ecosystem through its foundation's partnership: raw observations, three-leg timings, region and incident detail, history, webhooks, signed scorecards — provisioned to L1 teams by the foundation. Raw historical data is licensed to institutions. The score is never sold.
Operations
- Observer node. AvalancheGo with partial primary-network sync (P-Chain only), state sync for tracked L1s, pruning. Full capability, no history: an observer needs the present, not the past. Per observatory today: 2 vCPU · 8 GB · 150 GB NVMe covers the most active 25 L1s; disk and memory grow with coverage, the machine class does not change.
- Clocks. chrony against the provider's time service; offset under 1 ms; cross-observatory consistency checked continuously.
- Collector, scorer, delivery. Run on the Frankfurt observatory host today; separate host when load requires.
- Nothing on-chain but the commitment. One funded wallet exists in the whole system: on the C-Chain, for the hourly Merkle commitment. No wallet exists on any observed chain.
Retention
Raw observations: 90 days. Hourly aggregates: indefinitely. Incidents and evidence transactions: indefinitely. Methodology versions and the raw store are sufficient to rebuild every published number.
Deployment
The site is static: HTML, CSS and JavaScript with Three.js from a CDN; no build step. Upload the folder to Cloudflare Pages; _headers sets security headers. The dashboard reads demo data until it is pointed at the public stream; switch by setting DEMO=false in assets/dashboard.js and filling window.ES_CH from the subscriber with the same objects.
Build order
- One observatory. Frankfurt node, partial sync, five active L1s; first signed observations in the raw store. Proves t₀/t₁/t₂ from inside the network.
- Three observatories, 25 L1s. Virginia and Singapore online; coverage by activity. Proves cross-observatory consistency.
- Scorer, v1.0 frozen. Percentiles with intervals, rank bands, components, score, incidents, index. From here on scores are published. This is the step that changes the foundation conversation: a number anyone can recompute.
- Delivery. REST, public and evidence streams, webhooks, X publisher, hourly commitment on C-Chain.
- Dashboard on the stream.
DEMO=false. - Coverage growth. Disk and memory per observatory; more L1s, in order of activity, until every active mainnet L1 is observed.
Glossary
| Term | Meaning |
|---|---|
| observation | one user transaction with its t₀/t₁/t₂ timestamps from one observatory |
| observatory | a region running our observer node |
| finality | t₂ − t₀; what a user waits |
| incident | a threshold crossing with a start, an end and evidence |
| evidence tx | the real user transaction behind an observation or incident |
| leg | one of the three timed stages of a cross-chain message |
| region gap | (max − min) / median of p50 across observatories |
| index | monthly median measured finality across every observed L1 |
| commitment | the hourly Merkle root of raw observations, written on-chain |