Skip to content

verify: trustless on-device account reads + Android DNS fix#4

Merged
dkijania merged 7 commits into
mainfrom
verify/light-client
Jun 17, 2026
Merged

verify: trustless on-device account reads + Android DNS fix#4
dkijania merged 7 commits into
mainfrom
verify/light-client

Conversation

@dkijania

Copy link
Copy Markdown
Member

Summary

Adds trustless, on-device account reads to the capture layer, plus a DNS fix that lets the mobile light client dial libp2p seeds on Android.

  • Trustless account reads (transport-agnostic core): verify_account_at_root folds a sync-ledger Merkle path onto a proven epoch-ledger root, so a lying peer can't forge a balance — the path won't fold or the returned key won't match.
  • Verify at an explicit proven root (the epoch ledger), not the staged tip root.
  • Epoch-ledger sweep to build a pubkey → leaf-index map, plus an incremental tail-sweep (indices are append-only, so the tail is bounded).
  • rpc_net::fetch_sync_ledger_answers — libp2p transport for batched answer_sync_ledger_query calls over one persistent connection.
  • Android DNS fix: libp2p's .with_dns() reads the system resolver from /etc/resolv.conf, which doesn't exist on Android (the libp2p-dns docs note it "fails (panics even!)" there) — so /dns4/ seed multiaddrs never resolved and the mobile client hung forever on "Connecting". Resolve them with an explicit Cloudflare ResolverConfig via dns::tokio::Transport::custom instead, so the same path works on every OS.

Test plan

  • cargo build -p mina-verify-capture
  • cargo run --example rpc_fetch -p mina-verify-monitor -- devnet → fetched + verified a live devnet tip (verify_block = true)
  • mina-verify-mobile builds against this branch and does end-to-end trustless balance reads on devnet/mainnet

🤖 Generated with Claude Code

dkijania and others added 7 commits June 16, 2026 09:13
… core)

Add `account_read` module: the protocol layer that obtains an account + Merkle
path from an untrusted peer via `answer_sync_ledger_query`, atop the existing
pure fold in `account.rs`. Transport-free by design so every consumer
(light-node over libp2p, mobile, MCP) reuses one trust-critical implementation
instead of re-deriving it:

- `sync_ledger_queries(index, depth)` → the RPC query plan (one
  What_child_hashes per level root→leaf + What_contents for the leaf), built
  from mina-tree `Address::from_index`.
- `account_with_path(index, depth, answers)` → assembles `(Account,
  Vec<MerklePath>)`, picking the off-path sibling at each level and reversing to
  the leaf→root order `implied_root` folds.
- `verify_account(block, index, depth, answers)` → assemble + fold to the
  verified block's ledger root; a lying peer is caught as `NotIncluded`.
- `ledger_hash(block)` → the root the relay pairs with each query.

Hermetic test: answer the query plan from a real mina-tree `Database` and assert
the assembled path equals mina-tree's canonical `merkle_path_at_index` and folds
to the real root — correctness proven with no live network.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he staged root

Live devnet peers refuse sync-ledger queries for the tip's staged ledger root and
the snarked target/source roots; they serve only the epoch ledgers (probed:
staking 91585 accounts, next 91661). Both epoch-ledger hashes are fields of the
proven consensus state, so they're sound trustless-read targets (finalized,
proof-anchored balances).

- Replace `verify_account(block, …)` (folded to the unservable staged root) with
  `verify_account_at_root(root, index, depth, answers)` — the caller picks a proven
  root and the fold is checked against it.
- Add `staking_epoch_ledger_hash` / `next_epoch_ledger_hash` accessors (replacing
  `ledger_hash`, which returned the staged root).
- Test now also drives `verify_account_at_root`: accepts the true root, rejects a
  wrong one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the protocol logic to sweep a whole ledger and map public keys to leaf indices,
so a light node can serve /account by pubkey (resolving the untrusted index hint
itself) instead of requiring it from an indexer:

- CONTENTS_SUBTREE_HEIGHT = 5 — the live sync-ledger responder serves What_contents
  at LEDGER_DEPTH-5 (32 accounts/batch); shallower is refused (probed on devnet).
- ledger_sweep_queries(num_accounts, depth) — one What_contents per 32-account
  subtree covering indices 0..num.
- pubkey_index_pairs(answers, depth) — parse the batches into (pubkey, leaf index);
  an untrusted hint, since every read still re-proves inclusion via
  verify_account_at_root and the caller cross-checks the pubkey.

Test: a 70-account ledger (3 subtrees) reconstructs leaf indices 0..70 exactly
against mina-tree's own indexing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mina account indices are permanent and append-only (accounts are never deleted or
reordered), so a pubkey->index map is monotonic — valid across every ledger
snapshot, only growing. Generalize the sweep to a range so a light node sweeps once
at cold start, then only the newly-appended tail each epoch (a handful of accounts),
not the whole 80k+:

- ledger_sweep_queries(start_index, num_accounts, depth) — sweep [start, num); 0
  sweeps the whole ledger, larger start sweeps just the tail.
- sweep_base_index(start_index) — the subtree-aligned base leaf index to pass back.
- pubkey_index_pairs(answers, base_index, depth) — index from the base, not from 0.

Test: a tail sweep from index 40 reconstructs indices 32..n (subtree-aligned),
matching the full sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the sync-ledger RPC fetch into mina-verify-capture (the mobile/relay transport)
so the mobile bridge can do trustless account reads on-device — fetch the account +
Merkle path over libp2p, then verify via mina_verify::verify_account_at_root. Mirrors
the mina-relay impl; uses capture's existing RpcConn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
libp2p's `.with_dns()` reads the system resolver from /etc/resolv.conf, which
does not exist on Android — the libp2p-dns docs note it "fails (panics even!)"
there. So /dns4/ seed multiaddrs never resolved and the mobile light client
hung forever on "Connecting". Build the DNS transport with an explicit
Cloudflare ResolverConfig (via dns::tokio::Transport::custom) instead, so the
same path works on every OS. Verified end-to-end on the host (rpc_fetch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gate

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dkijania dkijania merged commit ab52b34 into main Jun 17, 2026
4 checks passed
@dkijania dkijania deleted the verify/light-client branch June 17, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant