feat(manifest): per-run reproducibility manifest + reproduce subcommand#105
Open
skylenet wants to merge 2 commits into
Open
feat(manifest): per-run reproducibility manifest + reproduce subcommand#105skylenet wants to merge 2 commits into
skylenet wants to merge 2 commits into
Conversation
Every run now drops a state-actor-manifest.json at the client datadir root (two levels up from --db for geth; --db itself for the others) capturing everything needed to reproduce it: - resolved flags — note the *resolved* seed (--seed=0 expands to wall-clock) and the *resolved* fork (empty --fork → client max), not the raw inputs - the full command, chain params, target size, etc. - the --spec config, written verbatim to a content-addressed sidecar (state-actor-spec-<sha256>.yaml) and referenced from the manifest - build provenance: version, go version, os/arch, and the git revision/time/dirty that Go embeds automatically - result: state root, counts, db size, elapsed Version sourcing: main.Version (previously a dead -X target) now exists and falls back to the Go-auto-stamped short commit when not linked in, so go-run and plain docker builds still record a meaningful version. Docker images stamp the real git-describe via a STATE_ACTOR_VERSION build-arg (wired through the Makefile and deploy-docker workflow). Also add OCI image labels (title/description/source/licenses/version/ revision) to all Dockerfiles, with the commit passed via a new STATE_ACTOR_REVISION build-arg. Manifest write failures are warnings, not fatal — a successful generation never exits non-zero because the manifest could not be written.
…ts manifest state-actor reproduce --manifest <state-actor-manifest.json> --db <new-dir> Loads the manifest's resolved flags (the concrete seed + fork, so runs created with --seed=0 reproduce exactly), replays them through the same generation pipeline into a fresh --db (refusing the original datadir), and reads any spec from the content-addressed sidecar next to the manifest rather than the original --spec path. After regenerating it verifies the new state root against the recorded one and exits non-zero on mismatch. main() is split into subcommand dispatch + a shared generate(); adds manifest.Load. Documents the manifest + reproduce flow in docs/SKILL.md.
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
Every run now writes a
state-actor-manifest.jsonto the output datadir capturing everything needed to reproduce it, plus areproducesubcommand that regenerates a run from that manifest and verifies it.What's in the manifest
Written to the datadir root (two levels up from
--dbfor geth, alongsidegeth-genesis.json; the--dbdir itself for the other five clients, beside their chainspec sidecars):flags— resolved config. Critically the resolved seed (--seed=0expands to wall-clock) and resolved fork (empty--fork→ client max), not just the raw inputs, so the run is reproducible even when the flags alone aren't.command— fullos.Args.spec— when--specis used, the YAML is written verbatim to a content-addressedstate-actor-spec-<sha256>.yamlsidecar and referenced by relative filename + sha256 + original input path (not inlined — keeps the manifest readable and the spec directly reusable).state_actor— version, go version, os/arch, and git revision/time/dirty (Go's automatic VCS stamping).result— state root, account/contract/slot counts, db size, elapsed.Manifest-write failures are warnings, not fatal — a successful generation never exits non-zero just because the manifest couldn't be written.
reproducesubcommandReplays the manifest's resolved flags through the identical generation pipeline into a fresh
--db(refuses the original datadir), reads any spec from the sidecar next to the manifest, then verifies the regenerated state root against the recorded one — exiting non-zero on mismatch. Works even for--seed=0runs, since the concrete seed was captured.Versioning
main.Version(the-X main.Versiontarget was previously a dead no-op) now exists and falls back to the Go-auto-stamped short commit when not linked in — sogo runand plaindocker buildstill record a meaningful version.git describevia aSTATE_ACTOR_VERSIONbuild-arg, wired through the Makefile and thedeploy-dockerworkflow (including av*.*.*tag →version: v1.2.3).OCI labels
Added
org.opencontainers.image.{title,description,source,licenses,version,revision}to all 7 Dockerfiles (the 5 cgo clients had none).version/revisioncome from build-args (STATE_ACTOR_VERSION/ newSTATE_ACTOR_REVISION), wired through the Makefile and deploy workflow.Verification
internal/manifestpackage with unit tests (version resolution, spec sidecar, load/round-trip);go build ./...,go vet,gofmtclean.--seed=0reproduced to the identical state root via the captured resolved seed; spec runs reproduce via the sidecar; same-dir guard and usage verified.version= git-describe,vcs_revisionpopulated, OCI labels present (verified on alpine + debian-cgo variants), and all six produce the identical genesis state root (instrumentation is side-effect-free).Docs
Added a "Reproduce a run from its manifest" task to
docs/SKILL.md.Example
Generate a besu DB from
examples/spec-minimal.yamlvia the Docker image:The datadir then contains the manifest plus the content-addressed spec sidecar:
state-actor-manifest.json:{ "schema_version": 1, "state_actor": { "version": "a2099cf-dirty", "go_version": "go1.25.11", "os": "linux", "arch": "arm64", "vcs_revision": "a2099cf6dc853cea687a7d2a47172bcdd617d330", "vcs_time": "2026-06-25T09:51:55Z", "vcs_modified": true }, "generated_at": "2026-06-25T12:51:13Z", "command": [ "/usr/local/bin/state-actor", "--client=besu", "--db=/data", "--spec=/specs/spec-minimal.yaml", "--seed=42", "--chain-id=1337" ], "flags": { "client": "besu", "db": "/data", "seed": 42, "seed_input": 42, "fork": "osaka", "fork_input": "", "chain_id": 1337, "gas_limit": 30000000, "timestamp": 0, "binary_trie": false, "group_depth": 8, "archive": false, "spec_path": "/specs/spec-minimal.yaml" }, "spec": { "input_path": "/specs/spec-minimal.yaml", "sha256": "5309e337ea6c0f88fb86f318c500ed197c1488636216c429ea0051d0824013e4", "output_file": "state-actor-spec-5309e337ea6c0f88fb86f318c500ed197c1488636216c429ea0051d0824013e4.yaml" }, "result": { "state_root": "0x123d9412948ca0f92f8b855420b9f34ee36be5dfaf2b1101baf0f6743e1c1cfd", "accounts_created": 1, "contracts_created": 5, "total_db_size_bytes": 201287, "elapsed_ms": 53 } }Reproduce it
Recover the run into a fresh directory and verify it matches:
mkdir -p /tmp/sa-spec-repro && \ docker run --rm \ -v /tmp/sa-spec:/orig:ro \ -v /tmp/sa-spec-repro:/out \ ghcr.io/ethereum/state-actor-besu:main \ reproduce --manifest /orig/state-actor-manifest.json --db /outNotes:
/orig) and the new output at/out. The same-dir guard compares container paths, and the manifest recordeddb=/data, so the reproduce--dbmust differ (/out)./origmust contain both the manifest and itsstate-actor-spec-<sha>.yamlsidecar — reproduce reads the spec from beside the manifest.