fix(cluster-tool): verify the Solana .so at deploy - #80
Open
valthon wants to merge 1 commit into
Open
Conversation
The harness never builds the Solana program and `target/` is gitignored, so nothing tied the binary on disk to the sources beside it. A `git checkout` or rebase moved the sources while the `.so` stayed put, and the stale binary deployed silently. It did not fail as a build-provenance problem. The validator loaded it, the program mis-executed at instruction entry, and the run showed `consumed 427 of 200000 compute units` / `Access violation writing 1 bytes at address 0x32` during the outpost's init-PDAs step -- which reads as a program bug, and was diagnosed as one. `anchor-build-strict.mjs` now stamps a manifest beside each emitted `.so` (arch, byte length, sha256, and the build checkout's `git describe`). `assertProgramSoFile` checks the binary against it and fails at startup naming the mismatch. Both halves of that check are load-bearing. The sha alone proves only that the `.so` and the manifest agree, and the two are written together into the same gitignored directory -- a branch switch leaves the pair intact and mutually consistent, so a sha-only check passes on exactly the incident that motivated it. `sourceDescribe` closes that gap by comparing the build's checkout against the current one. A DIRTY build is reported, not rejected: two different dirty trees at one commit describe identically, so the stamp can only mark the binary unverifiable. Refusing to run would block ordinary local iteration for a guarantee it cannot give either way. Verification lives at `runStart` -- the one path that actually loads the binary on this host -- and NOT in the shared `resolvePrograms`, which also feeds the `start.sh` renderer. That renderer emits a script which runs later and often elsewhere: `create-external-config` clones a tree whose wire-solana was never built here, so checking against this checkout would reject a valid deployment payload while proving nothing about the machine that will run it. The remediation hint moves from `anchor build` to `npm run build:programs`, since only the wrapper writes the manifest.
valthon
force-pushed
the
fix/verify-deployed-solana-program-binary
branch
from
August 21, 2026 23:38
b2e1396 to
5e0e8be
Compare
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
The harness never builds the Solana program and
target/is gitignored, so nothing tied theliqsol_core.soon disk to the sources beside it. This makes that link explicit and checks it at the point of deploy.anchor-build-strict.mjs(wire-solana#476, merged) stampstarget/deploy/wire-build-manifest.jsonbeside each emitted.so— arch, byte length, sha256, and the build checkout'sgit describeSolanaOutpostProgramTool.assertProgramSoFilechecks the binary against that manifest and fails at startup naming the mismatchSolanaValidatorProcessSteps.runStartcalls it immediately before launching the validatorBuildRemediationHintmoves fromanchor buildtonpm run build:programs— only the wrapper writes the manifestCause
SolanaValidatorProcesslaunches with--upgradeable-program <id> <target/deploy/liqsol_core.so>, so whatever sits at that path is what executes on chain. The tool asserted only that the file existed. Nothing rebuilds the program,target/is gitignored, and the//wire-solana:buildgenrule declares only a.stamp— so the.sois an untracked side effect agit checkout/rebase leaves behind while the sources move.Observed 2026-08-21: a host carrying a day-old
.sobuilt on a different branch failed asat the
SolanaOutpost: init PDAsstep. An instruction-entry abort reads as a program bug and invites blaming whichever wire-solana PR landed most recently — in that case #409, which CI had already passed 14/14 flows on. Making the mismatch a named startup error removes that whole misattribution path.Why both halves of the check are load-bearing
The sha alone proves only that the
.soand the manifest agree — and the two are written together into the same gitignored directory. A branch switch leaves the pair behind intact and mutually consistent, so a sha-only check passes on exactly the incident that motivated it.sourceDescribecloses that gap by comparing the build's checkout against the current one.A dirty build is reported, not rejected: two different dirty trees at one commit describe identically, so the stamp can only mark the binary unverifiable. Refusing to run would block ordinary local iteration for a guarantee it cannot give either way.
Why the check lives at
runStart, not inresolveProgramsresolveProgramsis shared by two callers, and only one of them deploys:runStartStartScriptSteps.resolveSolanaValidatorConfigstart.shThe renderer emits a script that runs later and often elsewhere.
create-external-configclones a cluster into a deployable tree whosewire-solanawas never built here, so checking against this checkout would reject a valid deployment payload while proving nothing about the machine that will run it. Both callers still produce the identical argv string.Verification
pnpm build/pnpm run lint: cleanpnpm test: 225 suites, 1964 tests, 0 failures (rebased onto currentmaster)flow-swap-non-native-tokensend to end against a verified binary: SUCCEEDED, 195 steps,fatal=0The flow ran before the call site moved from
resolveProgramsup intorunStart— the same assertion on the same deploy path, one frame earlier. Happy to re-run it if you'd rather have the evidence post-move.Dependency — satisfied
The manifest this checks against is written by wire-solana#476, merged to
nexton 2026-08-21 (9e64dbf1). Sincenextis the manifest revision the e2e gate floats wire-solana to, thewire-build-manifest.jsonthis PR reads is already present on the branch CI builds — no merge-order constraint remains.🤖 Generated with Claude Code