verify: trustless on-device account reads + Android DNS fix#4
Merged
Conversation
… 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
verify_account_at_rootfolds 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.pubkey → leaf-indexmap, plus an incremental tail-sweep (indices are append-only, so the tail is bounded).rpc_net::fetch_sync_ledger_answers— libp2p transport for batchedanswer_sync_ledger_querycalls over one persistent connection..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 CloudflareResolverConfigviadns::tokio::Transport::custominstead, so the same path works on every OS.Test plan
cargo build -p mina-verify-capturecargo run --example rpc_fetch -p mina-verify-monitor -- devnet→ fetched + verified a live devnet tip (verify_block = true)mina-verify-mobilebuilds against this branch and does end-to-end trustless balance reads on devnet/mainnet🤖 Generated with Claude Code