igneum/docs/plans/explorer.md
2026-10-06 16:16:11 +00:00

237 lines
26 KiB
Markdown

# Explorer: plan and recommendation
5 October 2026, after the project lead 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, the project lead'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/<hash>` 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/<addr>` 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://<your node>: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.
## 8. Verify a proof (6 October 2026, branch `explorer-verify`)
Goal: a stranger checks a block's proof without our software. What shipped, what is verified where, and the number
that decides the in-browser verifier.
### What a stranger sees
`/proof/<chain block hash or number>` (linked from every proven or finalised block on `/explorer`, from the shard
table on `/block/<hash>`, and from the "verify this block's proof" line under it). Paste a hash: the page fetches
`/api/verify?block=<hash>`, which returns the block's one state word, every planned shard with the record the chain
carries for it (vote key hash, payout, statement, proof hash, the carrying chain block) and, where the observer
captured the shard's proof, the bytes url and the verdicts. The browser then downloads the bytes
(`/api/verify?...&bytes=1`, 1,272,897 bytes for a compressed SP1 shard proof) and runs three checks in the tab
(`site/lib/proof.mjs`, no library, no server trust): SHA-256 of the bytes against the record's `proof_hash`
(`igneum/exec/src/proving.rs` `proof_hash`), keccak-256 of the public values parsed from the proof's bincode tail
against the record's `statement`, and the decoded 328-byte statement (`ShardOutput::to_bytes`) against this block's
hash, number, shard and payout. The strip shows the verdict (VERIFIED, NOT VERIFIED, binding only, not captured),
the time (checks plus download in this browser; the STARK's time on the node), the block's state word and the pinned
program id. Three cards say who verified what: in this browser (the binding), by this site's node (the native SP1
light verifier with the pinned key, `igneum-prove-host --mode verify`, its verify and setup times), by the network
(carried and paid; the observer's node runs with its own verifier off, and says so). The page carries the label
"the in-browser verifier is coming" on the STARK line, and never says the STARK was verified in the browser.
### The data path
| Step | Where | Rate |
|---|---|---|
| The pool entry appears in `igneum_getProofRecords` (`proofBytes` > 0) | observer `pollRecords` | every 2 s, as before |
| `igneum_getProofBytes [number, shard, keyHash]` while the node holds it (600 chain blocks, `RECORD_WINDOW_CHAIN_BLOCKS`) | observer `captureTick`, `tools/observer/proof-capture.mjs` | one proof every `PROOF_CAPTURE_EVERY_MS` (5 min) |
| The three browser checks, then the native verifier | the observer, `igneum-prove-host --mode verify` from the Mac app bundle | 0.213 s key setup plus the verify per proof (below) |
| `live_proof_bytes` row (bytes kept for the newest `PROOF_BYTES_KEEP` = 50, about 64 MB; record, checks and verdict kept 24 h) | Neon | prune each minute |
| `/api/verify` and `/proof/<hash>` | Vercel function `site/api/verify.mjs`, `site/proof.html` | cached 5 s; the bytes immutable |
Why a sample: 1,272,897 bytes per shard proof at one chain block per second is 110 GB a day. Every proof's RECORD
(statement, proof hash, key hash, payout) is now stored per shard in `live_proofs`, so a shard without captured
bytes still shows what the chain carries; any node hands the bytes out again while it holds them.
### The in-browser verifier: the size and time problem, with numbers
| Item | Number | Source |
|---|---|---|
| Compressed SP1 shard proof | 1,272,897 bytes (1,272,769 to 1,272,909 across runs) | `docs/bench-log.md` 5 October, the saved proof of block 59199 |
| Native verify of it, pinned key | 0.029 to 0.038 s; key setup 0.213 s with the light verifier (measured 6 October 15:46 UTC under other load, a functional run, not a benchmark) | bench-log; this branch's run of `--mode verify` on block 59199's proof |
| Browser checks on the same bytes | SHA-256 and keccak of 1.27 MB and 328 bytes: a few ms (measured on the page, shown in the strip) | `site/proof.html` |
| What verifies a COMPRESSED proof | `sp1-prover` `verify_compressed`: the recursion STARK verifier of `sp1-hypercube` 6.8.1 (KoalaBear, jagged PCS) plus the recursion verifying key and the allowed-vk Merkle root; std crates with rayon, tokio and the circuit artifacts | `proving/igneum-prove/host/src/proof_system.rs`, `~/.cargo/registry` sp1-* 6.8.1 |
| What `sp1-verifier` (the no_std, wasm-ready crate) verifies | Groth16 and Plonk WRAPPED proofs only | the crate's README (vendor registry) |
| The wrap step | NOT RUN here: `wrap` in `proof_system.rs` bails ("needs SP1's circuit artifacts", ledger P3); the wrapped proof would be about 260 bytes and the wasm verifier a few hundred KB, verify in tens of ms (approximate, from SP1's published figures, unmeasured on this project) | `host/src/proof_system.rs` line 327; ledger P3 |
Decision for the first cut: ship the server-side verify (the observer's native verifier, labelled on the page) and
the browser binding checks. The in-browser STARK verifier is owed on one of two routes, both unmeasured here:
(a) wrap each shard proof to Groth16 (the P3 measurement: the circuit artifacts, the minutes per wrap on a consumer
GPU, who pays for it) and compile `sp1-verifier` to wasm; (b) port the compressed-proof verifier to wasm32 (drop
rayon, tokio and the file reads from the sp1-hypercube verify path; the recursion vk and allowed-vk root shipped as
static bytes, about 1 MB). Route (a) is the one that gives a phone a millisecond verify; route (b) keeps the 1.27 MB
download per proof. Hours of work either way, after P3's numbers exist.
### The state word (ledger P17) and the fork
The P17 `state` and `failure` fields live on the fork `ledger-fixes-0311` (`fbb0082a`, `tx_status_json`,
`igneum_getFinalityView`). They are NOT in `release-0.3.13-node` (`bb43e9a8` does not contain `b1e98b79`;
`igneum_getTransactionStatus` there still answers the three flags). So the explorer cuts the word itself from the
observer's tables with the same rule (`site/lib/proof.mjs` `blockState`, `txState`; design 2.4): a block is pending
(not merged), included (merged, or a chain block the executor has not run), executed (the node planned it), proven
(every shard's record carried and paid), finalised (at or below the latest locked checkpoint, finality active) or
"finality not active"; the failure "finality paused" (executed, not locked, finality not active). A block that had a
number and left the selected chain is NOT "reorged out": it is still held and merged (the tip flips between parallel
blocks several times a minute on the devnet); the note says so, and the word stays included. "Reorged out" and
"skipped" are transaction failures, read from the node.
Per transaction the block page asks `igneum_getTransactionStatus` when `EXPLORER_EVM_RPC` is set and takes the node's
`state` when the fork carries it; otherwise the word is cut from the block's. Owed: the fork merge (P17 into the
node release), after which the page reads the node's word and the derived one is the fallback.
### Reviewer C46
`/api/stats` `algorithm` read "generator v2" as fixed text. The observer now asks `getBlockTemplate` every 10 s and
writes `powEpoch` (`program_class` 3, `next_program_class` 3, `program_class_v3_activation_daa` 154,800, epoch 55 on
the devnet at 15:52 UTC) to `live_state.pow_epoch`; the line reads "class v3 / generator 3 (epoch 55; ...)" and
will read v4 when the node reports 4. A new `lottery` field carries the epoch line. Until the restarted observer has
written it, the line says the class is unknown, never v2.
### Tests and the dry run
`site/lib/proof.test.mjs` (the tail parse and the statement decode against the real tail of block 59199's proof,
reproducing the statement the host printed; the three checks on genuine and tampered bytes; the state words),
`site/api/verify.test.mjs` (the API from a fixture: block, shards, capture, the bytes route, 404/410/400, the state
cut), `tools/observer/proof-capture.test.mjs` (the host-output parsers against real lines; the capture against a
fake node; on a Mac with the app, the native verifier refusing block 59199's pre-pin proof: "IS NOT OURS"),
`site/api/public-stats.test.mjs` (C46). CI runs the first, second and fourth with the explorer tests.
Dry run, 6 October 2026, 16:08 to 16:16 UTC. The devnet's execution was reset for 0.3.13 at about 15:48 UTC
and node 1's exec RPC (127.0.0.1:26790) came back at tip 135,933 by 16:12 with an EMPTY pool (the provers were
still off), so the live capture could not run; the fixture route was used instead, with the real verifier and
the real tables:
| Step | Result |
|---|---|
| Pinned host (the Mac app bundle's `igneum-prove-host`) proves shard 0 of `proving/fixtures/block-56-transfers.json` on the CPU | 81.3 s beside another agent's cargo build (not a number); proof 1,272,897 bytes, statement `0x5a4d59f4...`, `--mode verify` 0.041 s, program id the pinned one |
| `captureProof()` on those bytes with a fake node handing them out, the native verifier on | SHA-256 ok, statement ok, fields ok; native VERIFIED in 29 ms, key setup 197 ms, process 240 ms; the whole capture 282 ms |
| A test observer (`LIVE_TABLE_PREFIX=vtest_`) against the live node for 5 minutes | schema created, `pow_epoch` written (class 3, next 3, epoch 55, activation 154,800), proving feed on 26790; RPC load 230 to 248 wRPC calls a minute against the live observer's 222 to 224, EVM 2,466 against 2,481 |
| `/proof/c47bacc8...` through `tools/site-serve.mjs` in the browser pane | VERDICT VERIFIED; 5.8 ms of checks and 139 ms of download in the browser, STARK 29 ms on the node; the three checks print got and want; the statement decodes to block 56, 3 transactions, gas 63,000, pgas 600 |
| `/block/c47bacc8...`, `/block/136508`, `/explorer` | the state chip and the note; height 136508 first resolved to a block that had left the chain (two blocks shared the number after a tip reorg), fixed: the height lookup prefers the chain block |
| The known-failed case | `tools/observer/proof-capture.test.mjs`: the pinned verifier refuses block 59199's pre-pin proof ("IS NOT OURS", 0x0559759b...), parsed and stored as native NOT VERIFIED with the note |
Owed: the same run against a devnet pool proof once the provers are back (the test observer captures one within
`PROOF_CAPTURE_EVERY_MS` of seeing it; `grep "proof captured"` in its log), and the live observer's restart on
this code (autosync picks it up from master; until then `/api/verify` answers with empty shards and the stats
line says the class is unknown).