diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5cd62109..32aeab56d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,11 @@ jobs: run: bash tools/ci/no-secrets-check.sh --self-test && bash tools/ci/no-secrets-check.sh - name: faucet unit tests (validation, the daily limits, the signed transaction; keccak, RLP and secp256k1 vectors) run: node --test site/api/faucet.test.mjs + - name: explorer and public stats unit tests (search router, formatters, emission rule against the node's own test values, the documented API fields from a fixture) + run: node --test site/lib/explorer.test.mjs site/lib/emission.test.mjs site/api/public-stats.test.mjs + - name: public stats API answers with the documented fields (the live site; master only, the endpoints exist there after the merge) + if: github.ref == 'refs/heads/master' + run: node tools/ci/public-api-check.mjs https://igneum.network - name: ship tool self-test (version bump, the dl-both and public manifest helpers) run: node tools/ship-app.mjs --self-test - name: relay unit tests (parsers, secret compare, the wake endpoint) diff --git a/docs/api/public-stats.md b/docs/api/public-stats.md new file mode 100644 index 000000000..d41fb08ae --- /dev/null +++ b/docs/api/public-stats.md @@ -0,0 +1,227 @@ +# Public stats API + +5 October 2026. Two JSON endpoints on the site for profitability sites, pool software and anyone who wants the +network numbers without running a node: `/api/stats` and `/api/supply`. WhatToMine's listing form asks for an +explorer or pool with an API, the reward halving schedule and a source to fetch total coins from; this is that source. +A third endpoint, `/api/explorer`, feeds the explorer pages (docs/plans/explorer.md). + +Both are served by Vercel functions (`site/api/stats.mjs`, `site/api/supply.mjs`) that read what the devnet observer +(`tools/observer/observer.mjs`) wrote to Neon; no secret is involved beyond the database connection the site already +holds. Cached 10 s at the edge (`Cache-Control: public, max-age=10, s-maxage=10`), CORS open (`Access-Control-Allow-Origin: *`), +GET only. A failed read answers 500 with `{ok: false, error}` and `Cache-Control: no-store`. + +The contract is the `FIELDS` list exported by each handler. `site/api/public-stats.test.mjs` checks a fixture against +it without a database, and `tools/ci/public-api-check.mjs ` checks a deployment (CI runs it against +https://igneum.network on master). + +## /api/stats + +| Field | Meaning | Source | +|---|---|---| +| `network`, `chain_id`, `node_version` | Network name, EVM chain id (4461 mainnet, 4462 testnet, 4463 devnet, design 8.1), the node's version | observer `live_state` | +| `algorithm` | The lottery hash, named | fixed text | +| `stale`, `age_s`, `observer_updated_at` | `stale` when the observer has not written for 30 s; treat every number as last known then | observer | +| `height` | The chain block number (the EVM block number): the number of the newest chain block the observer has a shard plan for | `live_blocks.number` | +| `block_count`, `header_count` | Every DAG block the node holds | `getBlockDagInfo` | +| `daa`, `blue_score` | DAA score and blue score of the newest block | `live_blocks`, `getSinkBlueScore` | +| `difficulty` | The node's difficulty (target per block) | `getBlockDagInfo` | +| `hashrate`, `hashrate_unit`, `hashrate_source` | H/s: the node's `estimateNetworkHashesPerSecond` over 1,000 blocks, else blue work added per second over 10 min | observer | +| `block_time_target_s`, `block_time_measured_s` | 1 s by design (spec 2.1); 60 / DAG blocks in the last 60 s | observer | +| `blocks_per_day_target`, `blocks_per_day_measured` | 86,400; the last hour's DAG blocks x 24 (null until the observer has an hour) | observer | +| `block_reward` | `E(daa)` of spec 2.5 at the newest DAA score: `sompi` (8 decimals), `ign`, the 80% `miner_ign` and 20% `proving_pool_ign`, `ramp_factor`, `halving_period`, `next_halving_daa`, `next_halving_in_s` | `site/lib/emission.mjs` | +| `last_block` | Hash, time, age, blue score, DAA, coinbase address, vote key id, EVM transaction count, whether it is a chain block | `live_blocks` | +| `finality` | Finality v2: active, the latest locked checkpoint index and blue score | observer | +| `peers`, `mempool`, `miners_10m` | Connected peers, mempool size, distinct vote keys in 10 min (a card runs several) | observer | + +Note for a profitability calculator: `block_reward` is per blue block merged; at the 1 block per second target that is +also the reward per DAA second. The 20% proving-pool part is paid to provers, not to the miner of the block, so a +miner's expected income per block is `miner_ign`. During the 30-day launch ramp the reward climbs from 10% to 100% +of the schedule (`ramp_factor`); `/api/supply` shows where the ramp stands. + +Example, the devnet on 5 October 2026 (through the local preview against a test observer, so `block_time_measured_s` +reflects a one-minute window): + +``` +{ + "ok": true, + "now": "2026-10-05T19:32:09.321Z", + "network": "igneum-devnet", + "chain_id": 4463, + "node_version": "2.1.0", + "algorithm": "Igneum lottery hash: random-program GPU hash, new program every hour, generator v2 (docs/spec/01-lottery-hash.md)", + "stale": false, + "age_s": 0.9, + "height": 82145, + "block_count": 126358, + "header_count": 126358, + "daa": 126357, + "blue_score": 123504, + "difficulty": 125543017.00697394, + "hashrate": 258756880, + "hashrate_unit": "H/s", + "hashrate_source": "the node's estimateNetworkHashesPerSecond over a 1,000-block window; blue work added per second over 10 min when the node refuses the window", + "block_time_target_s": 1, + "block_time_measured_s": 0.952, + "blocks_per_day_target": 86400, + "blocks_per_day_measured": 14040, + "block_reward": { + "sompi": "455909062", + "ign": "4.55909062", + "miner_ign": "3.6472725", + "proving_pool_ign": "0.91181812", + "split": "80% block producer, 20% proving pool", + "daa_used": 126357, + "ramp_factor": 0.143874, + "halving_period": 0, + "next_halving_daa": 63115200, + "next_halving_in_s": 62988843 + }, + "last_block": { + "hash": "bbec3139d3f38e3517716374707998e77f536a67813edfb619c5bed93a5779ab", + "time": "2026-10-05T19:32:06.157Z", + "ts_ms": 1791228726157, + "age_s": 3.2, + "blue_score": 123504, + "daa": 126357, + "miner": "igneumdev:qrt8nzgrghr2a2xuc7lstzclcnt3n632d2chhc946rphkc6flcyswvkrym8s4", + "miner_id": "7b8ef6fd", + "tx_count": 0, + "chain": true + }, + "finality": { + "active": true, + "latest_locked_index": 4116, + "latest_locked_blue_score": 123480, + "chain_id": "igneum-devnet" + }, + "peers": 4, + "mempool": 0, + "miners_10m": 21, + "observer_updated_at": "2026-10-05T19:32:08.394726+00:00", + "source": "tools/observer reading one node every 2 s; reward from docs/spec/02-consensus.md 2.5 at the node's DAA score" +} +``` + +## /api/supply + +| Field | Meaning | +|---|---| +| `unit` | IGN; the coinbase pays in 8-decimal units (open item O-2.6), the EVM shows 18 | +| `daa` | The newest DAA score the observer stored | +| `max_supply_ign` | 4,000,000,000, the hard cap (spec 2.5, no tail emission: spec 5.10) | +| `circulating_ign`, `circulating_sompi` | Minted so far by the rule: `E(t)` summed over every DAA second from 0 to `daa`, exact (floor sum, `mintedByRule`) | +| `minted_at_end_ign`, `never_minted_ign` | What the schedule reaches when the per-second rate hits 0 (period 32), and the part of the cap the ramp and the floors never mint | +| `emission_per_second_ign`, `block_reward_ign` | `E(daa)` now | +| `halving` | Interval 63,115,200 DAA s (two years), current period, the next halving's DAA score, seconds to it, a date estimate at one DAA second per second | +| `ramp` | 10% at genesis to 100% at DAA 2,592,000 (30 days), the factor now, whether it is complete | +| `schedule` | 33 rows: period, start and end DAA, years from genesis, IGN per second, IGN per block at 1 BPS, minted by the end of the period, share of the cap | +| `check` | The observer's hourly comparison of the chain against the rule over its newest 500 blocks: `rule_match` counts blocks whose coinbase payload declares exactly `E(daa)`; `sum_match` counts blocks whose coinbase outputs equal the declared subsidies of the blocks they merge; `examples` names mismatches | +| `rule`, `source`, `note` | The formula, where it lives, and the caveat: the chain pays per block merged, so a block rate above target mints above the schedule for as long as it lasts | + +Example (schedule cut to five rows here): + +``` +{ + "ok": true, + "now": "2026-10-05T19:32:09.374Z", + "network": "igneum-devnet", + "chain_id": 4463, + "unit": { + "symbol": "IGN", + "decimals_consensus": 8, + "decimals_evm": 18, + "note": "the coinbase pays in 8-decimal units (open item O-2.6 keeps Kaspa's SOMPI_PER_KASPA); the EVM shows the same amount at 18 decimals" + }, + "daa": 126357, + "max_supply_ign": "4000000000", + "circulating_ign": "488236.39686436", + "circulating_sompi": "48823639686436", + "minted_at_end_ign": "3963038988.86765648", + "never_minted_ign": "36961011.13234352", + "emission_per_second_ign": "4.55909062", + "block_reward_ign": "4.55909062", + "halving": { + "interval_daa_s": 63115200, + "interval_years": 2, + "period": 0, + "next_halving_daa": 63115200, + "next_halving_in_s": 62988843, + "next_halving_estimate": "2028-10-03T20:26:12.374Z", + "estimate_note": "the estimate assumes one DAA second per wall-clock second from now" + }, + "ramp": { + "start_percent": 10, + "length_daa_s": 2592000, + "length_days": 30, + "factor_now": 0.143874, + "complete": false, + "remaining_s": 2465643, + "withheld_ign": "36961011.13234352", + "withheld_note": "the ramp withholds about 37 million IGN that are never minted; integer floors withhold the rest (spec 2.5)" + }, + "schedule": [ + { + "period": 0, + "start_daa": 0, + "end_daa": 63115200, + "years_from_genesis": "0 to 2", + "per_second_ign": "31.68808781", + "per_block_ign_at_1bps": "31.68808781", + "minted_by_end_ign": "1963038999.85152848", + "share_of_cap_by_end": 49.0759 + }, + { + "period": 1, + "start_daa": 63115200, + "end_daa": 126230400, + "years_from_genesis": "2 to 4", + "per_second_ign": "15.8440439", + "per_block_ign_at_1bps": "15.8440439", + "minted_by_end_ign": "2963038999.40880848", + "share_of_cap_by_end": 74.0759 + }, + { + "period": 2, + "start_daa": 126230400, + "end_daa": 189345600, + "years_from_genesis": "4 to 6", + "per_second_ign": "7.92202195", + "per_block_ign_at_1bps": "7.92202195", + "minted_by_end_ign": "3463038999.18744848", + "share_of_cap_by_end": 86.5759 + }, + "... 29 more rows ...", + { + "period": 32, + "start_daa": 2019686400, + "end_daa": 2082801600, + "years_from_genesis": "64 to 66", + "per_second_ign": "0", + "per_block_ign_at_1bps": "0", + "minted_by_end_ign": "3963038988.86765648", + "share_of_cap_by_end": 99.0759 + } + ], + "check": { + "bps": 1, + "sampled": 470, + "examples": [], + "sum_match": 466, + "checked_at": "2026-10-05T19:30:28.385Z", + "rule_match": 470, + "sum_skipped": 4, + "sum_mismatch": 0, + "rule_mismatch": 0 + }, + "rule": "E(t) = ramp(t) * floor(10^9 * UNIT / 31,557,600) >> floor(t / 63,115,200); ramp(t) = min(1, 1/10 + 9/10 * t / 2,592,000); t = DAA score / bps, bps = 1", + "source": "docs/spec/02-consensus.md 2.5; vendor/igneum-node consensus/core/src/igneum.rs block_subsidy and launch_ramp; circulating = the rule summed over every DAA second from 0 to the node's DAA score (site/lib/emission.mjs mintedByRule, exact)", + "note": "circulating is the schedule at this DAA score. The chain pays E per blue block it merges and per red inside the DAA window, which tracks the schedule one block per DAA step; the devnet of 3 October 2026 ran 4.7x the schedule for eight minutes during a retarget lag (spec 2.5). check reports what the observer measured on the newest blocks." +} +``` + +## /api/explorer + +The explorer's own feed, cached 5 s: `?blocks=N[&before=ms]` (latest blocks), `?block=hash`, `?height=N`, +`?address=0x..|igneumdev:..`, `?search=q`. Shapes are in `site/api/explorer.mjs`; the pages are the reference client. +Balances need `EXPLORER_EVM_RPC` on the deployment (a public EVM JSON-RPC); without it `balance.available` is false +with the reason. diff --git a/docs/plans/explorer.md b/docs/plans/explorer.md new file mode 100644 index 000000000..65daacd8e --- /dev/null +++ b/docs/plans/explorer.md @@ -0,0 +1,129 @@ +# Explorer: plan and recommendation + +5 October 2026, after Josh read WhatToMine's listing requirements ("build it, and do we build our own explorer? who +built etherscan?"). Branch `explorer`. What exists tonight: the public stats API (`docs/api/public-stats.md`) and the +first DAG explorer pages on the site, fed by the observer. What is recommended: Blockscout for the EVM side, our own +DAG and mining pages, one shared search box. + +## 1. Who built Etherscan, and the open-source routes + +| Explorer | What it is | Licence and cost | Fit for Igneum | +|---|---|---|---| +| Etherscan | Built and launched in 2015 by Matthew Tan (CEO and founder); the office since January 2017 (etherscan.io/aboutus, read 5 October 2026; the page does not name the city, Josh's brief says Kuala Lumpur). Since 2020 it sells "Explorer as a Service", a white-label instance for other chains, 40 clients by 2025 (same page) | Closed source, a private company; a chain pays for an instance. Price not published; not asked | Not for us: closed, paid, and it would show nothing of the DAG, the finality or the proving layer | +| Blockscout | Open-source EVM explorer: blocks, transactions, accounts, verified contracts, token pages, an API in Etherscan's shape. Elixir (Phoenix) backend, PostgreSQL, a separate frontend; "several hundred chains and rollups" use it (README, read 5 October 2026) | "Blockscout Software Licence" (the README badge; the licence text was not read line by line, so what it permits for a hosted instance is unverified) | The EVM side for free: contracts, transactions, logs, tokens, an API developers already know | +| Otterscan | "open-source, fast, local, laptop-friendly Ethereum block explorer": a React app over an Erigon archive node, using Erigon's custom `ots_` JSON-RPC methods (github.com/otterscan/otterscan, read 5 October 2026) | MIT (the app); the `ots_` API lives inside Erigon under its licence | Not for us: it needs Erigon's RPC extensions, which the Igneum node does not have, and it has no contract verification | + +## 2. What Blockscout gives us for free, and what it costs to run + +Blockscout indexes through standard JSON-RPC. Its documented requirements (docs.blockscout.com, read 5 October 2026): + +| Item | Blockscout's figure | Igneum node today (`vendor/igneum-node` 0.3.6 fork, `igneum/exec/src/rpc.rs`, 938 lines) | +|---|---|---| +| Software | Erlang/OTP 26, Elixir 1.15.x, Postgres 14+, Node.js 18.x.x (docs: setup/requirements/requirements) | n/a | +| Hardware, the docs' base line | "16 core, 32 thread", "128GB" RAM; the AWS example is one m5a.xlarge (4 vCPU, 16 GB) application server with 8 GB EBS and one db.t3.large RDS Postgres 14+ with 500 GB "depending on chain size" (docs: setup/requirements/resource-requirements) | A devnet at 0 EVM transactions per block needs nothing like the base line; the AWS example is the honest size for a small chain | +| Database | Ethereum mainnet 21,000 GiB, Sepolia 5,200 GiB, Ethereum Classic 555 GiB, Gnosis Chiado 470 GiB (docs: setup/requirements/database-storage-requirements, figures dated 23 December 2024) | Unmeasured for Igneum. At one chain block per second with empty blocks the row count is 86,400 blocks a day; the byte size per block is the thing to measure in the first week | +| RPC it needs from every client | `eth_blockNumber`, `eth_call`, `eth_getBalance`, `eth_getCode`, `eth_getBlockByHash`, `eth_getBlockByNumber`, `eth_getTransactionByHash`, `eth_getTransactionByBlockHashAndIndex`, `eth_getTransactionByBlockNumberAndIndex`, `eth_getTransactionReceipt`, `eth_getUncleByBlockHashAndIndex`, `eth_getLogs` (docs: setup/requirements/node-tracing-json-rpc-requirements) | The fork answers `eth_blockNumber`, `eth_call`, `eth_getBalance`, `eth_getCode`, `eth_getBlockByHash`, `eth_getBlockByNumber`, `eth_getTransactionByHash`, `eth_getTransactionByBlockNumberAndIndex`, `eth_getTransactionReceipt`, `eth_getLogs` (the match arms of `rpc.rs`). MISSING: `eth_getTransactionByBlockHashAndIndex`, `eth_getUncleByBlockHashAndIndex` (design 8.2 says uncles are "always empty": the method still has to exist). Also absent from the fork but in the design table: `eth_getStorageAt` is present; `eth_getProof`, `eth_feeHistory` present; `web3_clientVersion`, `net_version`, `net_peerCount`, `net_listening`, `eth_syncing`, `eth_mining`, `eth_protocolVersion`, `eth_accounts`, `eth_getBlockReceipts`, `eth_getBlockTransactionCountByNumber`, `eth_maxPriorityFeePerGas`, `eth_gasPrice`, `eth_estimateGas`, `eth_sendRawTransaction` present | +| Pending transactions | `txpool_content` (geth, erigon) or `parity_pendingTransactions` | Neither exists in the fork. Blockscout runs without it (the pending view stays empty) | +| Internal transactions and block rewards | `debug_traceBlockByNumber` and `debug_traceTransaction` with `callTracer` (geth variant), or `trace_replayBlockTransactions` and `trace_block` (erigon, nethermind) | None of the four exist in the fork. Design 8.2 lists them as "Supported, revm inspectors"; `rpc.rs` has no `debug_` or `trace_` method today. Without them Blockscout shows no internal transactions and no block-reward rows, and the indexer's trace fetcher must be switched off (`INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER`, Blockscout's env; unverified against the current version) | + +Cost of one instance on Hetzner (the price list the seeds are on, `docs/plans/seed-nodes.md`: cx23 2 vCPU 4 GB at USD 6.49 net a month; larger types not priced here): Blockscout's own AWS example is 4 vCPU 16 GB plus a 2 vCPU 8 GB database. The matching Hetzner shape is one box in the 8 GB to 16 GB class plus Postgres on the same box for a devnet, a second box for the database when the chain carries real traffic. Price it from the Hetzner API when the box is ordered; the figure here is approximate: USD 15 to 40 a month for the single box, under USD 80 for two. Plus an Igneum node on the same box or next to it (Blockscout wants a local, unlimited RPC; the public `rpc.testnet.igneum.network` is rate limited to 20 req/s, `docs/plans/testnet-go.md`). + +What it costs in work, in hours not weeks: the two missing `eth_` methods (small, same shape as their by-number siblings); a decision on tracing (the `debug_` namespace with revm inspectors, design 8.2, is the larger piece and is not needed to run Blockscout without internal transactions); Blockscout's env file and a Docker compose on the box; the chain's entry in its config (chain id 4463 devnet, 4462 testnet, 4461 mainnet, design 8.1); contract verification through Sourcify or Blockscout's own verifier microservice. + +## 3. What Igneum needs that must be ours + +Blockscout shows a chain: numbered blocks, one parent, transactions, accounts. Igneum's execution layer is such a chain (RPC "blocks" are chain blocks, design 8.2), so Blockscout is right for it. Everything the consensus layer adds is invisible to it: + +| Need | Where it comes from | State tonight | +|---|---|---| +| The DAG: every block, its parents, blue or red, pending, the selected chain, the mergeset of each chain block | the observer's `blockAdded` feed (`live_blocks`, `detail.mergeset`) | `/explorer` table and `/block/` built; the live DAG picture stays on `/live` | +| Blue score and DAA score per block, the miner's vote key, the engine tag | the block header and coinbase | built | +| Miners: payout address (the coinbase's `IGNA` tag) and the coinbase address, blocks mined, what they earned, balance | observer columns `evm_miner`, `miner_address`; `eth_getBalance` through `EXPLORER_EVM_RPC` | `/address/` built; balance shows when the deployment has an EVM RPC (none public for the devnet; `rpc.testnet.igneum.network` for the testnet) | +| The lottery program per epoch (which generated program is live, the epoch seed, the VDF) | the node reports `epochSeed` per shard plan (`igneum_getShardPlan`); the program id and the epoch boundary are not in any RPC the observer reads | not built; needs an RPC for the current program id and epoch (open) | +| Finality: checkpoints every 30 blue score, locks at two thirds of all 30-day weight, certificates, voter tables, the in-browser verifier | `live_checkpoints`, `live_certificates`, `/api/checkpoint`, `site/verify` | the block page shows a block's checkpoint state and certificate; a checkpoints list page is not built | +| Proof records and shards: the plan per chain block, who proved what, lag, payout | `live_proofs`, `igneum_getProofRecords` | the block page shows the shards and the records a block carries; a provers page (per prover: shards, lag, income) is not built | +| Pool payouts | a public pool does not exist (section 5) | not built | +| The switches: `difficulty_v2_activation_daa`, `proving_v0_activation_daa`, `fees_v1_activation_daa`, `finality_v3_activation_daa` (`/tmp/igneum-devnet/override-v3.json` on the devnet: 33,000, 84,100, 210,000, 135,200) | the params file; `igneum_getProvingStatus.activationDaa`; no RPC lists them all | not built; a "network parameters" card on `/explorer` reading a params RPC is the clean way | + +## 4. Recommendation + +Confirmed from the code and the RPC surface: Blockscout for contracts, transactions and accounts; our own DAG, mining, +finality and proving pages on the site, fed by the observer; one search box that routes a transaction or contract to +Blockscout and a block hash, a chain block number or a miner to our pages. Two things qualify it: + +1. Blockscout cannot run against the fork as it is: `eth_getTransactionByBlockHashAndIndex` and + `eth_getUncleByBlockHashAndIndex` are missing (an hour), and there is no tracing (`debug_traceTransaction`), so + internal transactions stay off until the revm inspectors of design 8.2 exist. The execution engineer owns both. +2. One box of our own and Postgres on it, next to a node with an unlimited local RPC. Not before the public testnet + has transactions worth looking at; the devnet's blocks are empty and our pages already show them. + +The search box: `site/lib/explorer.mjs` `classify()` already routes 64-hex to a block (the API falls through to a +transaction hash in the last 24 hours), 0x40 and bech32 to an address, digits to a chain block number. When Blockscout +is up, a 64-hex that is not a DAG block and a 0x40 that is a contract go to it instead of a 404. + +## 5. What was built tonight + +| Item | Where | +|---|---| +| `/api/stats`, `/api/supply` | `site/api/stats.mjs`, `site/api/supply.mjs`, `site/api/_neon.mjs`; the emission rule in `site/lib/emission.mjs`; `docs/api/public-stats.md` with example responses | +| `/api/explorer` | `site/api/explorer.mjs`: latest blocks, one block, height, address, search | +| Pages | `site/explorer.html`, `site/block.html`, `site/address.html`; `site/vercel.json` rewrites `/block/:id` and `/address/:addr`; the footer carries an Explorer link (the nav is unchanged: its eight items are measured to fit at 941 px, a ninth is a layout decision for the site owner) | +| Observer | `tools/observer/observer.mjs`: per-block explorer columns and `detail`, `number` from the shard plan, `rpc_load`, hourly `supply_check` | +| Preview | `node tools/site-serve.mjs` (clean URLs, the rewrites, the API functions in-process; `LIVE_TABLE_PREFIX`, `EXPLORER_EVM_RPC`, `PORT`) | +| Tests | `site/lib/explorer.test.mjs` (router, formatters), `site/lib/emission.test.mjs` (the node's own test values from `igneum.rs`, a devnet coinbase, the floor sum against a loop), `site/api/public-stats.test.mjs` (every documented field from a fixture); CI runs them, and on master `tools/ci/public-api-check.mjs https://igneum.network` | +| Screenshots | `docs/plans/explorer/explorer.png`, `block.png`, `address.png` (local preview against a test observer, 5 October 2026) | + +### Observer load, measured + +Two copies of the observer ran side by side against the observer node (`ws://127.0.0.1:28640`, EVM RPC +`http://127.0.0.1:26790`) with test table prefixes, 19:21 to 19:28 UTC on 5 October 2026: master's code with only a +call counter added, and this branch. Calls per wall-clock minute, as each process counted them: + +| Minute (UTC) | Before, wRPC | After, wRPC | Before, EVM | After, EVM | +|---|---|---|---|---| +| 19:21 to 19:22 (partial, 50 s) | 283 | 282 | 776 | 785 | +| 19:22 | 211 | 211 | 2,119 | 2,145 | +| 19:23 | 252 | 253 | 2,487 | 2,493 | +| 19:24 | 232 | 232 | 2,487 | 2,492 | +| 19:25 | 266 | 266 | 2,491 | 2,497 | +| 19:26 | 210 | 210 | 2,485 | 2,481 | + +Ratio after to before: 1.00 on both. The explorer reads everything from the notification the observer already +receives; the only new work is one SQL update per shard-plan batch. The EVM figure is the proving feed's record +polling (80 blocks per 2 s tick while proving is active, master behaviour); the observer that is live tonight points +at `http://127.0.0.1:26800` (the app's node, down), so its EVM load is zero and its proving feed reads "unreachable". +The observer node answers the proving RPCs on 26790 (`eth_chainId` 0x116f, `igneum_getProvingStatus` active); pointing +`IGNEUM_EVM_RPC` there when the observer is next restarted is a one-line change to the launch environment, not to the code. + +### Supply check, measured + +The first sample after start: "21 blocks, payload subsidy = rule 21/21, outputs = merged subsidies 20/20 (1 without a +verdict)". The rule in `site/lib/emission.mjs` reproduces the node's own test values (`igneum.rs`: 3,168,808,781 at +DAA 2,592,000; 1,584,404,390 at the first halving; 0 at the 32nd) and the devnet coinbase of block `2622db76` (payload +subsidy 454,486,399 at DAA 125,064; outputs 363,588,240 + 90,897,059 = 454,485,299 = E(125,063), what its merged +parent declared: `utxo_validation.rs:176` pays a merged block the subsidy its own payload carries). + +## 6. A public pool with a stats API (separate item, not built) + +The app carries a pool mode in the Hive package: the Flight Sheet's Pool URL is `grpc://:26610` for solo +mining or `local` to run the bundled node on the rig (`packaging/hive/README.md`); "there is no pool". A public pool +needs, in order: the pool protocol of `docs/spec/09-pool-protocol.md` implemented (templates, shares, the member's +own vote key and votes relayed, vardiff, the `stats` message: member `hashrate`, `workers`, `refusals`, `shares`, pool +`members`, `hashrate`, `blocks_24h`, `declared_share`); a pool server on its own box with a node; a payout scheme (the +spec leaves PPLNS windows and PPS fees to the pool); a stats API in the shape WhatToMine and MiningPoolStats read +(pool hashrate, miners, workers, blocks found with heights and times, fee, minimum payout, luck); and a page on the +site. None of this is in the repo; the stats API of this branch is the network side of what those sites ask for. + +## 7. Unverified + +- Vercel's `cleanUrls` with a rewrite destination of `/block` and `/address` (the clean names of `block.html` and + `address.html`): checked locally through `tools/site-serve.mjs`, not on a Vercel preview, because nothing was pushed. +- `/api/stats` and `/api/supply` on the live tables: the live observer has not been restarted on this code, so the + live `live_blocks` has no `number`, `tx_count` or `detail` columns yet; the handlers answer with nulls there until + the restart (the schema adds itself on start, `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`). The examples in + `docs/api/public-stats.md` come from the test observer's tables. +- Blockscout's licence terms for a hosted instance, and the exact env flag that disables its trace fetcher. +- The database size per Igneum block in Blockscout, and the Hetzner price of the box: approximate above, measure and + price when ordered. +- `EXPLORER_EVM_RPC` on Vercel: no public devnet EVM RPC exists, so balances show "no EVM RPC configured" on the devnet + deployment; the testnet's `https://rpc.testnet.igneum.network` is the value for the testnet. diff --git a/docs/plans/explorer/address.png b/docs/plans/explorer/address.png new file mode 100644 index 000000000..9f3a47dad Binary files /dev/null and b/docs/plans/explorer/address.png differ diff --git a/docs/plans/explorer/block.png b/docs/plans/explorer/block.png new file mode 100644 index 000000000..031cb2773 Binary files /dev/null and b/docs/plans/explorer/block.png differ diff --git a/docs/plans/explorer/explorer.png b/docs/plans/explorer/explorer.png new file mode 100644 index 000000000..a493c9277 Binary files /dev/null and b/docs/plans/explorer/explorer.png differ diff --git a/site/404.html b/site/404.html index 321532bd4..076e9b219 100644 --- a/site/404.html +++ b/site/404.html @@ -172,6 +172,7 @@ p{margin:0;color:var(--ink-2);max-width:52ch}