test: surfpool mainnet-fork integration for built-in IDL fuzz path#202
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new solana_test_utils workspace crate and a surfpool-backed property test to exercise the built-in IDL lookup path end-to-end against real mainnet program IDs (via simulateTransaction on a surfpool mainnet fork).
Changes:
- Introduces
solana_test_utilscrate with shared proptest IDL byte strategies (idl_strategies) and aSurfpoolManager/SurfpoolConfigfor managing a surfpool subprocess in tests. - Adds
surfpool_real_idl_roundtrip(ignored, networked) integration property test and a helper script to run it across all embedded IDLs. - Adds/commits a set of Anchor IDL JSON files used by the surfpool fuzz script, and updates workspace/test dependencies accordingly.
Reviewed changes
Copilot reviewed 25 out of 31 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Cargo.toml | Adds solana_test_utils to the workspace members. |
| src/solana_test_utils/Cargo.toml | New crate manifest (surfpool + proptest + solana deps). |
| src/solana_test_utils/src/lib.rs | Exposes idl_strategies and surfpool modules. |
| src/solana_test_utils/src/idl_strategies/mod.rs | Re-exports shared IDL byte strategies. |
| src/solana_test_utils/src/idl_strategies/bytes.rs | Implements borsh-correct proptest byte generation for IDL types/instructions. |
| src/solana_test_utils/src/surfpool/mod.rs | Wires up SurfpoolConfig/SurfpoolManager. |
| src/solana_test_utils/src/surfpool/config.rs | Default config for surfpool fork URL + RPC port selection. |
| src/solana_test_utils/src/surfpool/manager.rs | Spawns/kills surfpool and provides readiness polling + RpcClient. |
| src/solana_test_utils/tests/surfpool_fuzz.rs | New ignored Tokio property test that simulates + parses real-program IDL instructions. |
| src/solana_test_utils/.gitignore | Attempts to ignore surfpool runtime state output. |
| src/solana_test_utils/idls/ape_pro.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/cndy.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/drift.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/jupiter.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/jupiter_agg_v6.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/jupiter_limit.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/kamino.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/lifinity.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/meteora.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/openbook.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/orca.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/raydium.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/solana_test_utils/idls/stabble.json | Committed IDL fixture used by surfpool fuzz runner. |
| src/chain_parsers/visualsign-solana/Cargo.toml | Adds proptest under dev-dependencies for the fuzz/integration tests. |
| src/chain_parsers/visualsign-solana/tests/fuzz_idl_parsing.rs | Adds/contains property-based crash-safety + valid-data IDL parsing tests. |
| src/chain_parsers/visualsign-solana/tests/pipeline_integration.rs | Adds/contains end-to-end pipeline tests and proptests for IDL visualization. |
| src/chain_parsers/visualsign-solana/tests/fuzz_idl_parsing.proptest-regressions | Stores proptest regression seed(s). |
| scripts/fuzz_all_idls.sh | Runner script for running real-IDL fuzz tests across IDLs. |
| scripts/surfpool_fuzz_all_idls.sh | New runner script for surfpool-backed real-program IDL fuzzing across IDLs. |
| PLAN.md | Adds a planning document for surfpool + IDL-fuzz integration approach. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ea00e0a to
613c3b7
Compare
Security ReviewNo security vulnerabilities found above the reporting threshold. SummaryReviewed the PR diff covering: a new Solana surfpool integration test, a Areas examined and dismissed:
Informational (not findings)
🤖 Generated with Claude Code |
|
@prasanna-anchorage ready for re-review |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use `surfpool start` subcommand with correct flags (--port, --ws-port, --rpc-url, --no-tui, --ci) instead of obsolete top-level flags - Remove ledger_path/reset_ledger config (not surfpool concepts) - Use multi_thread tokio runtime in tests (required by solana-rpc-client) - All 15 IDLs pass surfpool integration tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wires the existing `HELIUS_API_KEY` repo secret into a label-gated CI job that runs `scripts/surfpool_fuzz_all_idls.sh` against all 13 embedded IDLs. The Solana parser's `SurfpoolConfig::default()` already prefers `HELIUS_API_KEY` over the public mainnet endpoint, so passing the secret through the job env is sufficient -- no code changes required. Modelled on `fuzz-solana.yml` and `proptest-solana.yml`: label-gated to control RPC quota usage, caches `~/.cargo/bin/surfpool` to skip the ~minute-long install on subsequent runs, and tags the PR with `surfpool-failure` on a red run. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses code review on the prior commit: 1. Pin surfpool to tag v1.1.1 (matches the version verified locally against the Helius mainnet fork) and key the cached binary on the pinned version. The previous cache used `src/Cargo.lock` hash, which is decoupled from surfpool's upstream and would silently pin CI to the first-cached binary indefinitely. Splitting the binary cache from the workspace cache also lets each invalidate independently. 2. Skip the job on fork PRs (head repo != base repo). Secrets are not passed to fork PRs, so HELIUS_API_KEY would be empty and the labeling step would lack `gh pr edit` permissions; both would silently fall back to flaky public mainnet and a no-op label step. 3. Use `surfpool --version` instead of `command -v surfpool` to gate reinstall. A corrupted cached binary (interrupted previous install, ABI mismatch after toolchain bump) is now detected and rebuilt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… install Three fixes addressing outstanding review comments and the failing CI: 1. CI install command -- the `surfpool` package was renamed to `surfpool-cli` upstream, so `cargo install surfpool --tag v1.1.1` fails with "could not find `surfpool` ... with version `*`". Drop the package name argument so cargo uses the workspace's `default-members` (`crates/cli`), which produces the `surfpool` binary regardless of future package renames within the workspace. 2. `wait_ready` no longer blocks a Tokio worker. The synchronous `RpcClient::get_version()` is now dispatched via `spawn_blocking` and the inter-attempt delay uses `tokio::time::sleep` instead of `thread::sleep`. Without this, a stalled RPC could starve other tasks on the same multi-thread runtime. 3. `surfpool_fuzz_all_idls.sh` now prints the captured cargo output on failure (both the no-summary and `N failed` paths). Previously a red IDL row printed only `FAIL (...)` and the diagnostics were silently dropped, making CI failures hard to debug. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tags are mutable -- a force-push could silently change what we install, and `--locked` only protects transitive deps (it uses the source's Cargo.lock; it does not lock the source commit). Pin to the SHA `v1.1.1` resolved to (d58df4c) so the install target is bit-for-bit reproducible. Cache key now keys on the rev. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The toolchain action exports `RUSTFLAGS: -D warnings` by default, which made the `cargo install` step fail on a benign `unused_parens` lint in upstream `surfpool-gql` v1.1.1. Deny-warnings is the right policy for our own code but wrong for installing a third-party binary at a pinned revision -- whether it compiles cleanly under future rustc is not something we want CI to gate on. Set `RUSTFLAGS=--cap-lints=warn` for just the install step so upstream warnings stay warnings without affecting any subsequent step. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the ~10-minute `cargo install` compile with a ~5-second curl of the upstream release asset. Pins the tarball's SHA256 in the workflow so a force-pushed release asset cannot silently change what we install. The binary is dropped into `~/.local/bin/` (added to `GITHUB_PATH` for subsequent steps). Cache is keyed on (version, sha256) so a checksum bump invalidates automatically. Drops `RUSTFLAGS=--cap-lints=warn` from the install step; that was only needed to work around upstream lint regressions during compile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The workflow toggles a `surfpool-failure` label on PRs. With the prior `if: contains(labels, 'surfpool')` guard, that toggle re-fires the workflow on the same PR (since the `surfpool` label is still present when the `surfpool-failure` label is added). Any other `labeled` event on a PR that happens to carry the `surfpool` label would also re-trigger unnecessarily. Match the pattern used in `.github/workflows/stagex.yml`: on `labeled` events only run when the label being added is `surfpool`; on other events (`opened`, `synchronize`, `reopened`) require the label to already be present. Addresses copilot review comment on #253 (which proposed the same guard); folded here so #253 can be closed with the workflow's review comments resolved on this PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3daa361 to
b713cc7
Compare
prasanna-anchorage
left a comment
There was a problem hiding this comment.
Approving. Rebased onto current main (1 conflict in visualsign-solana/Cargo.toml — resolved by keeping main's solana_parser rev 2146929 and adding the new solana_test_utils dev-dep). Workspace cargo check, clippy --all-targets -D warnings, cargo fmt, and cargo test --no-run all green locally. Substantive feedback from earlier review rounds has been addressed (python3 → Rust idl-meta tool, spawn_blocking for blocking RPC, SHA256-pinned tarball install, fork-PR skip, etc.).
Follow-ups worth filing as separate issues (not blocking this merge):
surfpool_jupiter_swap_roundtripstarts surfpool but never queries it — surfpool is a side effect, not a participant. Either tighten the test to actually use the fork, or rename to reflect that this is IDL-fuzz with a surfpool smoke-start.SurfpoolManager::airdropreturnsOk(signature)onOk(Some(_status))regardless of whether the innerResultis success orTransactionError. Currently unused in-tree but latent bug..surfpool/should be added to the workspace-root.gitignore— Copilot's original concern is partly unaddressed: removing the per-crate.gitignoredoesn't help when surfpool runs from the repo root.
Minor: Command::spawn inherits stdio (noisy in CI); scripts/fuzz_all_idls.sh invokes cargo run -p idl-meta per IDL inside the loop; idl-meta doesn't accept the --manifest-path=value form. None blocking.
Summary
solana_test_utilsworkspace crate exposingSurfpoolManager/SurfpoolConfig, which spawns asurfpoolsubprocess (--no-tui), polls its RPC until ready, and kills it onDrop.chain_parsers/visualsign-solana/tests/surfpool_fuzz.rs(both#[ignore], network-bound):surfpool_lifecycle— starts surfpool against a mainnet fork and verifies the RPC responds.surfpool_jupiter_swap_roundtrip— loads an Anchor IDL fromIDL_FILE, builds a synthetic transaction whose data starts with the first instruction's 8-byte discriminator, and asserts the parser returnsOkwith a non-empty payload.idl-metaRust workspace tool (src/tools/idl-meta/) that replaces the previous inlinepython3snippets inscripts/fuzz_all_idls.sh(locate-idls,counts <file>).scripts/surfpool_fuzz_all_idls.sh— runs thesurfpool_jupiter_swap_roundtriptest once per embedded IDL viaIDL_FILE=…and prints a per-IDL pass/fail table; on failure echoes the captured cargo output so CI logs show the diagnostic..github/workflows/surfpool-solana.yml— label-gated CI (surfpool), skips fork PRs (secrets unavailable), downloads the SHA256-pinned upstream prebuilt surfpool binary (~5s vs ~10min compile), runssurfpool_fuzz_all_idls.shagainst all embedded IDLs, and tags the PR withsurfpool-failureon a red run.Helius RPC
SurfpoolConfig::default()resolves the upstream fork URL from env in this order:HELIUS_API_KEY— wrapped intohttps://mainnet.helius-rpc.com/?api-key=$KEYSOLANA_RPC_URL— used as-ishttps://api.mainnet-beta.solana.com— fallback (rate-limited)The repo's
HELIUS_API_KEYsecret is plumbed into the CI job env automatically; local runs need the env var set.Test plan
surfpoollabel to this PR to trigger CI; expect13 passed, 0 failedin the per-IDL table.HELIUS_API_KEY=<key> PROPTEST_CASES=32 ./scripts/surfpool_fuzz_all_idls.sh.surfpoolbinary on PATH. Easiest is to grab the prebuilt:cargo install --git https://github.com/txtx/surfpool --rev d58df4cd7c3188561e671d8da0401487e3a60762 --locked.🤖 Generated with Claude Code