Skip to content

feat(snapshot): add ZETA snapshot tooling (state export + per-address balance compute) - #4613

Open
kingpinXD wants to merge 8 commits into
mainfrom
feat/snapshot-export
Open

kingpinXD wants to merge 8 commits into
mainfrom
feat/snapshot-export

Conversation

@kingpinXD

@kingpinXD kingpinXD commented Jul 23, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Stage 1, export (contrib/snapshot/export_snapshot.py + make snapshot-export): produces a zetacored export genesis JSON for testnet or mainnet. Reuses download_snapshot.py for the fetch and skips the node sync a devnet fork needs, since export reads committed state straight from the data dir. Strips the logger preamble so the output is valid JSON.
  • Stage 2, compute (zetatool snapshot): a pure, deterministic function over that export. Attributes liquid + staked + unbonding per address (slashing-aware via TokensFromShares), routes module accounts, the zero address and pinned contracts to a single remainder, and emits Postgres-ready CSV + schema.sql. remainder = supply - attributed, so nothing is dropped and the books balance by construction.
  • Contract pinning: contracts hold native ZETA no keypair can claim, so they are pinned out of attribution. --pin-file reads them from a file, and contrib/snapshot/pins_mainnet.txt carries the 16 mainnet contracts found by an eth_getCode scan of the top-500 holders (9,351,261 ZETA). A pin that matches no holder now fails the run, which is what catches a typo or a wrong-network pin file.
  • Verified against a real testnet export at height 22052574: sum of 2,411,443 balances equals recorded bank supply equals live supply, to the wei. Mainnet export at height 17619782 also produced and reconciled.
  • Offline tooling only. No changes to consensus, modules, or runtime behavior.

contrib/snapshot/README.md has the exact commands for both stages, the flags, and a Verification status section listing what is and is not proven yet.

Generated with Claude Code

Adds contrib/snapshot/export_snapshot.py and a `make snapshot-export`
target to produce a `zetacored export` genesis JSON for testnet or
mainnet. Reuses download_snapshot.py for the snapshot fetch and skips
the node sync that the devnet fork needs, since export reads committed
state straight from the data dir.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U888hFEu3YMDT9dnpcQ3MS
@kingpinXD kingpinXD added no-changelog Skip changelog CI check operation Operation related (no codebase change involved) labels Jul 23, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 8edf120. Configure here.

Comment thread contrib/snapshot/export_snapshot.py
Stage 2 of the migration snapshot: a pure, deterministic pass over a
`zetacored export` that attributes each account's native ZETA
(liquid + slashing-aware staked + unbonding) and routes module
accounts, pinned contracts, and the zero address into a remainder that
mints to the migration multisig. Emits a Postgres-ready CSV, schema,
and a human-readable audit copy, with supply invariants enforced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U888hFEu3YMDT9dnpcQ3MS
@github-actions

Copy link
Copy Markdown

!!!WARNING!!!
nosec detected in the following files: cmd/zetatool/cli/snapshot.go

Be very careful about using #nosec in code. It can be a quick way to suppress security warnings and move forward with development, it should be employed with caution. Suppressing warnings with #nosec can hide potentially serious vulnerabilities. Only use #nosec when you're absolutely certain that the security issue is either a false positive or has been mitigated in another way.

Only suppress a single rule (or a specific set of rules) within a section of code, while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within the #nosec annotation, e.g: /* #nosec G401 */ or //#nosec G201 G202 G203
Broad #nosec annotations should be avoided, as they can hide other vulnerabilities. The CI will block you from merging this PR until you remove #nosec annotations that do not target specific rules.

Pay extra attention to the way #nosec is being used in the files listed above.

@github-actions github-actions Bot added the nosec label Jul 24, 2026
kingpinXD and others added 2 commits July 24, 2026 18:34
Two-stage design, exact export + compute commands for testnet/mainnet,
flags, output exploration, and the contract-vs-EOA detection approach so
the team can run the snapshot and try attribution ideas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U888hFEu3YMDT9dnpcQ3MS
…ch pin

Contracts hold native ZETA that no keypair can claim on Solana, so their
balances have to route to the remainder instead of being attributed. The
addresses were already pinnable one at a time; passing sixteen of them on the
command line was not something you want to get right by hand.

- --pin-file reads them from a file: # comments, blank lines skipped, first
  whitespace field per line is the address, so a TSV works as-is.
- The mainnet list is committed as contrib/snapshot/pins_mainnet.txt: the 16
  contracts from the top-500 eth_getCode scan at height 17619782, 9,351,261
  ZETA. Mainnet only; a testnet run passes no pins.
- Report what each pin actually caught. The summary now prints per-pin azeta
  plus a subtotal, and a pin that matched no holder fails the run.

That last part is the point. An unmatched pin used to be silently ignored, so a
typo in one of sixteen addresses left that contract's ZETA marked claimable and
all three supply checks still balanced. Nothing signalled it. The check is
governed by the existing --supply-check, so it downgrades to a warning with the
rest.

Not yet run: the end-to-end mainnet snapshot with this pin file. The README says
so and labels the expected numbers a prediction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBYPC9DZYnjZoEhJD1K6zK
@kingpinXD
kingpinXD marked this pull request as ready for review July 27, 2026 23:28
@kingpinXD
kingpinXD requested a review from a team as a code owner July 27, 2026 23:28
Comment thread contrib/snapshot/export_snapshot.py
Comment thread contrib/snapshot/export_snapshot.py
@ws4charlie

Copy link
Copy Markdown
Contributor

can you pls check the cursor + greptile comments? the stale-cache one especially — download_snapshot.py exits 0 when a cache already exists, so a rerun without --force silently exports the old height.

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread contrib/snapshot/export_snapshot.py Outdated
Comment thread cmd/zetatool/cli/snapshot.go
@kingpinXD kingpinXD changed the title feat(contrib): add snapshot export tool for testnet/mainnet feat(snapshot): add ZETA snapshot tooling (state export + per-address balance compute) Jul 28, 2026
CI runs golangci-lint v2.1.6 and flagged `var pins []string`. Local was on
v2.8.0, which does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBYPC9DZYnjZoEhJD1K6zK

@usmaneth usmaneth left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core compute is solid - tried to double-count a validator's self-delegation via the valoper form and the pinned-delegator path, both hold, and the module list matches maccPerms exactly. the real gap is that nothing enforces a zero-height export, so a plain one ships every pending reward to the multisig with all checks green - inline. the open threads on the stale snapshot cache, mainnet-without-pin-file, and the rm -rf on --home are the other things i'd want answered before this produces a real migration snapshot.

Comment thread cmd/zetatool/cli/snapshot.go
…apshot (#4647)

* fix(zetatool): refuse incomplete staking and distribution input in snapshot

The snapshot now reads distribution and refuses, as hard errors that
--supply-check=false cannot skip:
- unpaid rewards or commission, or a distribution module balance other
  than the community pool
- staking records that do not add up: share sums, duplicates, unknown
  validators, invalid status, negative values, excess share dust
- staking pools that do not exactly match validator tokens and
  unbonding entries
- a pin on a staking pool, or a remainder its categories do not explain

The summary prints the remainder by category and the 18->9 conversion
dust. Attribution and the CSV output are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(snapshot): document the snapshot acceptance guarantees

--for-zero-height and the distribution module are now required by
zetatool snapshot, so the README and the export script say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(zetatool): name a supply mismatch and pin the remainder category precedence

- Compute refuses a bank supply that differs from the sum of bank
  balances with its own message. The remainder identity check is now a
  backstop, tested through finalize directly.
- A test pins the zero address and a module account, and checks the pin
  wins over both categories.
- The summary's non-std line says its amount is liquid only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(zetatool): list the bank/supply mismatch as a hard refusal

--supply-check=false now downgrades only three output checks. The bank
sum vs supply check fails in Compute before anything is written, so the
README, the command help and the flag help say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: add changelog entry for #4647

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

@usmaneth usmaneth left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the distribution check closes the zero-height gap properly - a plain export now fails before anything is written, and the pool/share reconciliation in validateStaking means stake can't quietly leak into the remainder either. good to go from my side.

kingpinXD and others added 2 commits October 18, 2026 00:34
…de homes

- Pass --force to the downloader unless --skip-download is set, so a normal
  run always exports a fresh snapshot instead of a stale cache.
- Quote every interpolated path with shlex.quote in the init, rm, cp and
  export commands, including the redirect targets.
- Refuse a --home that has data/ but no .zeta-snapshot-export marker, and
  write the marker after init, so the script never overwrites a real node home.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mainnet without pins attributes contract-held ZETA (WZETA and friends) to
claimable rows. Refuse --chain-id 7000 with no pins before reading the export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kingpinXD

Copy link
Copy Markdown
Member Author

@ws4charlie the cursor and greptile comments are all addressed: stale cache and path quoting in c9a1fc0, zero-height in b9b0ea5 (#4647, zetatool now refuses a non-zero-height export). On the #nosec warning, the four G304 suppressions in cmd/zetatool/cli/snapshot.go are operator-supplied file paths (export input, pin file, output dir, and the CSV we just wrote), so there's no untrusted input reaching them.

@kingpinXD
kingpinXD requested a review from ws4charlie October 18, 2026 04:36

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking:cli no-changelog Skip changelog CI check nosec operation Operation related (no codebase change involved)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants