Data Availability metrics and monitoring tool for evstack using Celestia DA. This tool monitors EVM block headers in real-time, queries ev-node for DA submission information, and verifies blob data on Celestia.
- Real-time EVM block header streaming
- Automatic verification of header and data blobs on Celestia
- Retry logic with exponential backoff for pending DA submissions
- Prometheus metrics for tracking unverified block ranges
- Streaming block verification of every new block
- Account balance monitoring via Celestia consensus RPC with automatic failover
# Clone the repository
git clone https://github.com/evstack/ev-metrics.git
cd ev-metrics
# Install dependencies
go mod download
# Build the binary
go build -o ev-metricsThe monitor command streams EVM block headers and verifies DA submission on Celestia:
./ev-metrics monitor \
--header-namespace testnet_header \
--data-namespace testnet_dataThe Prometheus metrics server always runs. Use --port to change the port:
./ev-metrics monitor \
--header-namespace collect_testnet_header \
--data-namespace collect_testnet_data \
--port 2112Metrics will be available at http://localhost:2112/metrics
Required:
--header-namespace: Header namespace (e.g. testnet_header )--data-namespace: Data namespace (e.g. testnet_data )
Optional:
--evnode-addr: ev-node Connect RPC address (default:http://localhost:7331)--evm-ws-url: EVM client WebSocket URL (default:ws://localhost:8546)--evm-rpc-url: EVM client JSON-RPC URL for health checks (optional, enables JSON-RPC monitoring)--celestia-url: Celestia DA JSON-RPC URL (default:http://localhost:26658)--celestia-token: Celestia authentication token (optional), sent as aBearertoken on all Celestia requests, including block timestamp lookups. Required when the celestia-node has auth enabled (the default).--duration: Duration in seconds to stream (0 = infinite)--chain-id: Chain identifier for metrics labels (default: "testnet")--port: HTTP server port for metrics (default: 2112)--jsonrpc-scrape-interval: JSON-RPC health check scrape interval in seconds (default: 10)--reference-node: Reference node RPC endpoint URL (sequencer) for drift monitoring--full-nodes: Comma-separated list of full node RPC endpoint URLs for drift monitoring--polling-interval: Polling interval in seconds for checking node block heights (default: 10)--balance.addresses: Comma-separated Celestia addresses to monitor (enables balance checking)--balance.consensus-rpc-urls: Comma-separated consensus RPC URLs for balance queries (required if balance.addresses is set)--balance.scrape-interval: Balance check scrape interval in seconds (default: 30)--verifier.workers: Number of concurrent workers for block verification (default: 50)--health-max-header-age: Seconds without a new block header after which/healthreturns 503 (default: 0, which disables the age check so only the subscription state matters). See Health Endpoint.--verbose: Enable verbose (debug level) logging (default: false)--log-format: Log output format,console(human-readable) orjson(one JSON object per line, for Loki/ELK ingestion) (default:console)
./ev-metrics monitor \
--header-namespace collect_testnet_header \
--data-namespace collect_testnet_data \
--evnode-addr "http://my-evnode:7331" \
--evm-ws-url "ws://my-evnode:8546" \
--celestia-url "http://my-celestia:26658"Enable JSON-RPC request duration monitoring by providing the --evm-rpc-url flag:
./ev-metrics monitor \
--header-namespace collect_testnet_header \
--data-namespace collect_testnet_data \
--evm-ws-url "ws://localhost:8546" \
--evm-rpc-url "http://localhost:8545" \
--jsonrpc-scrape-interval 10This will periodically send eth_blockNumber JSON-RPC requests to monitor node health and response times.
Monitor native token balances for one or more Celestia addresses:
./ev-metrics monitor \
--header-namespace collect_testnet_header \
--data-namespace collect_testnet_data \
--balance.addresses "celestia1abc...,celestia1def..." \
--balance.consensus-rpc-urls "https://rpc.celestia.org,https://rpc-mocha.pops.one" \
--balance.scrape-interval 30This will query account balances every 30 seconds with automatic failover between RPC endpoints.
https:// consensus endpoints are dialed over TLS (port 443 if none is given); http:// and bare host:port endpoints use plaintext gRPC.
The following metrics are exposed:
Blocks without transactions have only their header blob verified (type="header"). Blocks with transactions have both their header blob (type="header") and data blob (type="data") verified, and each outcome is recorded under its own type. Tx blocks now count under type="header" too, so header submission counts (submission_*, unsubmitted_*, DA height and duration) rise after upgrading. Each transaction block costs one extra Celestia request for its header blob. If retries are exhausted, only the blob types that are still unverified are recorded as missing.
- Type: Gauge
- Labels:
chain_id,blob_type,range_id - Description: Start block height of unverified block ranges
- Type: Gauge
- Labels:
chain_id,blob_type,range_id - Description: End block height of unverified block ranges
- Type: Gauge
- Labels:
chain_id,blob_type - Description: Total number of unsubmitted blocks
- Type: Summary
- Labels:
chain_id,type - Description: DA blob submission duration from block creation to DA availability, over a 60-second rolling window. It only contains real observations, so the quantiles are
NaNwhile no submission has been verified in the last 60 seconds. Useev_metrics_last_submission_duration_secondsto see the latest value during quiet periods.
- Type: Gauge
- Labels:
chain_id,type - Description: Duration from block creation to DA availability of the most recently verified submission. It keeps its value until the next submission is verified and is absent until the first one.
- Type: Gauge
- Labels:
chain_id,type - Description: Latest DA height for header and data submissions
- Type: Counter
- Labels:
chain_id,type - Description: Total number of DA submission attempts (both successful and failed). Counted once per blob type per verification round, not once per RPC attempt. A block whose retries run out and which is re-verified after the cooldown starts a new round.
- Type: Counter
- Labels:
chain_id,type - Description: Total number of failed DA submission attempts. Counted once per blob type per verification round, not once per RPC attempt. A block whose retries run out and which is re-verified after the cooldown starts a new round.
- Type: Gauge
- Labels:
chain_id,type - Description: Timestamp of the last DA submission attempt (Unix timestamp)
- Type: Gauge
- Labels:
chain_id,type - Description: Timestamp of the last successful DA submission (Unix timestamp)
- Type: Histogram
- Labels:
chain_id - Description: Time between consecutive blocks with histogram buckets for accurate SLO calculations
- Buckets: 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, 1.5, 2 seconds
- Type: Summary
- Labels:
chain_id - Description: Block time with percentiles over a 60-second rolling window
- Note: Will show NaN when no blocks have been received in the last 60 seconds
- Type: Gauge
- Labels:
chain_id - Description: Seconds since last block was received. Use this metric for alerting on stale blocks.
- Alerting: Alert when this value exceeds 60 seconds to detect block production issues before summary metrics show NaN
- Type: Gauge
- Labels:
chain_id,quantile - Description: SLO thresholds for block time
- Values:
0.5: 2.0s0.9: 3.0s0.95: 4.0s0.99: 5.0s
- Type: Histogram
- Labels:
chain_id - Description: Delay between block creation (
header.Time) and reception with histogram buckets. Only headers received from the subscription are observed (not backfilled ones), and negative values from clock skew are recorded as 0.header.Timehas 1-second resolution, so the value is only accurate to about ±1s. - Buckets: 0.1, 0.25, 0.5, 1.0, 2.0, 3.0, 5.0, 10.0, 15.0, 30.0, 60.0 seconds
- Type: Gauge
- Labels:
chain_id,quantile - Description: SLO thresholds for block receive delay
- Values:
0.5: 1.0s0.9: 3.0s0.95: 5.0s0.99: 10.0s
- Type: Gauge
- Labels:
chain_id - Description: Number of blocks waiting in the verifier queue, sampled every 10 seconds. A value that stays near the queue capacity (2 x
--verifier.workers) means the verifier is falling behind.
- Type: Gauge
- Labels:
chain_id - Description: Blocks between their first verification attempt and being verified or running out of retries, including blocks waiting on a retry timer. A block re-queued after the cooldown is counted again.
- Type: Counter
- Labels:
chain_id - Description: Number of times the block header WebSocket subscription was re-established after dropping.
- Type: Gauge
- Labels:
chain_id - Description: Unix timestamp in seconds of the last block header received from the subscription (backfilled headers are not counted). It is 0 until the first header arrives.
- Type: Counter
- Labels:
chain_id - Description: Blocks enqueued after a gap in the header subscription (for example after a reconnect).
- Type: Counter
- Labels:
chain_id - Description: Blocks skipped because a subscription gap was larger than the 1000-block backfill cap. The oldest blocks are dropped.
GET /health returns 200 OK, or 503 with a short plain-text reason when:
- the block header subscription has not been established yet (
not ready), - the subscription is down (it is re-established automatically with backoff), or
--health-max-header-ageis greater than 0 and no header has arrived within that many seconds (measured from the first subscription until a header arrives).
Use /health as a readiness probe, not a liveness probe. With the age check enabled, a halted chain also fails it, and restarting the monitor would not fix that.
When --evm-rpc-url is provided:
- Type: Histogram
- Labels:
chain_id - Description: Duration of JSON-RPC requests to the EVM node (enabled when
--evm-rpc-urlis provided) - Buckets: 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 seconds
- Type: Gauge
- Labels:
chain_id,quantile - Description: SLO thresholds for JSON-RPC request duration (enabled when
--evm-rpc-urlis provided) - Values:
0.5: 0.2s0.9: 0.35s0.95: 0.4s0.99: 0.5s
When --reference-node and --full-nodes are provided:
Endpoint labels (here and in the balance metrics below) and logs show only scheme://host[:port], so credentials in endpoint URLs (userinfo, path, query) are not exposed. Endpoints that redact to the same value get a #<index> suffix to stay distinct.
- Type: Gauge
- Labels:
chain_id,endpoint - Description: Current block height of the reference endpoint (sequencer)
- Type: Gauge
- Labels:
chain_id,endpoint - Description: Current block height of target endpoints (operator nodes)
- Type: Gauge
- Labels:
chain_id,target_endpoint - Description: Block height difference between reference and target endpoints (positive = target behind, negative = target ahead)
When --balance.addresses and --balance.consensus-rpc-urls are provided:
- Type: Gauge
- Labels:
chain_id,address,denom - Description: Native token balance for Celestia addresses
- Type: Gauge
- Labels:
chain_id,endpoint - Description: Consensus RPC endpoint availability status (1.0 = available, 0.0 = unavailable)
- Type: Counter
- Labels:
chain_id,endpoint,error_type - Description: Total number of consensus RPC endpoint errors by type
Use the --verbose flag to set the application log level to debug. It is the single way to get debug output: per-block, per-poll (drift, balance, JSON-RPC health checks) and per-verification success messages. Warnings and errors are always logged.