Observer: additive live_blocks.color (pending by default). Every chain block's mergeset marks its blues blue and its reds red, from the notification's verbose data or getBlock for chain blocks learned via virtualChainChanged; a reorg puts the removed chain blocks' mergesets back to pending. API serves color. Page: blue side blocks filled in the miner's hue at 70% with the ring, pending the faint outline, red a dark outline with a strike; tooltip and legend name the state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| observer.mjs | ||
| README.md | ||
Igneum devnet observer
Watches one Igneum node over wRPC JSON and writes what it sees to Neon, so /api/live and /live on the site can show the devnet as it runs. Node 22 or newer, no dependencies.
Run
node tools/observer/observer.mjs
Environment, every value optional:
| Variable | Default | Meaning |
|---|---|---|
IGNEUM_RPC |
ws://127.0.0.1:28610 |
The node's wRPC JSON url. 28610 is the igneum-devnet wRPC JSON port (consensus/core/src/network.rs). Start the node with --rpclisten-json=127.0.0.1:28610 or the port of your choice. |
DATABASE_URL |
read from ~/.config/igneum/env |
Neon connection string. Never commit it. |
LIVE_RETAIN_HOURS |
24 |
Hours of blocks kept in live_blocks. Older rows are deleted once a minute. |
LIVE_TABLE_PREFIX |
empty | Prefix for every table name, so a test observer against a test network can write fintest_live_* without touching the site. |
A node started by another tool may listen on gRPC only. Then run your own non-mining peer with a JSON listener, on ports that do not clash with the devnet's (gRPC 26610, P2P 26611):
vendor/igneum-node/target/release/igneumd --devnet --nodnsseed --disable-upnp \
--appdir=/tmp/igneum-obsnode --rpclisten=127.0.0.1:26640 --rpclisten-json=127.0.0.1:28640 \
--listen=127.0.0.1:26641 --connect=127.0.0.1:26611 --nologfiles
IGNEUM_RPC=ws://127.0.0.1:28640 node tools/observer/observer.mjs
What it does
- Subscribes to
blockAddedandvirtualChainChanged; pollsgetBlockDagInfo,getInfo,getConnectedPeerInfo,getSinkBlueScoreandestimateNetworkHashesPerSecondevery 2 s. - Decodes the miner address from the coinbase payload script (same bech32 variant as
crypto/addresses). The fork'svote_key_hashheader field is stored per block; the first 8 hex characters are the miner's short id on the site. engineis the miner's tag in the coinbase extra data after the node's version prefix. The node exposes no engine name over RPC, so this is null on devnet v0.- When the node refuses the hash-rate estimate (it needs a 1,000-block window) the observer reports blue work added per second over the last 10 minutes instead.
- Finality v2 (3 Oct 2026): subscribes to
FinalityLock(the node's lock event) and pollsgetFinalityCheckpointsevery 2 s andgetFinalityWeightsevery 10 s. Every checkpoint the node reports is upserted intolive_checkpoints; a checkpoint turninglockedwrites the eventcheckpoint N locked (xx% of weight, yy% of active, v votes of n voters) at block h. The weights snapshot (total, active, per key) goes intolive_state.finality. A node from before the finality layer answers the RPC with an error; the observer then logs once and skips finality.
Tables
Created on start if missing.
| Table | Rows | Columns |
|---|---|---|
live_blocks |
one per block, kept LIVE_RETAIN_HOURS |
hash, blue_score, daa_score, timestamp_ms, parents (count), parent_hashes, is_chain_block, vote_key_hash, miner_address, engine, received_at. Indexes on received_at, timestamp_ms, (vote_key_hash, received_at). |
live_state |
one row, updated every 2 s | block_count, header_count, blue_score, difficulty, hashes_per_second_estimate, peers, mempool, node_version, network, blocks_60s, blocks_per_minute (60 pairs of minute epoch ms and count), observer_started_at, updated_at |
live_events |
one per event, kept 7 days | ts, kind, text. Kinds: observer, miner_seen, miner_quiet, miner_back, peer_joined, peer_left, difficulty (step over 5%), checkpoint_locked. |
live_checkpoints |
one per checkpoint index, kept 7 days | index, hash, blue_score, daa_score, state (proposed, certified, locked), signed_weight, active_weight, total_weight, fraction_active, fraction_total, votes_seen, voters, aggregators (key hashes whose sortition proof made them aggregators), locked_at, first_seen_at, updated_at. |
live_state.finality |
jsonb, updated every 2 s | params, chain_id, next_index, finality_active, latest_locked_index, latest_locked_hash, latest_locked_blue_score, weights (total_weight, active_weight, voters, keys[] with id, blocks, voter, participation, stripped_until_daa, revealed). |
Reading it
site/api/live.mjs serves /api/live from these tables in three indexed queries. site/live.html polls it every 2 s. The site shows OFFLINE when live_state.updated_at is older than 30 s.