diff --git a/.github/workflows/surfpool-solana.yml b/.github/workflows/surfpool-solana.yml new file mode 100644 index 00000000..92d5292c --- /dev/null +++ b/.github/workflows/surfpool-solana.yml @@ -0,0 +1,106 @@ +name: "Surfpool Fuzz: Solana" + +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + +env: + # Use the upstream prebuilt binary instead of `cargo install` (which is a + # ~10 minute compile). Pin the tarball's SHA256 so a force-pushed release + # asset can't silently change what we install. + SURFPOOL_VERSION: v1.1.1 + SURFPOOL_SHA256: e17b44331ce3baa58fe530a061d6dc6df0e288521fec0bd5892333854ffeecf5 + +jobs: + surfpool: + # Two guards: + # 1. On `labeled` events only run when the label being added is `surfpool`. + # Without this, the workflow's own `surfpool-failure` add (and any other + # label change on a PR that happens to already carry `surfpool`) would + # re-fire the workflow. + # 2. Skip fork PRs: secrets aren't passed across forks, so HELIUS_API_KEY + # would be empty and `gh pr edit` would lack write permissions. + if: | + ( + (github.event.action == 'labeled' && github.event.label.name == 'surfpool') + || + (github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'surfpool')) + ) + && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest-4-cores + permissions: + pull-requests: write + env: + HELIUS_API_KEY: ${{ secrets.HELIUS_API_KEY }} + PROPTEST_CASES: 32 + steps: + - name: git checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0 + - name: Cache surfpool binary + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: ~/.local/bin/surfpool + key: ${{ runner.os }}-surfpool-bin-${{ env.SURFPOOL_VERSION }}-${{ env.SURFPOOL_SHA256 }} + - name: Cache Rust dependencies + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + src/target/ + key: ${{ runner.os }}-cargo-surfpool-${{ hashFiles('src/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-surfpool- + - name: install protoc + uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0 + with: + version: "21.4" + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: free disk space + run: | + sudo swapoff -a + sudo rm -f /swapfile + sudo apt clean + df -h + - name: Install surfpool + run: | + mkdir -p "${HOME}/.local/bin" + echo "${HOME}/.local/bin" >> "${GITHUB_PATH}" + # `surfpool --version` is a stronger gate than `command -v`: a + # corrupted cached binary will fail this check and trigger redownload. + if "${HOME}/.local/bin/surfpool" --version >/dev/null 2>&1; then + exit 0 + fi + tarball="$(mktemp)" + curl -fsSL -o "${tarball}" \ + "https://github.com/txtx/surfpool/releases/download/${SURFPOOL_VERSION}/surfpool-linux-x64.tar.gz" + echo "${SURFPOOL_SHA256} ${tarball}" | sha256sum -c - + tar xzf "${tarball}" -C "${HOME}/.local/bin" + rm -f "${tarball}" + "${HOME}/.local/bin/surfpool" --version + - name: Run codegen + run: make -C src generated + - name: Run surfpool fuzz against all embedded IDLs + # Tests are gated by `#[ignore]` so they only run on `--ignored`. + # `--test-threads=1` serialises the surfpool spawns so each test owns + # its mainnet fork; otherwise 14 surfpools start simultaneously and + # contend for ports / RPC quota. + id: surfpool + continue-on-error: true + working-directory: src + run: cargo test -p visualsign-solana --test surfpool_fuzz -- --ignored --test-threads=1 + - name: Label PR on surfpool failure + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + if [ "${{ steps.surfpool.outcome }}" = "failure" ]; then + gh pr edit "$PR_NUMBER" --add-label surfpool-failure || true + else + gh pr edit "$PR_NUMBER" --remove-label surfpool-failure || true + fi diff --git a/CLAUDE.md b/CLAUDE.md index 4277fdd3..1880f716 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,6 +74,7 @@ Raw tx bytes → ChainPlugin (CLI) or gRPC request - Integration tests in `integration/tests/` use gRPC client against built binaries - `test_utils` module in `visualsign` provides shared test helpers - Place all `use` imports at the top of the test module, not inside individual test functions +- Surfpool-backed integration tests (`visualsign-solana/tests/surfpool_fuzz.rs`) are `#[ignore]` and require the `surfpool` binary on `$PATH`. Each `idl_test!(...)` invocation references a `pub const` from `solana_parser::solana::embedded_idls`, so IDL contents are baked in at compile time and the macro expands into one `#[tokio::test]` discoverable by cargo. Run with `HELIUS_API_KEY= cargo test -p visualsign-solana --test surfpool_fuzz -- --ignored --test-threads=1`. Adding a new IDL: once it's exposed as a `pub const` upstream in `solana_parser`, add an `idl_test!(name, CONST)` line. ### Local Dev Container diff --git a/scripts/fuzz_all_idls.sh b/scripts/fuzz_all_idls.sh index 7a0a123c..8b1b3dad 100755 --- a/scripts/fuzz_all_idls.sh +++ b/scripts/fuzz_all_idls.sh @@ -32,7 +32,7 @@ # PROPTEST_CASES=1000 ./scripts/fuzz_all_idls.sh # ./scripts/fuzz_all_idls.sh /path/to/extra.json ... # append extra IDLs # -# Requirements: cargo, python3 +# Requirements: cargo (builds idl-meta from workspace automatically) set -euo pipefail @@ -42,25 +42,11 @@ CASES="${PROPTEST_CASES:-256}" # ── Locate the solana_parser IDL directory via cargo metadata ───────────────── -IDL_DIR="$(python3 - "$WORKSPACE_TOML" <<'PY' -import json, os, subprocess, sys - -manifest = sys.argv[1] -result = subprocess.run( - ["cargo", "metadata", "--manifest-path", manifest, "--format-version", "1"], - capture_output=True, text=True, check=True, -) -data = json.loads(result.stdout) -for pkg in data["packages"]: - if pkg["name"] == "solana_parser": - idl_dir = os.path.join(os.path.dirname(pkg["manifest_path"]), "src", "solana", "idls") - if os.path.isdir(idl_dir): - print(idl_dir) - sys.exit(0) -print("error: solana_parser IDL directory not found", file=sys.stderr) -sys.exit(1) -PY -)" +# Build the idl-meta tool once (shares the workspace build cache). +cargo build --manifest-path "$WORKSPACE_TOML" -p idl-meta --quiet + +IDL_META="cargo run --manifest-path $WORKSPACE_TOML -p idl-meta --quiet --" +IDL_DIR="$($IDL_META locate-idls --manifest-path "$WORKSPACE_TOML")" # ── Collect IDL files: embedded + any extras passed as arguments ────────────── @@ -93,14 +79,7 @@ for idl_file in "${IDL_FILES[@]}"; do name="$(basename "$idl_file" .json)" # Get instruction/type counts - read -r inst_count type_count < <(python3 -c " -import json, sys -try: - d = json.load(open(sys.argv[1])) - print(len(d.get('instructions', [])), len(d.get('types', []))) -except Exception: - print(0, 0) -" "$idl_file") + read -r inst_count type_count < <($IDL_META counts "$idl_file") printf "%-30s %13s %7s " "$name" "$inst_count" "$type_count" diff --git a/src/Cargo.lock b/src/Cargo.lock index abf54322..496300ce 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -718,8 +718,8 @@ dependencies = [ "rand 0.8.5", "rcgen", "ring", - "rustls", - "rustls-webpki", + "rustls 0.23.35", + "rustls-webpki 0.103.8", "serde", "serde_json", "socket2 0.5.10", @@ -729,7 +729,7 @@ dependencies = [ "tokio-util", "tower 0.4.13", "tracing", - "x509-parser", + "x509-parser 0.17.0", ] [[package]] @@ -1191,14 +1191,30 @@ dependencies = [ "serde", ] +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive 0.4.0", + "asn1-rs-impl 0.1.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "asn1-rs" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", + "asn1-rs-derive 0.6.0", + "asn1-rs-impl 0.2.0", "displaydoc", "nom", "num-traits", @@ -1207,6 +1223,18 @@ dependencies = [ "time", ] +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + [[package]] name = "asn1-rs-derive" version = "0.6.0" @@ -1219,6 +1247,17 @@ dependencies = [ "synstructure 0.13.2", ] +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "asn1-rs-impl" version = "0.2.0" @@ -1230,6 +1269,17 @@ dependencies = [ "syn 2.0.112", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-compression" version = "0.4.36" @@ -1243,6 +1293,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -1413,7 +1474,7 @@ dependencies = [ "sha1", "sync_wrapper 1.0.2", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.28.0", "tower 0.5.2", "tower-layer", "tower-service", @@ -1505,6 +1566,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -1996,6 +2063,15 @@ dependencies = [ "serde", ] +[[package]] +name = "caps" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1ddba47aba30b6a889298ad0109c3b8dcb0e8fc993b459daa7067d46f865e0" +dependencies = [ + "libc", +] + [[package]] name = "cbc" version = "0.1.2" @@ -2017,6 +2093,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -2158,7 +2240,7 @@ checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "serde", "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -2177,6 +2259,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compression-codecs" version = "0.4.35" @@ -2197,6 +2289,15 @@ version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "consensus-config" version = "0.1.0" @@ -2229,6 +2330,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", + "unicode-width 0.2.2", "windows-sys 0.59.0", ] @@ -2327,6 +2429,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -2385,6 +2497,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -2674,13 +2795,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs 0.5.2", + "displaydoc", + "nom", + "num-bigint 0.4.6", + "num-traits", + "rusticata-macros", +] + [[package]] name = "der-parser" version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.1", "displaydoc", "nom", "num-bigint 0.4.6", @@ -2842,6 +2977,29 @@ dependencies = [ "syn 2.0.112", ] +[[package]] +name = "dlopen2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.112", +] + [[package]] name = "dunce" version = "1.0.5" @@ -3176,6 +3334,33 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + [[package]] name = "eyre" version = "0.6.12" @@ -3186,6 +3371,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher 1.0.1", +] + [[package]] name = "fastcrypto" version = "0.1.8" @@ -3981,6 +4178,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "histogram" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cb882ccb290b8646e554b157ab0b71e64e8d5bef775cd66b6531e52d302669" + [[package]] name = "hkdf" version = "0.12.4" @@ -4174,10 +4377,10 @@ dependencies = [ "http 1.4.0", "hyper 1.8.1", "hyper-util", - "rustls", + "rustls 0.23.35", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower-service", "webpki-roots 1.0.5", ] @@ -4360,6 +4563,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idl-meta" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", +] + [[package]] name = "idna" version = "1.1.0" @@ -4471,6 +4683,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width 0.2.2", + "web-time", +] + [[package]] name = "inline_colorization" version = "0.1.6" @@ -4599,6 +4824,50 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.112", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -4628,6 +4897,21 @@ dependencies = [ "tabled", ] +[[package]] +name = "jsonrpc-core" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +dependencies = [ + "futures", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "jupiter-swap-api-client" version = "0.2.0" @@ -5265,7 +5549,7 @@ dependencies = [ "indexmap 2.12.1", "leb128", "move-proc-macros", - "num", + "num 0.4.3", "once_cell", "primitive-types 0.10.1", "rand 0.8.5", @@ -5599,12 +5883,12 @@ dependencies = [ "pin-project-lite", "prometheus", "rand 0.8.5", - "rustls", + "rustls 0.23.35", "serde", "snap", "sui-http", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-stream", "tonic 0.13.1", "tonic-health", @@ -5622,10 +5906,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -5675,6 +5959,19 @@ dependencies = [ "memoffset 0.9.1", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset 0.9.1", +] + [[package]] name = "no-std-compat" version = "0.4.1" @@ -5712,6 +6009,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits", +] + [[package]] name = "num" version = "0.4.3" @@ -5719,10 +6030,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint 0.4.6", - "num-complex", + "num-complex 0.4.6", "num-integer", "num-iter", - "num-rational", + "num-rational 0.4.2", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", "num-traits", ] @@ -5764,6 +6086,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -5813,18 +6145,30 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ - "num-bigint 0.4.6", + "autocfg", + "num-bigint 0.2.6", "num-integer", "num-traits", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ @@ -5885,6 +6229,12 @@ dependencies = [ "syn 2.0.112", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "nybbles" version = "0.4.6" @@ -5908,13 +6258,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs 0.5.2", +] + [[package]] name = "oid-registry" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.1", ] [[package]] @@ -5967,6 +6326,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -6066,7 +6431,7 @@ checksum = "ae7891b22598926e4398790c8fe6447930c72a67d36d983a49d6ce682ce83290" dependencies = [ "bytecount", "fnv", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -6123,6 +6488,12 @@ dependencies = [ "syn 2.0.112", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -6274,6 +6645,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + [[package]] name = "pem" version = "3.0.6" @@ -6308,6 +6688,15 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "percentage" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd23b938276f14057220b707937bcb42fa76dda7560e57a2da30cb52d557937" +dependencies = [ + "num 0.2.1", +] + [[package]] name = "pest" version = "2.8.4" @@ -7039,7 +7428,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", + "rustls 0.23.35", "socket2 0.6.1", "thiserror 2.0.17", "tokio", @@ -7054,13 +7443,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", + "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", "rustc-hash", - "rustls", + "rustls 0.23.35", "rustls-pki-types", + "rustls-platform-verifier", "slab", "thiserror 2.0.17", "tinyvec", @@ -7283,7 +7674,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" dependencies = [ - "pem", + "pem 3.0.6", "ring", "rustls-pki-types", "time", @@ -7406,7 +7797,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", + "rustls 0.23.35", "rustls-pki-types", "serde", "serde_json", @@ -7414,7 +7805,7 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower 0.5.2", "tower-http 0.6.8", "tower-service", @@ -7425,6 +7816,21 @@ dependencies = [ "webpki-roots 1.0.5", ] +[[package]] +name = "reqwest-middleware" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" +dependencies = [ + "anyhow", + "async-trait", + "http 1.4.0", + "reqwest", + "serde", + "thiserror 1.0.69", + "tower-service", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -7664,6 +8070,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.35" @@ -7674,11 +8092,23 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -7698,6 +8128,43 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.35", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.8", + "security-framework 3.5.1", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.103.8" @@ -7806,6 +8273,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -7888,7 +8365,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -8583,6 +9073,52 @@ dependencies = [ "borsh 1.6.0", ] +[[package]] +name = "solana-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc55d1f263e0be4127daf33378d313ea0977f9ffd3fba50fa544ca26722fc695" +dependencies = [ + "async-trait", + "bincode", + "dashmap 5.5.3", + "futures", + "futures-util", + "indexmap 2.12.1", + "indicatif", + "log", + "quinn", + "rayon", + "solana-account", + "solana-client-traits", + "solana-commitment-config", + "solana-connection-cache", + "solana-epoch-info", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-keypair", + "solana-measure", + "solana-message", + "solana-pubkey 2.4.0", + "solana-pubsub-client", + "solana-quic-client", + "solana-quic-definitions", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-rpc-client-nonce-utils", + "solana-signature 2.3.0", + "solana-signer 2.2.1", + "solana-streamer", + "solana-thin-client", + "solana-time-utils", + "solana-tpu-client", + "solana-transaction", + "solana-transaction-error 2.2.1", + "solana-udp-client", + "thiserror 2.0.17", + "tokio", +] + [[package]] name = "solana-client-traits" version = "2.2.1" @@ -8677,6 +9213,29 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-connection-cache" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c1cff5ebb26aefff52f1a8e476de70ec1683f8cc6e4a8c86b615842d91f436" +dependencies = [ + "async-trait", + "bincode", + "crossbeam-channel", + "futures-util", + "indexmap 2.12.1", + "log", + "rand 0.8.5", + "rayon", + "solana-keypair", + "solana-measure", + "solana-metrics", + "solana-time-utils", + "solana-transaction-error 2.2.1", + "thiserror 2.0.17", + "tokio", +] + [[package]] name = "solana-cpi" version = "2.2.1" @@ -9265,6 +9824,12 @@ dependencies = [ "signal-hook", ] +[[package]] +name = "solana-measure" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11dcd67cd2ae6065e494b64e861e0498d046d95a61cbbf1ae3d58be1ea0f42ed" + [[package]] name = "solana-message" version = "2.4.0" @@ -9288,6 +9853,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "solana-metrics" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0375159d8460f423d39e5103dcff6e07796a5ec1850ee1fcfacfd2482a8f34b5" +dependencies = [ + "crossbeam-channel", + "gethostname", + "log", + "reqwest", + "solana-cluster-type", + "solana-sha256-hasher 2.3.0", + "solana-time-utils", + "thiserror 2.0.17", +] + [[package]] name = "solana-msg" version = "2.2.1" @@ -9312,6 +9893,27 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61515b880c36974053dd499c0510066783f0cc6ac17def0c7ef2a244874cf4a9" +[[package]] +name = "solana-net-utils" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a9e831d0f09bd92135d48c5bc79071bb59c0537b9459f1b4dec17ecc0558fa" +dependencies = [ + "anyhow", + "bincode", + "bytes", + "itertools 0.12.1", + "log", + "nix 0.30.1", + "rand 0.8.5", + "serde", + "serde_derive", + "socket2 0.5.10", + "solana-serde", + "tokio", + "url", +] + [[package]] name = "solana-nonce" version = "2.2.1" @@ -9378,6 +9980,38 @@ dependencies = [ "solana_parser", ] +[[package]] +name = "solana-perf" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37192c0be5c222ca49dbc5667288c5a8bb14837051dd98e541ee4dad160a5da9" +dependencies = [ + "ahash 0.8.12", + "bincode", + "bv", + "bytes", + "caps", + "curve25519-dalek 4.1.3", + "dlopen2", + "fnv", + "libc", + "log", + "nix 0.30.1", + "rand 0.8.5", + "rayon", + "serde", + "solana-hash 2.3.0", + "solana-message", + "solana-metrics", + "solana-packet", + "solana-pubkey 2.4.0", + "solana-rayon-threadlimit", + "solana-sdk-ids 2.2.1", + "solana-short-vec", + "solana-signature 2.3.0", + "solana-time-utils", +] + [[package]] name = "solana-poh-config" version = "2.2.1" @@ -9648,6 +10282,63 @@ dependencies = [ "solana-address 2.0.0", ] +[[package]] +name = "solana-pubsub-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18a7476e1d2e8df5093816afd8fffee94fbb6e442d9be8e6bd3e85f88ce8d5c" +dependencies = [ + "crossbeam-channel", + "futures-util", + "http 0.2.12", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-clock 2.2.2", + "solana-pubkey 2.4.0", + "solana-rpc-client-types", + "solana-signature 2.3.0", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-tungstenite 0.20.1", + "tungstenite 0.20.1", + "url", +] + +[[package]] +name = "solana-quic-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feb5f4a97494459c435aa56de810500cc24e22d0afc632990a8e54a07c05a4" +dependencies = [ + "async-lock", + "async-trait", + "futures", + "itertools 0.12.1", + "log", + "quinn", + "quinn-proto", + "rustls 0.23.35", + "solana-connection-cache", + "solana-keypair", + "solana-measure", + "solana-metrics", + "solana-net-utils", + "solana-pubkey 2.4.0", + "solana-quic-definitions", + "solana-rpc-client-api", + "solana-signer 2.2.1", + "solana-streamer", + "solana-tls-utils", + "solana-transaction-error 2.2.1", + "thiserror 2.0.17", + "tokio", +] + [[package]] name = "solana-quic-definitions" version = "2.3.1" @@ -9657,6 +10348,15 @@ dependencies = [ "solana-keypair", ] +[[package]] +name = "solana-rayon-threadlimit" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cc2a4cae3ef7bb6346b35a60756d2622c297d5fa204f96731db9194c0dc75b" +dependencies = [ + "num_cpus", +] + [[package]] name = "solana-rent" version = "2.2.1" @@ -9732,6 +10432,111 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "solana-rpc-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d3161ac0918178e674c1f7f1bfac40de3e7ed0383bd65747d63113c156eaeb" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bincode", + "bs58 0.5.1", + "futures", + "indicatif", + "log", + "reqwest", + "reqwest-middleware", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-account-decoder-client-types", + "solana-clock 2.2.2", + "solana-commitment-config", + "solana-epoch-info", + "solana-epoch-schedule 2.2.1", + "solana-feature-gate-interface", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-message", + "solana-pubkey 2.4.0", + "solana-rpc-client-api", + "solana-signature 2.3.0", + "solana-transaction", + "solana-transaction-error 2.2.1", + "solana-transaction-status-client-types", + "solana-version", + "solana-vote-interface", + "tokio", +] + +[[package]] +name = "solana-rpc-client-api" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dbc138685c79d88a766a8fd825057a74ea7a21e1dd7f8de275ada899540fff7" +dependencies = [ + "anyhow", + "jsonrpc-core", + "reqwest", + "reqwest-middleware", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-clock 2.2.2", + "solana-rpc-client-types", + "solana-signer 2.2.1", + "solana-transaction-error 2.2.1", + "solana-transaction-status-client-types", + "thiserror 2.0.17", +] + +[[package]] +name = "solana-rpc-client-nonce-utils" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f0ee41b9894ff36adebe546a110b899b0d0294b07845d8acdc73822e6af4b0" +dependencies = [ + "solana-account", + "solana-commitment-config", + "solana-hash 2.3.0", + "solana-message", + "solana-nonce", + "solana-pubkey 2.4.0", + "solana-rpc-client", + "solana-sdk-ids 2.2.1", + "thiserror 2.0.17", +] + +[[package]] +name = "solana-rpc-client-types" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea428a81729255d895ea47fba9b30fd4dacbfe571a080448121bd0592751676" +dependencies = [ + "base64 0.22.1", + "bs58 0.5.1", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-account-decoder-client-types", + "solana-clock 2.2.2", + "solana-commitment-config", + "solana-fee-calculator 2.2.1", + "solana-inflation", + "solana-pubkey 2.4.0", + "solana-transaction-error 2.2.1", + "solana-transaction-status-client-types", + "solana-version", + "spl-generic-token", + "thiserror 2.0.17", +] + [[package]] name = "solana-sanitize" version = "2.2.1" @@ -10173,6 +10978,53 @@ dependencies = [ "solana-sysvar-id 2.2.1", ] +[[package]] +name = "solana-streamer" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5643516e5206b89dd4bdf67c39815606d835a51a13260e43349abdb92d241b1d" +dependencies = [ + "async-channel", + "bytes", + "crossbeam-channel", + "dashmap 5.5.3", + "futures", + "futures-util", + "governor", + "histogram", + "indexmap 2.12.1", + "itertools 0.12.1", + "libc", + "log", + "nix 0.30.1", + "pem 1.1.1", + "percentage", + "quinn", + "quinn-proto", + "rand 0.8.5", + "rustls 0.23.35", + "smallvec", + "socket2 0.5.10", + "solana-keypair", + "solana-measure", + "solana-metrics", + "solana-net-utils", + "solana-packet", + "solana-perf", + "solana-pubkey 2.4.0", + "solana-quic-definitions", + "solana-signature 2.3.0", + "solana-signer 2.2.1", + "solana-time-utils", + "solana-tls-utils", + "solana-transaction-error 2.2.1", + "solana-transaction-metrics-tracker", + "thiserror 2.0.17", + "tokio", + "tokio-util", + "x509-parser 0.14.0", +] + [[package]] name = "solana-svm-feature-set" version = "2.3.13" @@ -10314,12 +11166,88 @@ dependencies = [ "solana-sdk-ids 3.1.0", ] +[[package]] +name = "solana-thin-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c1025715a113e0e2e379b30a6bfe4455770dc0759dabf93f7dbd16646d5acbe" +dependencies = [ + "bincode", + "log", + "rayon", + "solana-account", + "solana-client-traits", + "solana-clock 2.2.2", + "solana-commitment-config", + "solana-connection-cache", + "solana-epoch-info", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-keypair", + "solana-message", + "solana-pubkey 2.4.0", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-signature 2.3.0", + "solana-signer 2.2.1", + "solana-system-interface 1.0.0", + "solana-transaction", + "solana-transaction-error 2.2.1", +] + [[package]] name = "solana-time-utils" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af261afb0e8c39252a04d026e3ea9c405342b08c871a2ad8aa5448e068c784c" +[[package]] +name = "solana-tls-utils" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14494aa87a75a883d1abcfee00f1278a28ecc594a2f030084879eb40570728f6" +dependencies = [ + "rustls 0.23.35", + "solana-keypair", + "solana-pubkey 2.4.0", + "solana-signer 2.2.1", + "x509-parser 0.14.0", +] + +[[package]] +name = "solana-tpu-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17895ce70fd1dd93add3fbac87d599954ded93c63fa1c66f702d278d96a6da14" +dependencies = [ + "async-trait", + "bincode", + "futures-util", + "indexmap 2.12.1", + "indicatif", + "log", + "rayon", + "solana-client-traits", + "solana-clock 2.2.2", + "solana-commitment-config", + "solana-connection-cache", + "solana-epoch-schedule 2.2.1", + "solana-measure", + "solana-message", + "solana-net-utils", + "solana-pubkey 2.4.0", + "solana-pubsub-client", + "solana-quic-definitions", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-signature 2.3.0", + "solana-signer 2.2.1", + "solana-transaction", + "solana-transaction-error 2.2.1", + "thiserror 2.0.17", + "tokio", +] + [[package]] name = "solana-transaction" version = "2.2.3" @@ -10386,6 +11314,22 @@ dependencies = [ "solana-sanitize 3.0.1", ] +[[package]] +name = "solana-transaction-metrics-tracker" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fc4e1b6252dc724f5ee69db6229feb43070b7318651580d2174da8baefb993" +dependencies = [ + "base64 0.22.1", + "bincode", + "log", + "rand 0.8.5", + "solana-packet", + "solana-perf", + "solana-short-vec", + "solana-signature 2.3.0", +] + [[package]] name = "solana-transaction-status" version = "2.3.13" @@ -10453,12 +11397,43 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "solana-udp-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd36227dd3035ac09a89d4239551d2e3d7d9b177b61ccc7c6d393c3974d0efa" +dependencies = [ + "async-trait", + "solana-connection-cache", + "solana-keypair", + "solana-net-utils", + "solana-streamer", + "solana-transaction-error 2.2.1", + "thiserror 2.0.17", + "tokio", +] + [[package]] name = "solana-validator-exit" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bbf6d7a3c0b28dd5335c52c0e9eae49d0ae489a8f324917faf0ded65a812c1d" +[[package]] +name = "solana-version" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3324d46c7f7b7f5d34bf7dc71a2883bdc072c7b28ca81d0b2167ecec4cf8da9f" +dependencies = [ + "agave-feature-set", + "rand 0.8.5", + "semver 1.0.27", + "serde", + "serde_derive", + "solana-sanitize 2.2.1", + "solana-serde-varint", +] + [[package]] name = "solana-vote-interface" version = "2.2.6" @@ -10588,6 +11563,17 @@ dependencies = [ "solana_idl", ] +[[package]] +name = "solana_test_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "solana-client", + "solana-sdk", + "tokio", + "tracing", +] + [[package]] name = "spin" version = "0.9.8" @@ -11778,7 +12764,7 @@ dependencies = [ "pin-project-lite", "socket2 0.5.10", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-util", "tower 0.5.2", "tracing", @@ -12014,7 +13000,7 @@ dependencies = [ "tonic 0.13.1", "tracing", "typed-store-error", - "x509-parser", + "x509-parser 0.17.0", ] [[package]] @@ -12096,7 +13082,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -12118,7 +13104,7 @@ checksum = "0ce69a5028cd9576063ec1f48edb2c75339fd835e6094ef3e05b3a079bf594a6" dependencies = [ "papergrid", "tabled_derive", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -12304,6 +13290,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.6.1", @@ -12342,13 +13329,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.35", "tokio", ] @@ -12364,6 +13361,21 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", + "tungstenite 0.20.1", + "webpki-roots 0.25.4", +] + [[package]] name = "tokio-tungstenite" version = "0.28.0" @@ -12373,7 +13385,7 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.28.0", ] [[package]] @@ -12491,7 +13503,7 @@ dependencies = [ "prost 0.13.5", "socket2 0.5.10", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-stream", "tower 0.5.2", "tower-layer", @@ -12617,13 +13629,18 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "async-compression", "bitflags 2.10.0", "bytes", + "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower 0.5.2", "tower-layer", "tower-service", @@ -12770,6 +13787,27 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.21.12", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", + "webpki-roots 0.24.0", +] + [[package]] name = "tungstenite" version = "0.28.0" @@ -12878,6 +13916,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -13053,11 +14097,13 @@ dependencies = [ "solana-system-interface 1.0.0", "solana-transaction-status", "solana_parser", + "solana_test_utils", "spl-associated-token-account 6.0.0", "spl-stake-pool", "spl-token 7.0.0", "spl-token-2022 10.0.0", "spl-token-2022-interface", + "tokio", "tracing", "visualsign", ] @@ -13126,7 +14172,7 @@ dependencies = [ "elliptic-curve-tools", "generic-array 1.3.5", "hex", - "num", + "num 0.4.3", "rand_core 0.6.4", "serde", "sha3", @@ -13285,6 +14331,30 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" +dependencies = [ + "rustls-webpki 0.101.7", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "webpki-roots" version = "0.26.11" @@ -13416,6 +14486,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -13452,6 +14531,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -13485,6 +14579,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -13497,6 +14597,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -13509,6 +14615,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -13533,6 +14645,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -13545,6 +14663,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -13557,6 +14681,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -13569,6 +14699,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -13628,18 +14764,36 @@ dependencies = [ "spki 0.7.3", ] +[[package]] +name = "x509-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" +dependencies = [ + "asn1-rs 0.5.2", + "base64 0.13.1", + "data-encoding", + "der-parser 8.2.0", + "lazy_static", + "nom", + "oid-registry 0.6.1", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "x509-parser" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.1", "data-encoding", - "der-parser", + "der-parser 10.0.0", "lazy_static", "nom", - "oid-registry", + "oid-registry 0.8.1", "ring", "rusticata-macros", "thiserror 2.0.17", diff --git a/src/Cargo.toml b/src/Cargo.toml index c4e42db5..d0d6ff8c 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -17,6 +17,8 @@ members = [ "chain_parsers/visualsign-sui", "chain_parsers/visualsign-tron", "chain_parsers/visualsign-unspecified", + "solana_test_utils", + "tools/idl-meta", ] resolver = "3" diff --git a/src/chain_parsers/visualsign-solana/Cargo.toml b/src/chain_parsers/visualsign-solana/Cargo.toml index a07d5b3f..25c082ea 100644 --- a/src/chain_parsers/visualsign-solana/Cargo.toml +++ b/src/chain_parsers/visualsign-solana/Cargo.toml @@ -26,12 +26,14 @@ spl-token-2022-interface = "2.1.0" [dev-dependencies] solana-parser-fuzz-core = { git = "https://github.com/anchorageoss/solana-parser.git", rev = "a0c554d", features = ["proptest"] } +solana_test_utils = { path = "../../solana_test_utils" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" jupiter-swap-api-client = "0.2.0" base64 = "0.22.1" bs58 = "0.5" proptest = "1" +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } [lints] workspace = true diff --git a/src/chain_parsers/visualsign-solana/tests/surfpool_fuzz.rs b/src/chain_parsers/visualsign-solana/tests/surfpool_fuzz.rs new file mode 100644 index 00000000..2a7147bb --- /dev/null +++ b/src/chain_parsers/visualsign-solana/tests/surfpool_fuzz.rs @@ -0,0 +1,130 @@ +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] +//! Surfpool-backed integration tests for the Solana visual-sign parser. +//! +//! Tests are network-bound (start a `surfpool` mainnet fork; require the +//! `surfpool` binary on `$PATH`) and are therefore `#[ignore]`. Each test +//! references a `solana_parser::solana::embedded_idls::*` const directly, +//! so the IDL contents are baked in at compile time -- no filesystem +//! lookup, no env var, no `cargo metadata`. +//! +//! Run all surfpool tests: +//! +//! ```bash +//! HELIUS_API_KEY= cargo test \ +//! --manifest-path src/Cargo.toml -p visualsign-solana \ +//! --test surfpool_fuzz -- --ignored --test-threads=1 +//! ``` +//! +//! Run a single IDL: +//! +//! ```bash +//! cargo test ... --test surfpool_fuzz surfpool_idl_jupiter -- --ignored +//! ``` +//! +//! Adding a new IDL: once it's exposed as a `pub const` in +//! `solana_parser::solana::embedded_idls`, add an `idl_test!(name, CONST)` +//! line below; cargo's harness picks it up. + +mod common; + +use common::{build_transaction, options_with_idl}; +use solana_parser::decode_idl_data; +use solana_parser::solana::embedded_idls::{ + APE_PRO_IDL, CANDY_MACHINE_IDL, DRIFT_IDL, JUPITER_AGG_V6_IDL, JUPITER_IDL, JUPITER_LIMIT_IDL, + KAMINO_IDL, LIFINITY_IDL, METEORA_IDL, OPENBOOK_IDL, ORCA_IDL, RAYDIUM_IDL, STABBLE_IDL, +}; +use solana_sdk::pubkey::Pubkey; +use solana_test_utils::{SurfpoolConfig, SurfpoolManager}; +use visualsign::vsptrait::{Transaction, VisualSignConverter}; +use visualsign_solana::{SolanaTransactionWrapper, SolanaVisualSignConverter}; + +/// Smoke test: start surfpool, verify the RPC responds, let `Drop` tear it down. +#[tokio::test(flavor = "multi_thread")] +#[ignore] +async fn surfpool_lifecycle() { + let manager = SurfpoolManager::start(SurfpoolConfig::default()) + .await + .expect("surfpool should start"); + + let client = manager.rpc_client(); + let version = client + .get_version() + .expect("RPC should respond with a version"); + + assert!( + !version.solana_core.is_empty(), + "solana_core version string must not be empty" + ); +} + +/// Per-IDL roundtrip: decode the IDL, build a synthetic transaction whose data +/// starts with the first instruction's discriminator, run it through the +/// visual-sign converter, and assert the payload is non-empty. +async fn run_idl_roundtrip(idl_label: &str, idl_json: &str) { + // Distinguish the three failure modes explicitly so a red test names the + // IDL and the actual cause (decode rejection from a malformed IDL, empty + // instruction list, or a missing discriminator). + let idl = decode_idl_data(idl_json) + .unwrap_or_else(|e| panic!("{idl_label}: decode_idl_data rejected the IDL: {e}")); + assert!( + !idl.instructions.is_empty(), + "{idl_label}: IDL has no instructions" + ); + let disc = idl.instructions[0] + .discriminator + .as_ref() + .unwrap_or_else(|| panic!("{idl_label}: instructions[0] has no discriminator")); + let mut data = disc.clone(); + data.extend_from_slice(&[0u8; 32]); + + let _manager = SurfpoolManager::start(SurfpoolConfig::default()) + .await + .expect("surfpool should start"); + + let program_id = Pubkey::new_unique(); + let tx = build_transaction(program_id, vec![Pubkey::new_unique()], data); + let tx_bytes = bincode::serialize(&tx).expect("tx should serialize"); + let tx_b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &tx_bytes); + + let wrapper = SolanaTransactionWrapper::from_string(&tx_b64) + .expect("from_string should succeed for a valid base64 transaction"); + + let options = options_with_idl(&program_id, idl_json, "test_program"); + let payload = SolanaVisualSignConverter + .to_visual_sign_payload(wrapper, options) + .expect("converter should succeed"); + + assert!( + !payload.fields.is_empty(), + "payload must contain at least one field" + ); +} + +macro_rules! idl_test { + ($name:ident, $idl:expr) => { + #[tokio::test(flavor = "multi_thread")] + #[ignore] + async fn $name() { + run_idl_roundtrip(stringify!($name), $idl).await; + } + }; +} + +// `collision.json` and `cyclic.json` exist in `solana_parser`'s `idls/` +// directory but are negative test fixtures (duplicate type names / cyclic +// type refs); they're rejected by `decode_idl_data` and therefore not +// exposed via `embedded_idls`. + +idl_test!(surfpool_idl_ape_pro, APE_PRO_IDL); +idl_test!(surfpool_idl_cndy, CANDY_MACHINE_IDL); +idl_test!(surfpool_idl_drift, DRIFT_IDL); +idl_test!(surfpool_idl_jupiter, JUPITER_IDL); +idl_test!(surfpool_idl_jupiter_agg_v6, JUPITER_AGG_V6_IDL); +idl_test!(surfpool_idl_jupiter_limit, JUPITER_LIMIT_IDL); +idl_test!(surfpool_idl_kamino, KAMINO_IDL); +idl_test!(surfpool_idl_lifinity, LIFINITY_IDL); +idl_test!(surfpool_idl_meteora, METEORA_IDL); +idl_test!(surfpool_idl_openbook, OPENBOOK_IDL); +idl_test!(surfpool_idl_orca, ORCA_IDL); +idl_test!(surfpool_idl_raydium, RAYDIUM_IDL); +idl_test!(surfpool_idl_stabble, STABBLE_IDL); diff --git a/src/solana_test_utils/Cargo.toml b/src/solana_test_utils/Cargo.toml new file mode 100644 index 00000000..989615f8 --- /dev/null +++ b/src/solana_test_utils/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "solana_test_utils" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +solana-sdk = "2.1.15" +solana-client = "2.1.15" +tokio = { workspace = true, features = ["full"] } +anyhow = "1.0" +tracing = { workspace = true } + +[lints] +workspace = true diff --git a/src/solana_test_utils/src/lib.rs b/src/solana_test_utils/src/lib.rs new file mode 100644 index 00000000..b2427ebc --- /dev/null +++ b/src/solana_test_utils/src/lib.rs @@ -0,0 +1,3 @@ +pub mod surfpool; + +pub use surfpool::{SurfpoolConfig, SurfpoolManager}; diff --git a/src/solana_test_utils/src/surfpool/config.rs b/src/solana_test_utils/src/surfpool/config.rs new file mode 100644 index 00000000..ad104303 --- /dev/null +++ b/src/solana_test_utils/src/surfpool/config.rs @@ -0,0 +1,61 @@ +/// Configuration for a Surfpool validator instance. +/// +/// Maps to `surfpool start` CLI flags. See `surfpool start --help` for details. +#[derive(Debug, Clone)] +pub struct SurfpoolConfig { + /// Datasource RPC URL to fork from (`-u`/`--rpc-url`). + pub rpc_url: Option, + /// Local Simnet RPC port (`-p`/`--port`). Auto-selected if `None`. + pub port: Option, + /// Local Simnet WebSocket port (`-w`/`--ws-port`). Auto-selected if `None`. + pub ws_port: Option, + /// Log level (`-l`/`--log-level`). + pub log_level: String, + /// Use CI-adequate settings (`--ci`). + pub ci: bool, +} + +impl Default for SurfpoolConfig { + fn default() -> Self { + let rpc_url = std::env::var("HELIUS_API_KEY") + .ok() + .map(|key| format!("https://mainnet.helius-rpc.com/?api-key={key}")) + .or_else(|| std::env::var("SOLANA_RPC_URL").ok()) + .unwrap_or_else(|| "https://api.mainnet-beta.solana.com".to_string()); + + Self { + rpc_url: Some(rpc_url), + port: None, + ws_port: None, + log_level: "info".to_string(), + ci: true, + } + } +} + +impl SurfpoolConfig { + pub fn with_rpc_url(mut self, url: impl Into) -> Self { + self.rpc_url = Some(url.into()); + self + } + + pub fn with_port(mut self, port: u16) -> Self { + self.port = Some(port); + self + } + + pub fn with_ws_port(mut self, port: u16) -> Self { + self.ws_port = Some(port); + self + } + + pub fn with_log_level(mut self, level: impl Into) -> Self { + self.log_level = level.into(); + self + } + + pub fn with_ci(mut self, ci: bool) -> Self { + self.ci = ci; + self + } +} diff --git a/src/solana_test_utils/src/surfpool/manager.rs b/src/solana_test_utils/src/surfpool/manager.rs new file mode 100644 index 00000000..388163b9 --- /dev/null +++ b/src/solana_test_utils/src/surfpool/manager.rs @@ -0,0 +1,172 @@ +use super::config::SurfpoolConfig; +use anyhow::{Context, Result}; +use solana_client::rpc_client::RpcClient; +use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signature}; +use std::net::TcpListener; +use std::process::{Child, Command}; +use std::time::Duration; +use tracing::{debug, info, warn}; + +/// Manages the lifecycle of a Surfpool validator instance. +/// +/// Spawns a `surfpool` subprocess on [`start`](Self::start), polls until the +/// RPC server is ready, and kills the process on [`Drop`]. +pub struct SurfpoolManager { + process: Option, + rpc_url: String, + ws_url: String, +} + +impl SurfpoolManager { + /// Start a new Surfpool instance with the given configuration. + /// + /// Runs `surfpool start` with `--no-tui` (headless) and the flags derived + /// from [`SurfpoolConfig`]. + pub async fn start(config: SurfpoolConfig) -> Result { + info!("Starting Surfpool with config: {:?}", config); + + let rpc_port = config.port.map_or_else(Self::find_free_port, Ok)?; + let ws_port = config.ws_port.map_or_else(Self::find_free_port, Ok)?; + + let rpc_url = format!("http://127.0.0.1:{rpc_port}"); + let ws_url = format!("ws://127.0.0.1:{ws_port}"); + + let mut args = vec![ + "start".to_string(), + "--no-tui".to_string(), + "--port".to_string(), + rpc_port.to_string(), + "--ws-port".to_string(), + ws_port.to_string(), + "--log-level".to_string(), + config.log_level.clone(), + ]; + + if let Some(upstream) = &config.rpc_url { + args.push("--rpc-url".to_string()); + args.push(upstream.clone()); + } + + if config.ci { + args.push("--ci".to_string()); + } + + debug!("Spawning surfpool with args: {:?}", args); + + let child = Command::new("surfpool") + .args(&args) + .spawn() + .context("Failed to spawn surfpool process. Is surfpool installed?")?; + + let manager = Self { + process: Some(child), + rpc_url: rpc_url.clone(), + ws_url, + }; + + manager + .wait_ready() + .await + .context("Surfpool failed to become ready")?; + + info!("Surfpool started successfully at {}", rpc_url); + Ok(manager) + } + + /// Poll the RPC server until it responds (up to 30 attempts, 500ms apart). + /// + /// `RpcClient::get_version` is synchronous and blocks for up to its HTTP + /// timeout. Running it on a Tokio worker thread would stall other tasks, + /// so each probe is dispatched via `spawn_blocking` and the inter-attempt + /// delay uses `tokio::time::sleep`. + pub async fn wait_ready(&self) -> Result<()> { + let max_attempts = 30; + let delay = Duration::from_millis(500); + let rpc_url = self.rpc_url.clone(); + + for attempt in 1..=max_attempts { + debug!( + "Checking if Surfpool is ready (attempt {}/{})", + attempt, max_attempts + ); + + let url = rpc_url.clone(); + let probe = tokio::task::spawn_blocking(move || { + RpcClient::new_with_commitment(url, CommitmentConfig::confirmed()).get_version() + }) + .await + .context("Surfpool readiness probe task panicked")?; + + match probe { + Ok(version) => { + info!("Surfpool is ready! Version: {:?}", version); + return Ok(()); + } + Err(e) => { + if attempt == max_attempts { + return Err(anyhow::anyhow!( + "Surfpool did not become ready after {max_attempts} attempts: {e}" + )); + } + warn!("Surfpool not ready yet (attempt {}): {}", attempt, e); + tokio::time::sleep(delay).await; + } + } + } + + Err(anyhow::anyhow!("Surfpool readiness check failed")) + } + + /// Return an RPC client pointed at this instance. + pub fn rpc_client(&self) -> RpcClient { + RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::confirmed()) + } + + pub fn rpc_url(&self) -> &str { + &self.rpc_url + } + + pub fn ws_url(&self) -> &str { + &self.ws_url + } + + /// Request an airdrop and wait for confirmation (bounded). + pub async fn airdrop(&self, pubkey: &Pubkey, lamports: u64) -> Result { + let client = self.rpc_client(); + let signature = client + .request_airdrop(pubkey, lamports) + .context("Failed to request airdrop")?; + + let max_attempts = 60; + for _ in 0..max_attempts { + if let Ok(Some(_status)) = client.get_signature_status(&signature) { + return Ok(signature); + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + + Err(anyhow::anyhow!( + "Airdrop confirmation timed out after {max_attempts} attempts" + )) + } + + /// Find a free TCP port by binding to port 0. + fn find_free_port() -> Result { + let listener = TcpListener::bind("127.0.0.1:0").context("Failed to bind ephemeral port")?; + let port = listener + .local_addr() + .context("Failed to get local address")? + .port(); + Ok(port) + } +} + +impl Drop for SurfpoolManager { + fn drop(&mut self) { + if let Some(mut child) = self.process.take() { + info!("Stopping Surfpool process"); + let _ = child.kill(); + let _ = child.wait(); + } + } +} diff --git a/src/solana_test_utils/src/surfpool/mod.rs b/src/solana_test_utils/src/surfpool/mod.rs new file mode 100644 index 00000000..474541bb --- /dev/null +++ b/src/solana_test_utils/src/surfpool/mod.rs @@ -0,0 +1,5 @@ +mod config; +mod manager; + +pub use config::SurfpoolConfig; +pub use manager::SurfpoolManager; diff --git a/src/tools/idl-meta/Cargo.toml b/src/tools/idl-meta/Cargo.toml new file mode 100644 index 00000000..5f5980dc --- /dev/null +++ b/src/tools/idl-meta/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "idl-meta" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +serde_json = { workspace = true } +serde = { workspace = true } +anyhow = "1.0" + +[lints] +workspace = true diff --git a/src/tools/idl-meta/src/main.rs b/src/tools/idl-meta/src/main.rs new file mode 100644 index 00000000..62f9bf2c --- /dev/null +++ b/src/tools/idl-meta/src/main.rs @@ -0,0 +1,105 @@ +//! Minimal tool for IDL metadata extraction. +//! +//! Replaces the Python snippets in `scripts/fuzz_all_idls.sh`. +//! +//! Usage: +//! idl-meta locate-idls --manifest-path +//! idl-meta counts + +use anyhow::{Context, Result}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn main() -> Result<()> { + let args: Vec = std::env::args().collect(); + let subcmd = args.get(1).map(String::as_str); + + match subcmd { + Some("locate-idls") => { + let manifest_path = args + .iter() + .position(|a| a == "--manifest-path") + .and_then(|i| args.get(i + 1)) + .context("usage: idl-meta locate-idls --manifest-path ")?; + let dir = locate_idl_dir(manifest_path)?; + println!("{}", dir.display()); + } + Some("counts") => { + let file = args + .get(2) + .context("usage: idl-meta counts ")?; + let (instructions, types) = idl_counts(file)?; + println!("{instructions} {types}"); + } + _ => { + anyhow::bail!( + "usage: idl-meta [args]\n\ + \n locate-idls --manifest-path \ + Print the solana_parser IDL directory\n \ + counts \ + Print instruction and type counts" + ); + } + } + Ok(()) +} + +/// Run `cargo metadata`, find the `solana_parser` package, and return +/// `/src/solana/idls`. +fn locate_idl_dir(manifest_path: &str) -> Result { + let output = Command::new("cargo") + .args([ + "metadata", + "--manifest-path", + manifest_path, + "--format-version", + "1", + ]) + .output() + .context("failed to run `cargo metadata`")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + anyhow::bail!("cargo metadata failed: {stderr}"); + } + + let meta: serde_json::Value = + serde_json::from_slice(&output.stdout).context("failed to parse cargo metadata JSON")?; + + let packages = meta["packages"] + .as_array() + .context("no 'packages' array in cargo metadata")?; + + for pkg in packages { + if pkg["name"].as_str() == Some("solana_parser") { + let manifest = pkg["manifest_path"] + .as_str() + .context("missing manifest_path for solana_parser")?; + let pkg_dir = Path::new(manifest) + .parent() + .context("manifest_path has no parent")?; + let idl_dir = pkg_dir.join("src").join("solana").join("idls"); + if idl_dir.is_dir() { + return Ok(idl_dir); + } + anyhow::bail!( + "solana_parser found at {manifest} but IDL dir does not exist: {}", + idl_dir.display() + ); + } + } + + anyhow::bail!("package 'solana_parser' not found in cargo metadata") +} + +/// Parse an Anchor IDL JSON file and return (instruction_count, type_count). +fn idl_counts(path: &str) -> Result<(usize, usize)> { + let contents = + std::fs::read_to_string(path).with_context(|| format!("failed to read {path}"))?; + let idl: serde_json::Value = + serde_json::from_str(&contents).with_context(|| format!("invalid JSON in {path}"))?; + + let instructions = idl["instructions"].as_array().map_or(0, Vec::len); + let types = idl["types"].as_array().map_or(0, Vec::len); + Ok((instructions, types)) +}