From 9c0e38f6d23324fe85f22ff205ff10db4acb833c Mon Sep 17 00:00:00 2001 From: Arthur Bretas <158767751+BretasArthur1@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:43:03 -0300 Subject: [PATCH 1/8] feat(cli): add surfpool config and integration (#3) * wip * refactor: Simplify Surfpool configuration and enhance validator argument handling - Removed the SurfpoolConfig struct and related logic, simplifying the ValidatorType enum. - Updated the command processing to accept additional arguments for the validator. - Adjusted the localnet and test functions to handle Surfpool and Solana validators more effectively. - Improved logging management for Surfpool validator. * feat(cli): Enhance argument handling for Surfpool validator - Added support for additional arguments to be passed to the test runner and validator. - Updated command definitions to include `--args` and `--validator-args` options. - Improved handling of validator arguments in the `test` and `localnet` functions. - Ensured proper routing of arguments to Surfpool when no explicit validator arguments are provided. * refactor(cli): Update ValidatorType enum and streamline argument handling * feat(cli): Introduce Surfpool configuration with validator integration * feat(cli): enable tui startup to localnet and fix some typos * feat(cli): add runbooks startup check to wait for the deployment of the program then run the tests against the local environment * chore: remove debug print * refactor(cli): change flags to be compatible with surfpool cli, add the runbooks serialization struct to make a more robust verification when waiting for runbooks * fix(cli): Improve runbook execution handling to consider failures as completed * clean up runbook execution checking logic * augment surfpool config fields * remove `SURFPOOL_RPC_URL` const * `remote_rpc_url` -> `datasource_rpc_url` * use `DEFAULT_RPC_PORT` const * fix surfpool command * add/use workflow to install surfpool * specify surfpool cli version for CI * disable surfpool logs by default; allow configuring in anchor.toml * add timeout_minutes to setup surfpool * clean up action * allow too many arguments for localnet fn * bump surfpool version for CI * start surfpool instead of validator in ci * update run-test.sh * fix setup-surfpool cache * add `.surfpool` folder to gitignore for `anchor init` * fix: apply `--offline` flag by default for surfpool * only print message about surfpool logs if logs created * feat: add block production mode to Surfpool configuration * bump surfpool versions in CI * feat: add block production mode to surfpool config for ido-pool test * feat: add sleep functionality to log tests for improved reliability * fix: update program log messages to use dynamic identifiers for better clarity * fix: skip mut error test until LiteSVM issue is resolved * fix: update surfpool CLI version to 0.11.2 in workflow files * fix: refactor testValidateAssociatedToken calls to use confirmed commitment * fix: add surfpool block production mode configuration for errors test * fix: start surfpool in daemon mode for client/example test * fix: add block production mode configuration for surfpool for misc * lint * fix: add setup step for surfpool in reusable tests workflow * add sleep in run-test.sh to execute runbooks * disable check_surfpool call in run-test.sh --------- Co-authored-by: MicaiahReid --- .github/actions/setup-surfpool/action.yaml | 24 ++ .github/workflows/no-caching-tests.yaml | 1 + .github/workflows/reusable-tests.yaml | 13 +- .github/workflows/tests.yaml | 1 + cli/src/config.rs | 117 +++++++ cli/src/lib.rs | 358 +++++++++++++++++++-- cli/src/rust_template.rs | 1 + client/example/run-test.sh | 79 ++--- client/example/setup.tx | 40 +++ client/example/txtx.yml | 9 + tests/errors/Anchor.toml | 3 + tests/errors/tests/errors.ts | 22 +- tests/ido-pool/Anchor.toml | 3 + tests/misc/Anchor.toml | 3 + tests/misc/tests/misc/misc.ts | 36 ++- 15 files changed, 618 insertions(+), 92 deletions(-) create mode 100644 .github/actions/setup-surfpool/action.yaml create mode 100644 client/example/setup.tx create mode 100644 client/example/txtx.yml diff --git a/.github/actions/setup-surfpool/action.yaml b/.github/actions/setup-surfpool/action.yaml new file mode 100644 index 0000000000..0e055662a3 --- /dev/null +++ b/.github/actions/setup-surfpool/action.yaml @@ -0,0 +1,24 @@ +name: "Setup Surfpool" +description: "Installs and caches the Surfpool CLI" +runs: + using: "composite" + steps: + - uses: actions/cache@v3 + name: Cache Surfpool Binary + id: cache-surfpool + with: + path: /usr/local/bin/surfpool + key: surfpool-${{ runner.os }}-v${{ env.SURFPOOL_CLI_VERSION }} + + - uses: nick-fields/retry@v2 + if: steps.cache-surfpool.outputs.cache-hit != 'true' + with: + retry_wait_seconds: 300 + timeout_minutes: 2 + max_attempts: 10 + retry_on: error + shell: bash + command: | + echo "Installing Surfpool version ${{ env.SURFPOOL_CLI_VERSION }}" + curl -sL https://run.surfpool.run/ | sudo bash + diff --git a/.github/workflows/no-caching-tests.yaml b/.github/workflows/no-caching-tests.yaml index a8fe3e0984..d5cec6ff03 100644 --- a/.github/workflows/no-caching-tests.yaml +++ b/.github/workflows/no-caching-tests.yaml @@ -17,3 +17,4 @@ jobs: node_version: 20.18.0 cargo_profile: release anchor_binary_name: anchor-binary-no-caching + surfpool_cli_version: 0.11.2 diff --git a/.github/workflows/reusable-tests.yaml b/.github/workflows/reusable-tests.yaml index f7c08e2948..62a8898134 100644 --- a/.github/workflows/reusable-tests.yaml +++ b/.github/workflows/reusable-tests.yaml @@ -18,12 +18,16 @@ on: anchor_binary_name: required: true type: string + surfpool_cli_version: + required: true + type: string env: CACHE: ${{ inputs.cache }} SOLANA_CLI_VERSION: ${{ inputs.solana_cli_version }} NODE_VERSION: ${{ inputs.node_version }} CARGO_PROFILE: ${{ inputs.cargo_profile }} ANCHOR_BINARY_NAME: ${{ inputs.anchor_binary_name }} + SURFPOOL_CLI_VERSION: ${{ inputs.surfpool_cli_version }} CARGO_CACHE_PATH: | ~/.cargo/bin/ ~/.cargo/registry/index/ @@ -111,6 +115,7 @@ jobs: - uses: ./.github/actions/setup/ - uses: ./.github/actions/setup-solana/ - uses: ./.github/actions/setup-ts/ + - uses: ./.github/actions/setup-surfpool/ - uses: actions/cache@v3 if: ${{ env.CACHE != 'false' }} @@ -236,6 +241,7 @@ jobs: path: client/example/target key: cargo-${{ runner.os }}-client/example-${{ env.ANCHOR_VERSION }}-${{ env.SOLANA_CLI_VERSION }}-${{ hashFiles('**/Cargo.lock') }} - uses: ./.github/actions/setup-solana/ + - uses: ./.github/actions/setup-surfpool/ - run: cd client/example && ./run-test.sh - uses: ./.github/actions/git-diff/ @@ -249,6 +255,7 @@ jobs: - uses: ./.github/actions/setup/ - uses: ./.github/actions/setup-ts/ - uses: ./.github/actions/setup-solana/ + - uses: ./.github/actions/setup-surfpool/ - uses: actions/cache@v3 if: ${{ env.CACHE != 'false' }} @@ -272,8 +279,8 @@ jobs: path: tests/bpf-upgradeable-state/target key: cargo-${{ runner.os }}-tests/bpf-upgradeable-state-${{ env.ANCHOR_VERSION }}-${{ env.SOLANA_CLI_VERSION }}-${{ hashFiles('**/Cargo.lock') }} - - run: solana-test-validator -r --quiet & - name: start validator + - run: surfpool start --ci --offline & + name: start surfpool - run: cd tests/bpf-upgradeable-state && yarn --frozen-lockfile - run: cd tests/bpf-upgradeable-state - run: cd tests/bpf-upgradeable-state && anchor build --skip-lint --ignore-keys @@ -346,6 +353,7 @@ jobs: - uses: ./.github/actions/setup/ - uses: ./.github/actions/setup-ts/ - uses: ./.github/actions/setup-solana/ + - uses: ./.github/actions/setup-surfpool/ - uses: actions/cache@v3 if: ${{ env.CACHE != 'false' }} @@ -473,6 +481,7 @@ jobs: - uses: ./.github/actions/setup/ - uses: ./.github/actions/setup-ts/ - uses: ./.github/actions/setup-solana/ + - uses: ./.github/actions/setup-surfpool/ - uses: actions/cache@v3 if: ${{ env.CACHE != 'false' }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ff4cf077b2..c91b90ba62 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -22,3 +22,4 @@ jobs: node_version: 20.18.0 cargo_profile: debug anchor_binary_name: anchor-binary + surfpool_cli_version: 0.11.2 diff --git a/cli/src/config.rs b/cli/src/config.rs index 731d72f061..56c935449d 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -28,6 +28,7 @@ use std::str::FromStr; use std::{fmt, io}; use walkdir::WalkDir; +pub const SURFPOOL_HOST: &str = "127.0.0.1"; /// Wrapper around CommitmentLevel to support case-insensitive parsing #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CaseInsensitiveCommitmentLevel(pub CommitmentLevel); @@ -328,10 +329,19 @@ pub struct Config { // Separate entry next to test_config because // "anchor localnet" only has access to the Anchor.toml, // not the Test.toml files + pub validator: Option, pub test_validator: Option, pub test_config: Option, + pub surfpool_config: Option, } +#[derive(ValueEnum, Parser, Clone, Copy, PartialEq, Eq, Debug)] +pub enum ValidatorType { + /// Use Surfpool validator (default) + Surfpool, + /// Use Solana test validator + Legacy, +} #[derive(Default, Clone, Debug, Serialize, Deserialize)] pub struct ToolchainConfig { pub anchor_version: Option, @@ -618,6 +628,7 @@ struct _Config { scripts: Option, hooks: Option, test: Option<_TestValidator>, + surfpool: Option<_SurfpoolConfig>, } #[derive(Debug, Serialize, Deserialize)] @@ -719,6 +730,7 @@ impl fmt::Display for Config { programs, workspace: (!self.workspace.members.is_empty() || !self.workspace.exclude.is_empty()) .then(|| self.workspace.clone()), + surfpool: self.surfpool_config.clone().map(Into::into), }; let cfg = toml::to_string(&cfg).expect("Must be well formed"); @@ -742,10 +754,12 @@ impl FromStr for Config { }, scripts: cfg.scripts.unwrap_or_default(), hooks: cfg.hooks.unwrap_or_default(), + validator: None, // Will be set based on CLI flags test_validator: cfg.test.map(Into::into), test_config: None, programs: cfg.programs.map_or(Ok(BTreeMap::new()), deser_programs)?, workspace: cfg.workspace.unwrap_or_default(), + surfpool_config: cfg.surfpool.map(Into::into), }) } } @@ -830,6 +844,23 @@ pub struct TestValidator { pub upgradeable: bool, } +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct SurfpoolConfig { + pub startup_wait: i32, + pub shutdown_wait: i32, + pub rpc_port: u16, + pub ws_port: Option, + pub host: String, + pub online: Option, + pub datasource_rpc_url: Option, + pub airdrop_addresses: Option>, + pub manifest_file_path: Option, + pub runbooks: Option>, + pub slot_time: Option, + pub log_level: Option, + pub block_production_mode: Option, +} + #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct _TestValidator { #[serde(skip_serializing_if = "Option::is_none")] @@ -844,6 +875,75 @@ pub struct _TestValidator { pub upgradeable: Option, } +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct _SurfpoolConfig { + #[serde(skip_serializing_if = "Option::is_none")] + pub startup_wait: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub shutdown_wait: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rpc_port: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ws_port: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub online: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub datasource_rpc_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub airdrop_addresses: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub manifest_file_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub runbooks: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub slot_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub log_level: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub block_production_mode: Option, +} + +impl From<_SurfpoolConfig> for SurfpoolConfig { + fn from(_surfpool_config: _SurfpoolConfig) -> Self { + Self { + startup_wait: _surfpool_config.startup_wait.unwrap_or(STARTUP_WAIT), + shutdown_wait: _surfpool_config.shutdown_wait.unwrap_or(SHUTDOWN_WAIT), + rpc_port: _surfpool_config.rpc_port.unwrap_or(DEFAULT_RPC_PORT), + host: _surfpool_config.host.unwrap_or(SURFPOOL_HOST.to_string()), + ws_port: _surfpool_config.ws_port, + online: _surfpool_config.online, + datasource_rpc_url: _surfpool_config.datasource_rpc_url, + airdrop_addresses: _surfpool_config.airdrop_addresses, + manifest_file_path: _surfpool_config.manifest_file_path, + runbooks: _surfpool_config.runbooks, + slot_time: _surfpool_config.slot_time, + log_level: _surfpool_config.log_level, + block_production_mode: _surfpool_config.block_production_mode, + } + } +} + +impl From for _SurfpoolConfig { + fn from(surfpool_config: SurfpoolConfig) -> Self { + Self { + startup_wait: Some(surfpool_config.startup_wait), + shutdown_wait: Some(surfpool_config.shutdown_wait), + rpc_port: Some(surfpool_config.rpc_port), + ws_port: surfpool_config.ws_port, + host: Some(surfpool_config.host), + online: surfpool_config.online, + datasource_rpc_url: surfpool_config.datasource_rpc_url, + airdrop_addresses: surfpool_config.airdrop_addresses, + manifest_file_path: surfpool_config.manifest_file_path, + runbooks: surfpool_config.runbooks, + slot_time: surfpool_config.slot_time, + log_level: surfpool_config.log_level, + block_production_mode: surfpool_config.block_production_mode, + } + } +} pub const STARTUP_WAIT: i32 = 5000; pub const SHUTDOWN_WAIT: i32 = 2000; @@ -1461,6 +1561,23 @@ impl AnchorPackage { } } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SurfnetInfoResponse { + pub runbook_executions: Vec, +} +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RunbookExecution { + #[serde(rename = "startedAt")] + pub started_at: u32, + #[serde(rename = "completedAt")] + pub completed_at: Option, + #[serde(rename = "runbookId")] + pub runbook_id: String, + pub errors: Option>, +} + #[macro_export] macro_rules! home_path { ($my_struct:ident, $path:literal) => { diff --git a/cli/src/lib.rs b/cli/src/lib.rs index cf866543df..3d0b2ac894 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,7 +1,8 @@ use crate::config::{ get_default_ledger_path, BootstrapMode, BuildConfig, Config, ConfigOverride, HookType, Manifest, PackageManager, ProgramArch, ProgramDeployment, ProgramWorkspace, ScriptsConfig, - TestValidator, WithPath, SHUTDOWN_WAIT, STARTUP_WAIT, + SurfnetInfoResponse, SurfpoolConfig, TestValidator, ValidatorType, WithPath, SHUTDOWN_WAIT, + STARTUP_WAIT, SURFPOOL_HOST, }; use anchor_client::Cluster; use anchor_lang::prelude::UpgradeableLoaderState; @@ -27,6 +28,7 @@ use solana_pubkey::Pubkey; use solana_pubsub_client::pubsub_client::{PubsubClient, PubsubClientSubscription}; use solana_rpc_client::rpc_client::RpcClient; use solana_rpc_client_api::config::{RpcTransactionLogsConfig, RpcTransactionLogsFilter}; +use solana_rpc_client_api::request::RpcRequest; use solana_rpc_client_api::response::{Response as RpcResponse, RpcLogsResponse}; use solana_signer::{EncodableKey, Signer}; use std::collections::BTreeMap; @@ -50,7 +52,6 @@ pub mod rust_template; // Version of the docker image. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const DOCKER_BUILDER_VERSION: &str = VERSION; - /// Default RPC port pub const DEFAULT_RPC_PORT: u16 = 8899; @@ -223,6 +224,9 @@ pub enum Command { /// Run the test suites under the specified path #[clap(long)] run: Vec, + /// Validator type to use for local testing + #[clap(value_enum, long, default_value = "surfpool")] + validator: ValidatorType, args: Vec, /// Environment variables to pass into the docker container #[clap(short, long, required = false)] @@ -347,6 +351,9 @@ pub enum Command { /// Architecture to use when building the program #[clap(value_enum, long, default_value = "sbf")] arch: ProgramArch, + /// Validator type to use for local testing + #[clap(value_enum, long, default_value = "surfpool")] + validator: ValidatorType, /// Environment variables to pass into the docker container #[clap(short, long, required = false)] env: Vec, @@ -1246,6 +1253,7 @@ fn process_command(opts: Opts) -> Result<()> { no_idl, detach, run, + validator, args, env, cargo_args, @@ -1261,6 +1269,7 @@ fn process_command(opts: Opts) -> Result<()> { no_idl, detach, run, + validator, args, env, cargo_args, @@ -1281,6 +1290,7 @@ fn process_command(opts: Opts) -> Result<()> { skip_deploy, skip_lint, ignore_keys, + validator, env, cargo_args, arch, @@ -1290,6 +1300,7 @@ fn process_command(opts: Opts) -> Result<()> { skip_deploy, skip_lint, ignore_keys, + validator, env, cargo_args, arch, @@ -2458,7 +2469,7 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> { fn rpc_url(cfg_override: &ConfigOverride) -> Result { let cfg = Config::discover(cfg_override)?.expect("Not in workspace"); - Ok(cluster_url(&cfg, &cfg.test_validator)) + Ok(cluster_url(&cfg, &cfg.test_validator, &cfg.surfpool_config)) } fn idl_init( @@ -3165,6 +3176,7 @@ fn test( no_idl: bool, detach: bool, tests_to_run: Vec, + validator_type: ValidatorType, extra_args: Vec, env_vars: Vec, cargo_args: Vec, @@ -3180,6 +3192,9 @@ fn test( .collect::, _>>()?; with_workspace(cfg_override, |cfg| -> Result<()> { + // Set validator type based on CLI choice + cfg.validator = Some(validator_type); + // Build if needed. if !skip_build { build( @@ -3254,9 +3269,11 @@ fn test( skip_local_validator, skip_deploy, detach, + validator_type, &cfg.test_validator, &cfg.scripts, &extra_args, + &cfg.surfpool_config, )?; } if let Some(test_config) = &cfg.test_config { @@ -3281,9 +3298,11 @@ fn test( skip_local_validator, skip_deploy, detach, + validator_type, &test_suite.1.test, &test_suite.1.scripts, &extra_args, + &cfg.surfpool_config, )?; } } @@ -3300,22 +3319,46 @@ fn run_test_suite( skip_local_validator: bool, skip_deploy: bool, detach: bool, + validator_type: ValidatorType, test_validator: &Option, scripts: &ScriptsConfig, extra_args: &[String], + surfpool_config: &Option, ) -> Result<()> { println!("\nRunning test suite: {:#?}\n", test_suite_path.as_ref()); - // Start local test validator, if needed. let mut validator_handle = None; - if is_localnet && (!skip_local_validator) { - let flags = match skip_deploy { - true => None, - false => Some(validator_flags(cfg, test_validator)?), - }; - validator_handle = Some(start_test_validator(cfg, test_validator, flags, true)?); + if is_localnet && !skip_local_validator { + match validator_type { + ValidatorType::Surfpool => { + let full_simnet_mode = false; + let flags = Some(surfpool_flags( + cfg, + surfpool_config, + full_simnet_mode, + skip_deploy, + Some(test_suite_path.as_ref()), + )?); + validator_handle = Some(start_surfpool_validator( + flags, + surfpool_config, + full_simnet_mode, + )?); + } + ValidatorType::Legacy => { + let flags = match skip_deploy { + true => None, + false => Some(validator_flags(cfg, test_validator)?), + }; + validator_handle = Some(start_solana_test_validator( + cfg, + test_validator, + flags, + true, + )?); + } + } } - - let url = cluster_url(cfg, test_validator); + let url = cluster_url(cfg, test_validator, surfpool_config); let node_options = format!( "{} {}", @@ -3345,6 +3388,7 @@ fn run_test_suite( .expect("Not able to find script for `test`") .clone(); let script_args = format!("{cmd} {}", extra_args.join(" ")); + std::process::Command::new("bash") .arg("-c") .arg(script_args) @@ -3412,7 +3456,6 @@ fn validator_flags( for mut program in cfg.read_all_programs()? { let verifiable = false; let binary_path = program.binary_path(verifiable).display().to_string(); - // Use the [programs.cluster] override and fallback to the keypair // files if no override is given. let address = programs @@ -3566,6 +3609,123 @@ fn validator_flags( Ok(flags) } +// Returns Surfpool flags. +// This flags will be passed to the Surfpool, it allows to configure the validator. +fn surfpool_flags( + cfg: &WithPath, + surfpool_config: &Option, + full_simnet_mode: bool, + skip_deploy: bool, + test_suite_path: Option<&Path>, +) -> Result> { + let programs = cfg.programs.get(&Cluster::Localnet); + let mut flags = Vec::new(); + + for mut program in cfg.read_all_programs()? { + let address = programs + .and_then(|m| m.get(&program.lib_name)) + .map(|deployment| Ok(deployment.address.to_string())) + .unwrap_or_else(|| program.pubkey().map(|p| p.to_string()))?; + if let Some(idl) = program.idl.as_mut() { + // Creating the idl files + idl.address = address; + let idl_out = Path::new("target") + .join("idl") + .join(&idl.metadata.name) + .with_extension("json"); + write_idl(idl, OutFile::File(idl_out))?; + } + } + + if let Some(config) = &surfpool_config { + if let Some(airdrop_addresses) = &config.airdrop_addresses { + for address in airdrop_addresses { + flags.push("--airdrop".to_string()); + flags.push(address.to_string()); + } + } + if let Some(datasource_rpc_url) = &config.datasource_rpc_url { + flags.push("--rpc-url".to_string()); + flags.push(datasource_rpc_url.to_string()); + } + + let host = &config.host; + flags.push("--host".to_string()); + flags.push(host.to_string()); + + let rpc_port = &config.rpc_port; + flags.push("--port".to_string()); + flags.push(rpc_port.to_string()); + + if let Some(ws_port) = &config.ws_port { + flags.push("--ws-port".to_string()); + flags.push(ws_port.to_string()); + } + + if let Some(manifest_file_path) = &config.manifest_file_path { + flags.push("--manifest-file-path".to_string()); + flags.push(manifest_file_path.to_string()); + } + + if let Some(runbooks) = &config.runbooks { + for runbook in runbooks { + flags.push("--runbook".to_string()); + flags.push(runbook.to_string()); + } + } + + if let Some(slot_time) = &config.slot_time { + flags.push("--slot-time".to_string()); + flags.push(slot_time.to_string()); + } + } + + let online = surfpool_config + .as_ref() + .and_then(|c| c.online) + .unwrap_or(false); + if !online { + flags.push("--offline".to_string()); + } + + let block_production_mode = surfpool_config + .as_ref() + .and_then(|c| c.block_production_mode.clone()) + .unwrap_or("transaction".into()); + flags.push("--block-production-mode".to_string()); + flags.push(block_production_mode); + + flags.push("--log-level".to_string()); + flags.push( + surfpool_config + .as_ref() + .and_then(|c| c.log_level.clone()) + .unwrap_or("none".into()), + ); + + if !full_simnet_mode { + flags.push("--no-tui".to_string()); + flags.push("--disable-instruction-profiling".to_string()); + flags.push("--max-profiles".to_string()); + flags.push("1".to_string()); + flags.push("--no-studio".to_string()); + } + + match skip_deploy { + true => flags.push("--no-deploy".to_string()), + false => { + // automatically generate in-memory runbooks + flags.push("--legacy-anchor-compatibility".to_string()); + if let Some(test_suite_path) = test_suite_path { + flags.push("--anchor-test-config-path".to_string()); + flags.push(test_suite_path.display().to_string()); + } + } + } + + Ok(flags) +} + /// Handle for a log streaming thread. /// /// Manages a WebSocket subscription and its associated receiver thread. @@ -3623,6 +3783,30 @@ where } fn stream_logs(config: &WithPath, rpc_url: &str) -> Result> { + // Determine validator type to use appropriate logging + match &config.validator { + Some(ValidatorType::Surfpool) => { + // For Surfpool, we don't need to stream logs via external commands + // Surfpool handles its own logging to .surfpool/logs/ directory + if config + .surfpool_config + .as_ref() + .and_then(|s| { + s.log_level + .as_ref() + .map(|l| l.to_ascii_lowercase().ne("none")) + }) + .unwrap_or(false) + { + println!("Surfpool validator logs: .surfpool/logs/ directory"); + } + Ok(vec![]) + } + Some(ValidatorType::Legacy) | None => stream_solana_logs(config, rpc_url), + } +} + +fn stream_solana_logs(config: &WithPath, rpc_url: &str) -> Result> { let program_logs_dir = Path::new(".anchor").join("program-logs"); if program_logs_dir.exists() { fs::remove_dir_all(&program_logs_dir)?; @@ -3730,7 +3914,77 @@ fn stream_logs(config: &WithPath, rpc_url: &str) -> Result>, + surfpool_config: &Option, + full_simnet_mode: bool, +) -> Result { + let rpc_url = surfpool_rpc_url(surfpool_config); + + let (test_validator_stdout, test_validator_stderr) = match full_simnet_mode { + true => (Stdio::inherit(), Stdio::inherit()), + false => (Stdio::null(), Stdio::null()), + }; + + let mut validator_handle = std::process::Command::new("surfpool") + .arg("start") + .args(flags.unwrap_or_default()) + .stdout(test_validator_stdout) + .stderr(test_validator_stderr) + .spawn() + .map_err(|e| anyhow!("Failed to spawn `surfpool`: {e}"))?; + + let client = create_client(rpc_url.clone()); + + let mut count = 0; + + let ms_wait = surfpool_config + .as_ref() + .map(|surfpool| surfpool.startup_wait) + .unwrap_or(STARTUP_WAIT); + + while count < ms_wait { + let r = client.get_latest_blockhash(); + if r.is_ok() { + break; + } + std::thread::sleep(std::time::Duration::from_millis(100)); + count += 100; + } + + if count >= ms_wait { + eprintln!( + "Unable to get latest blockhash. Surfpool validator does not look started. \ + Check .surfpool/logs/ directory for errors. Consider increasing [surfpool.startup_wait] in Anchor.toml." + ); + validator_handle.kill()?; + std::process::exit(1); + } + + loop { + let resp = client + .send::>( + RpcRequest::Custom { + method: "surfnet_getSurfnetInfo", + }, + serde_json::Value::Null, + )? + .value; + + // break out if all runbooks are completed + if resp + .runbook_executions + .iter() + .all(|ex| ex.completed_at.is_some()) + { + break; + } + std::thread::sleep(std::time::Duration::from_millis(500)); + } + Ok(validator_handle) +} + +fn start_solana_test_validator( cfg: &Config, test_validator: &Option, flags: Option>, @@ -3830,6 +4084,14 @@ fn test_validator_rpc_url(test_validator: &Option) -> String { } } +// Returns the URL that surfpool should be running for the given configuration +fn surfpool_rpc_url(surfpool_config: &Option) -> String { + match surfpool_config { + Some(SurfpoolConfig { host, rpc_port, .. }) => format!("http://{}:{}", host, rpc_port), + _ => format!("http://{}:{}", SURFPOOL_HOST, DEFAULT_RPC_PORT), + } +} + // Setup and return paths to the solana-test-validator ledger directory and log // files given the configuration fn test_validator_file_paths(test_validator: &Option) -> Result<(PathBuf, PathBuf)> { @@ -3867,12 +4129,18 @@ fn test_validator_file_paths(test_validator: &Option) -> Result<( Ok((ledger_path, log_path)) } -fn cluster_url(cfg: &Config, test_validator: &Option) -> String { +fn cluster_url( + cfg: &Config, + test_validator: &Option, + surfpool_config: &Option, +) -> String { let is_localnet = cfg.provider.cluster == Cluster::Localnet; match is_localnet { - // Cluster is Localnet, assume the intent is to use the configuration - // for solana-test-validator - true => test_validator_rpc_url(test_validator), + // Cluster is Localnet, determine which validator to use + true => match &cfg.validator { + Some(ValidatorType::Surfpool) => surfpool_rpc_url(surfpool_config), + Some(ValidatorType::Legacy) | None => test_validator_rpc_url(test_validator), + }, false => cfg.provider.cluster.url().to_string(), } } @@ -3938,7 +4206,7 @@ fn deploy( ) -> Result<()> { // Execute the code within the workspace with_workspace(cfg_override, |cfg| -> Result<()> { - let url = cluster_url(cfg, &cfg.test_validator); + let url = cluster_url(cfg, &cfg.test_validator, &cfg.surfpool_config); let keypair = cfg.provider.wallet.to_string(); // Augment the given solana args with recommended defaults. @@ -4008,7 +4276,7 @@ fn migrate(cfg_override: &ConfigOverride) -> Result<()> { with_workspace(cfg_override, |cfg| -> Result<()> { println!("Running migration deploy script"); - let url = cluster_url(cfg, &cfg.test_validator); + let url = cluster_url(cfg, &cfg.test_validator, &cfg.surfpool_config); let cur_dir = std::env::current_dir()?; let migrations_dir = cur_dir.join("migrations"); let deploy_ts = Path::new("deploy.ts"); @@ -4307,7 +4575,7 @@ fn shell(cfg_override: &ConfigOverride) -> Result<()> { .collect::>(), } }; - let url = cluster_url(cfg, &cfg.test_validator); + let url = cluster_url(cfg, &cfg.test_validator, &cfg.surfpool_config); let js_code = rust_template::node_shell(&url, &cfg.provider.wallet.to_string(), programs)?; let mut child = std::process::Command::new("node") .args(["-e", &js_code, "-i", "--experimental-repl-await"]) @@ -4326,7 +4594,7 @@ fn shell(cfg_override: &ConfigOverride) -> Result<()> { fn run(cfg_override: &ConfigOverride, script: String, script_args: Vec) -> Result<()> { with_workspace(cfg_override, |cfg| -> Result<()> { - let url = cluster_url(cfg, &cfg.test_validator); + let url = cluster_url(cfg, &cfg.test_validator, &cfg.surfpool_config); let script = cfg .scripts .get(&script) @@ -4512,6 +4780,7 @@ fn localnet( skip_deploy: bool, skip_lint: bool, ignore_keys: bool, + validator_type: ValidatorType, env_vars: Vec, cargo_args: Vec, arch: ProgramArch, @@ -4540,14 +4809,37 @@ fn localnet( )?; } - let flags = match skip_deploy { - true => None, - false => Some(validator_flags(cfg, &cfg.test_validator)?), + let validator_handle: Option = match validator_type { + ValidatorType::Surfpool => { + let full_simnet_mode = true; + let flags = Some(surfpool_flags( + cfg, + &cfg.surfpool_config, + full_simnet_mode, + skip_deploy, + None, + )?); + Some(start_surfpool_validator( + flags, + &cfg.surfpool_config, + full_simnet_mode, + )?) + } + ValidatorType::Legacy => { + let flags = match skip_deploy { + true => None, + false => Some(validator_flags(cfg, &cfg.test_validator)?), + }; + Some(start_solana_test_validator( + cfg, + &cfg.test_validator, + flags, + false, + )?) + } }; - let validator_handle = &mut start_test_validator(cfg, &cfg.test_validator, flags, false)?; - - // Setup log reader - kept alive until end of scope + // Setup log reader. let url = test_validator_rpc_url(&cfg.test_validator); let log_streams = match stream_logs(cfg, &url) { Ok(streams) => { @@ -4567,12 +4859,10 @@ fn localnet( std::io::stdin().lock().lines().next().unwrap().unwrap(); // Check all errors and shut down. - if let Err(err) = validator_handle.kill() { - println!( - "Failed to kill subprocess {}: {}", - validator_handle.id(), - err - ); + if let Some(mut handle) = validator_handle { + if let Err(err) = handle.kill() { + println!("Failed to kill subprocess {}: {}", handle.id(), err); + } } // Explicitly shutdown log streams - closes WebSocket subscriptions diff --git a/cli/src/rust_template.rs b/cli/src/rust_template.rs index ab8e078782..ec41c45d1f 100644 --- a/cli/src/rust_template.rs +++ b/cli/src/rust_template.rs @@ -563,6 +563,7 @@ target node_modules test-ledger .yarn +.surfpool "# } diff --git a/client/example/run-test.sh b/client/example/run-test.sh index 14bd4f9ab1..909fe657d5 100755 --- a/client/example/run-test.sh +++ b/client/example/run-test.sh @@ -42,17 +42,7 @@ main() { # # Bootup validator. # - solana-test-validator -r \ - --bpf-program $composite_pid ../../tests/composite/target/deploy/composite.so \ - --bpf-program $basic_2_pid ../../examples/tutorial/basic-2/target/deploy/basic_2.so \ - --bpf-program $basic_4_pid ../../examples/tutorial/basic-4/target/deploy/basic_4.so \ - --bpf-program $events_pid ../../tests/events/target/deploy/events.so \ - --bpf-program $optional_pid ../../tests/optional/target/deploy/optional.so \ - > test-validator.log & - test_validator_pid=$! - - sleep 5 - check_solana_validator_running $test_validator_pid + surfpool_pid=$(start_surfpool) # # Run single threaded test. @@ -67,18 +57,8 @@ main() { # # Restart validator for multithreaded test # - cleanup - solana-test-validator -r \ - --bpf-program $composite_pid ../../tests/composite/target/deploy/composite.so \ - --bpf-program $basic_2_pid ../../examples/tutorial/basic-2/target/deploy/basic_2.so \ - --bpf-program $basic_4_pid ../../examples/tutorial/basic-4/target/deploy/basic_4.so \ - --bpf-program $events_pid ../../tests/events/target/deploy/events.so \ - --bpf-program $optional_pid ../../tests/optional/target/deploy/optional.so \ - > test-validator.log & - test_validator_pid=$! - - sleep 5 - check_solana_validator_running $test_validator_pid + cleanup $surfpool_pid + surfpool_pid=$(start_surfpool) # # Run multi threaded test. @@ -94,18 +74,8 @@ main() { # # Restart validator for async test # - cleanup - solana-test-validator -r \ - --bpf-program $composite_pid ../../tests/composite/target/deploy/composite.so \ - --bpf-program $basic_2_pid ../../examples/tutorial/basic-2/target/deploy/basic_2.so \ - --bpf-program $basic_4_pid ../../examples/tutorial/basic-4/target/deploy/basic_4.so \ - --bpf-program $events_pid ../../tests/events/target/deploy/events.so \ - --bpf-program $optional_pid ../../tests/optional/target/deploy/optional.so \ - > test-validator.log & - test_validator_pid=$! - - sleep 5 - check_solana_validator_running $test_validator_pid + cleanup $surfpool_pid + surfpool_pid=$(start_surfpool) # # Run async test. @@ -121,6 +91,19 @@ main() { } cleanup() { + local surfpool_pid=${1:-} + + # Kill specific surfpool process if PID provided + if [ -n "$surfpool_pid" ]; then + echo "Killing surfpool process with PID: $surfpool_pid" + kill "$surfpool_pid" 2>/dev/null || true + # Give it a moment to shutdown gracefully + sleep 1 + # Force kill if still running + kill -9 "$surfpool_pid" 2>/dev/null || true + fi + + # Kill any remaining child processes pkill -P $$ || true wait || true } @@ -137,15 +120,37 @@ trap_add() { done } -check_solana_validator_running() { +check_surfpool() { local pid=$1 + echo "Checking surfpool with PID: $pid" exit_state=$(kill -0 "$pid" && echo 'living' || echo 'exited') if [ "$exit_state" == 'exited' ]; then - echo "Cannot start test validator, see ./test-validator.log" + echo "Cannot start surfpool" exit 1 fi } +start_surfpool() { + surfpool start --ci --offline --daemon & + local surfpool_pid=$! + + sleep 3 + + surfpool run setup -u \ + --input composite_pid=$composite_pid \ + --input basic_2_pid=$basic_2_pid \ + --input basic_4_pid=$basic_4_pid \ + --input events_pid=$events_pid \ + --input optional_pid=$optional_pid + + sleep 3 + + echo "Surfpool PID: $surfpool_pid" + # check_surfpool $surfpool_pid + + echo $surfpool_pid +} + declare -f -t trap_add trap_add 'cleanup' EXIT main diff --git a/client/example/setup.tx b/client/example/setup.tx new file mode 100644 index 0000000000..b6ab4c0e3a --- /dev/null +++ b/client/example/setup.tx @@ -0,0 +1,40 @@ +addon "svm" { + rpc_api_url = "http://localhost:8899" + network_id = "localnet" +} + +signer "authority" "svm::secret_key" { + keypair_json = "~/.config/solana/id.json" +} +output "pid" { + value = input.composite_pid +} + + +action "setup" "svm::setup_surfnet" { + deploy_program { + program_id = input.composite_pid + binary_path = "../../tests/composite/target/deploy/composite.so" + authority = signer.authority.public_key + } + deploy_program { + program_id = input.basic_2_pid + binary_path = "../../examples/tutorial/basic-2/target/deploy/basic_2.so" + authority = signer.authority.public_key + } + deploy_program { + program_id = input.basic_4_pid + binary_path = "../../examples/tutorial/basic-4/target/deploy/basic_4.so" + authority = signer.authority.public_key + } + deploy_program { + program_id = input.events_pid + binary_path = "../../tests/events/target/deploy/events.so" + authority = signer.authority.public_key + } + deploy_program { + program_id = input.optional_pid + binary_path = "../../tests/optional/target/deploy/optional.so" + authority = signer.authority.public_key + } +} diff --git a/client/example/txtx.yml b/client/example/txtx.yml new file mode 100644 index 0000000000..4efbb4460d --- /dev/null +++ b/client/example/txtx.yml @@ -0,0 +1,9 @@ +--- +name: Test Setup +id: test-setup +runbooks: + - name: setup + description: Set up surfnet + location: setup.tx +environments: + diff --git a/tests/errors/Anchor.toml b/tests/errors/Anchor.toml index 6a6719b9a4..74456dc9fe 100644 --- a/tests/errors/Anchor.toml +++ b/tests/errors/Anchor.toml @@ -10,3 +10,6 @@ test = "yarn run ts-mocha -t 1000000 tests/*.ts" [features] seeds = false + +[surfpool] +block_production_mode = "clock" diff --git a/tests/errors/tests/errors.ts b/tests/errors/tests/errors.ts index e8f322a55b..e708841e84 100644 --- a/tests/errors/tests/errors.ts +++ b/tests/errors/tests/errors.ts @@ -4,6 +4,7 @@ import { Keypair, Transaction, TransactionInstruction } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID, Token } from "@solana/spl-token"; import { assert, expect } from "chai"; import { Errors } from "../target/types/errors"; +import { sleep } from "@project-serum/common"; const withLogTest = async (callback, expectedLogs) => { let logTestOk = false; @@ -34,6 +35,13 @@ const withLogTest = async (callback, expectedLogs) => { anchor.getProvider().connection.removeOnLogsListener(listener); throw err; } + // wait for a max of 5 seconds to receive logs + for (let i = 0; i < 50; i++) { + if (logTestOk) { + break; + } + await sleep(100); + } anchor.getProvider().connection.removeOnLogsListener(listener); assert.isTrue(logTestOk); }; @@ -163,7 +171,8 @@ describe("errors", () => { } }); - it("Emits a mut error", async () => { + // Skip until LiteSVM issue is resolved: https://github.com/LiteSVM/litesvm/issues/235 + it.skip("Emits a mut error", async () => { await withLogTest(async () => { try { const tx = await program.rpc.mutError({ @@ -259,6 +268,13 @@ describe("errors", () => { await program.provider.sendAndConfirm(tx); assert.ok(false); } catch (err) { + // wait for logs to be captured + for (let i = 0; i < 20; i++) { + if (signature) { + break; + } + await sleep(100); + } anchor.getProvider().connection.removeOnLogsListener(listener); const errMsg = `Error: Raw transaction ${signature} failed ({"err":{"InstructionError":[0,{"Custom":3010}]}})`; assert.strictEqual(err.toString(), errMsg); @@ -337,9 +353,9 @@ describe("errors", () => { }, [ "Program log: AnchorError caused by account: wrong_account. Error Code: AccountOwnedByWrongProgram. Error Number: 3007. Error Message: The given account is owned by a different program than expected.", "Program log: Left:", - "Program log: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + `Program log: ${TOKEN_PROGRAM_ID}`, "Program log: Right:", - "Program log: Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS", + `Program log: ${program.programId.toString()}`, ]); }); diff --git a/tests/ido-pool/Anchor.toml b/tests/ido-pool/Anchor.toml index f38b98b038..9795af853f 100644 --- a/tests/ido-pool/Anchor.toml +++ b/tests/ido-pool/Anchor.toml @@ -9,3 +9,6 @@ ido_pool = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run mocha -t 1000000 tests/" [features] + +[surfpool] +block_production_mode = "clock" diff --git a/tests/misc/Anchor.toml b/tests/misc/Anchor.toml index e169d907e7..c30089bf20 100644 --- a/tests/misc/Anchor.toml +++ b/tests/misc/Anchor.toml @@ -12,3 +12,6 @@ remaining_accounts = "RemainingAccounts11111111111111111111111111" [workspace] exclude = ["programs/shared"] + +[surfpool] +block_production_mode = "clock" diff --git a/tests/misc/tests/misc/misc.ts b/tests/misc/tests/misc/misc.ts index 257f3507cc..4bfa73217d 100644 --- a/tests/misc/tests/misc/misc.ts +++ b/tests/misc/tests/misc/misc.ts @@ -997,13 +997,14 @@ const miscTest = ( it("Can validate associated_token constraints", async () => { const localClient = await client; - await program.rpc.testValidateAssociatedToken({ - accounts: { + await program.methods + .testValidateAssociatedToken() + .accountsStrict({ token: associatedToken, mint: localClient.publicKey, wallet: provider.wallet.publicKey, - }, - }); + }) + .rpc({ commitment: "confirmed" }); let otherMint = await Token.createMint( program.provider.connection, @@ -1016,13 +1017,14 @@ const miscTest = ( await nativeAssert.rejects( async () => { - await program.rpc.testValidateAssociatedToken({ - accounts: { + await program.methods + .testValidateAssociatedToken() + .accountsStrict({ token: associatedToken, mint: otherMint.publicKey, wallet: provider.wallet.publicKey, - }, - }); + }) + .rpc({ commitment: "confirmed" }); }, (err) => { assert.strictEqual(err.error.errorCode.number, 2009); @@ -1033,13 +1035,14 @@ const miscTest = ( it("associated_token constraints check do not allow authority change", async () => { const localClient = await client; - await program.rpc.testValidateAssociatedToken({ - accounts: { + await program.methods + .testValidateAssociatedToken() + .accountsStrict({ token: associatedToken, mint: localClient.publicKey, wallet: provider.wallet.publicKey, - }, - }); + }) + .rpc({ commitment: "confirmed" }); await localClient.setAuthority( associatedToken, @@ -1051,13 +1054,14 @@ const miscTest = ( await nativeAssert.rejects( async () => { - await program.rpc.testValidateAssociatedToken({ - accounts: { + await program.methods + .testValidateAssociatedToken() + .accountsStrict({ token: associatedToken, mint: localClient.publicKey, wallet: provider.wallet.publicKey, - }, - }); + }) + .rpc({ commitment: "confirmed" }); }, (err) => { assert.strictEqual(err.error.errorCode.number, 2015); From 78234c2f795fd6794af14654a17871fa9451914a Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 27 Oct 2025 14:01:02 -0400 Subject: [PATCH 2/8] fix: Ensure proper quoting in cleanup calls and improve process termination logic --- client/example/run-test.sh | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/client/example/run-test.sh b/client/example/run-test.sh index 909fe657d5..6d0ca12673 100755 --- a/client/example/run-test.sh +++ b/client/example/run-test.sh @@ -57,7 +57,7 @@ main() { # # Restart validator for multithreaded test # - cleanup $surfpool_pid + cleanup "$surfpool_pid" surfpool_pid=$(start_surfpool) # @@ -74,7 +74,7 @@ main() { # # Restart validator for async test # - cleanup $surfpool_pid + cleanup "$surfpool_pid" surfpool_pid=$(start_surfpool) # @@ -91,23 +91,21 @@ main() { } cleanup() { - local surfpool_pid=${1:-} - - # Kill specific surfpool process if PID provided - if [ -n "$surfpool_pid" ]; then + local surfpool_pid=$1 + + if [ -n "${surfpool_pid:-}" ]; then echo "Killing surfpool process with PID: $surfpool_pid" kill "$surfpool_pid" 2>/dev/null || true - # Give it a moment to shutdown gracefully sleep 1 - # Force kill if still running kill -9 "$surfpool_pid" 2>/dev/null || true fi - - # Kill any remaining child processes + + # Kill remaining children pkill -P $$ || true wait || true } + trap_add() { trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error" for trap_add_name in "$@"; do @@ -152,5 +150,5 @@ start_surfpool() { } declare -f -t trap_add -trap_add 'cleanup' EXIT +trap 'cleanup' EXIT main From 8ffc9a5766f4d112062c8432739a3099e2cff85d Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 27 Oct 2025 15:37:32 -0400 Subject: [PATCH 3/8] fix: Improve cleanup logic and enhance surfpool process management --- client/example/run-test.sh | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/client/example/run-test.sh b/client/example/run-test.sh index 6d0ca12673..2a42927cd7 100755 --- a/client/example/run-test.sh +++ b/client/example/run-test.sh @@ -91,17 +91,20 @@ main() { } cleanup() { - local surfpool_pid=$1 + local pid="${1:-}" - if [ -n "${surfpool_pid:-}" ]; then - echo "Killing surfpool process with PID: $surfpool_pid" - kill "$surfpool_pid" 2>/dev/null || true + if [ -n "$pid" ]; then + echo "Attempting to kill tracked surfpool process PID: $pid" + kill "$pid" 2>/dev/null || true sleep 1 - kill -9 "$surfpool_pid" 2>/dev/null || true + kill -9 "$pid" 2>/dev/null || true fi - # Kill remaining children - pkill -P $$ || true + echo "Ensuring all surfpool processes are stopped..." + pkill -9 -f 'surfpool' 2>/dev/null || true + + # Clean up any sub-processes from this script + pkill -P $$ 2>/dev/null || true wait || true } @@ -129,24 +132,25 @@ check_surfpool() { } start_surfpool() { - surfpool start --ci --offline --daemon & + surfpool start --ci --offline --daemon &>/dev/null & local surfpool_pid=$! sleep 3 + # Send setup output to stderr, not stdout surfpool run setup -u \ --input composite_pid=$composite_pid \ --input basic_2_pid=$basic_2_pid \ --input basic_4_pid=$basic_4_pid \ --input events_pid=$events_pid \ - --input optional_pid=$optional_pid + --input optional_pid=$optional_pid \ + >&2 sleep 3 - echo "Surfpool PID: $surfpool_pid" - # check_surfpool $surfpool_pid + echo "Surfpool PID: $surfpool_pid" >&2 - echo $surfpool_pid + echo "$surfpool_pid" } declare -f -t trap_add From a491813385cbc7ce9a7eac6936be9f9953bf8f7b Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 3 Dec 2025 12:06:23 -0500 Subject: [PATCH 4/8] fix: supply surfpool_config to cluster_url fn --- cli/src/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/program.rs b/cli/src/program.rs index d8c87e78d0..b04e1554e2 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -284,7 +284,7 @@ fn deploy_workspace( // For Cargo workspaces, we don't have cluster/wallet in config, so just print basic info if let Ok(Some(cfg)) = Config::discover(cfg_override) { // Anchor workspace - we have cluster/wallet config - let url = crate::cluster_url(&cfg, &cfg.test_validator); + let url = crate::cluster_url(&cfg, &cfg.test_validator, &cfg.surfpool_config); let keypair = cfg.provider.wallet.to_string(); println!("Deploying cluster: {url}"); println!("Upgrade authority: {keypair}"); From 5a7422958fa237f987e4ed9a164ef3e493eeec49 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 3 Dec 2025 12:28:18 -0500 Subject: [PATCH 5/8] fix(ci): ensure local bin path is added to GITHUB_PATH during Surfpool installation --- .github/actions/setup-surfpool/action.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/setup-surfpool/action.yaml b/.github/actions/setup-surfpool/action.yaml index 0e055662a3..e7bea5b6f3 100644 --- a/.github/actions/setup-surfpool/action.yaml +++ b/.github/actions/setup-surfpool/action.yaml @@ -21,4 +21,5 @@ runs: command: | echo "Installing Surfpool version ${{ env.SURFPOOL_CLI_VERSION }}" curl -sL https://run.surfpool.run/ | sudo bash + echo "$HOME/.local/bin" >> $GITHUB_PATH From 244f303f415f6ad0254dd9c10aa19d793978cf94 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 3 Dec 2025 12:37:52 -0500 Subject: [PATCH 6/8] ci: debug surfpool install --- .github/actions/setup-surfpool/action.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/actions/setup-surfpool/action.yaml b/.github/actions/setup-surfpool/action.yaml index e7bea5b6f3..ee6503a42d 100644 --- a/.github/actions/setup-surfpool/action.yaml +++ b/.github/actions/setup-surfpool/action.yaml @@ -21,5 +21,11 @@ runs: command: | echo "Installing Surfpool version ${{ env.SURFPOOL_CLI_VERSION }}" curl -sL https://run.surfpool.run/ | sudo bash - echo "$HOME/.local/bin" >> $GITHUB_PATH + + - run: echo "$HOME/.local/bin" >> $GITHUB_PATH + shell: bash + - run: echo "$PATH" + - run: ls -al $HOME/.local/bin + + From 0a47fc30a9124bcfedea62611cdf8c605f4548ca Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 3 Dec 2025 12:43:19 -0500 Subject: [PATCH 7/8] debug ci --- .github/actions/setup-surfpool/action.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/actions/setup-surfpool/action.yaml b/.github/actions/setup-surfpool/action.yaml index ee6503a42d..49a9f13e3b 100644 --- a/.github/actions/setup-surfpool/action.yaml +++ b/.github/actions/setup-surfpool/action.yaml @@ -21,11 +21,9 @@ runs: command: | echo "Installing Surfpool version ${{ env.SURFPOOL_CLI_VERSION }}" curl -sL https://run.surfpool.run/ | sudo bash - - - run: echo "$HOME/.local/bin" >> $GITHUB_PATH - shell: bash - - run: echo "$PATH" - - run: ls -al $HOME/.local/bin + echo "$HOME/.local/bin" >> $GITHUB_PATH + echo "$PATH" + ls -al $HOME/.local/bin From ee86e0588feadefe4bcd014775812fa48408e596 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 3 Dec 2025 12:52:23 -0500 Subject: [PATCH 8/8] ci: remove sudo from install --- .github/actions/setup-surfpool/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-surfpool/action.yaml b/.github/actions/setup-surfpool/action.yaml index 49a9f13e3b..ca29ab7fde 100644 --- a/.github/actions/setup-surfpool/action.yaml +++ b/.github/actions/setup-surfpool/action.yaml @@ -20,7 +20,7 @@ runs: shell: bash command: | echo "Installing Surfpool version ${{ env.SURFPOOL_CLI_VERSION }}" - curl -sL https://run.surfpool.run/ | sudo bash + curl -sL https://run.surfpool.run/ | bash echo "$HOME/.local/bin" >> $GITHUB_PATH echo "$PATH" ls -al $HOME/.local/bin