From 718b3004016b8d564f0576ca31e977e52a2e4518 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 13:09:36 +0000 Subject: [PATCH 01/15] feat: add surfpool auto-download and enable surfpool for all tests Add auto-download of surfpool binary to Light CLI following the prover binary pattern. The CLI now downloads surfpool from GitHub releases to ~/.config/light/bin/surfpool when --use-surfpool is used. Also set use_surfpool: true as the default in LightValidatorConfig and in all explicit test configurations across the repo. --- cli/src/commands/test-validator/index.ts | 6 + cli/src/utils/constants.ts | 4 + cli/src/utils/initTestEnv.ts | 228 ++++++++++++++---- cli/src/utils/process.ts | 3 +- cli/src/utils/processPhotonIndexer.ts | 4 + forester/tests/e2e_test.rs | 1 + forester/tests/legacy/address_v2_test.rs | 1 + forester/tests/legacy/batched_address_test.rs | 1 + .../batched_state_async_indexer_test.rs | 1 + .../legacy/batched_state_indexer_test.rs | 1 + forester/tests/legacy/batched_state_test.rs | 1 + forester/tests/legacy/e2e_test.rs | 2 + forester/tests/legacy/e2e_v1_test.rs | 2 + forester/tests/test_batch_append_spent.rs | 1 + forester/tests/test_compressible_ctoken.rs | 2 + forester/tests/test_compressible_mint.rs | 3 + forester/tests/test_compressible_pda.rs | 3 + .../compressed-token-test/tests/v1.rs | 1 + .../system-cpi-v2-test/tests/event.rs | 1 + sdk-libs/client/src/local_test_validator.rs | 46 +++- sdk-tests/client-test/tests/light_client.rs | 1 + 21 files changed, 255 insertions(+), 58 deletions(-) diff --git a/cli/src/commands/test-validator/index.ts b/cli/src/commands/test-validator/index.ts index d668c90e20..f62849d8f8 100644 --- a/cli/src/commands/test-validator/index.ts +++ b/cli/src/commands/test-validator/index.ts @@ -141,6 +141,11 @@ class SetupCommand extends Command { description: "Skip resetting the ledger.", default: false, }), + "use-surfpool": Flags.boolean({ + description: + "Use surfpool instead of solana-test-validator. Requires surfpool binary in PATH.", + default: false, + }), }; validatePrograms( @@ -272,6 +277,7 @@ class SetupCommand extends Command { : undefined, verbose: flags.verbose, skipReset: flags["skip-reset"], + useSurfpool: flags["use-surfpool"], }); this.log("\nSetup tasks completed successfully \x1b[32m✔\x1b[0m"); } diff --git a/cli/src/utils/constants.ts b/cli/src/utils/constants.ts index e3a9137737..db2547d45e 100644 --- a/cli/src/utils/constants.ts +++ b/cli/src/utils/constants.ts @@ -16,9 +16,13 @@ export const DEFAULT_CONFIG = { export const CARGO_GENERATE_TAG = "v0.18.4"; export const SOLANA_VALIDATOR_PROCESS_NAME = "solana-test-validator"; +export const SURFPOOL_PROCESS_NAME = "surfpool"; export const LIGHT_PROVER_PROCESS_NAME = "light-prover"; export const INDEXER_PROCESS_NAME = "photon"; +export const SURFPOOL_VERSION = "1.0.1"; +export const SURFPOOL_RELEASE_TAG = "v1.0.1-light"; + export const PHOTON_VERSION = "0.51.2"; // Set these to override Photon requirements with a specific git commit: diff --git a/cli/src/utils/initTestEnv.ts b/cli/src/utils/initTestEnv.ts index c98c8c29bb..d168623cf3 100644 --- a/cli/src/utils/initTestEnv.ts +++ b/cli/src/utils/initTestEnv.ts @@ -7,8 +7,10 @@ import { LIGHT_REGISTRY_TAG, LIGHT_SYSTEM_PROGRAM_TAG, SPL_NOOP_PROGRAM_TAG, + SURFPOOL_RELEASE_TAG, } from "./constants"; import path from "path"; +import os from "os"; import { downloadBinIfNotExists } from "../psp-utils"; import { confirmRpcReadiness, @@ -141,6 +143,7 @@ export async function initTestEnv({ cloneNetwork, verbose, skipReset, + useSurfpool, }: { additionalPrograms?: { address: string; path: string }[]; upgradeablePrograms?: { @@ -163,24 +166,48 @@ export async function initTestEnv({ cloneNetwork?: "devnet" | "mainnet"; verbose?: boolean; skipReset?: boolean; + useSurfpool?: boolean; }) { - // We cannot await this promise directly because it will hang the process - startTestValidator({ - additionalPrograms, - upgradeablePrograms, - skipSystemAccounts, - limitLedgerSize, - rpcPort, - gossipHost, - validatorArgs, - geyserConfig, - cloneNetwork, - verbose, - skipReset, - }); - await waitForServers([{ port: rpcPort, path: "/health" }]); - await confirmServerStability(`http://127.0.0.1:${rpcPort}/health`); - await confirmRpcReadiness(`http://127.0.0.1:${rpcPort}`); + if (useSurfpool) { + // For surfpool we can await startTestValidator because spawnBinary returns + // immediately (surfpool starts in ~30ms). For solana-test-validator we must + // NOT await because the validator is a long-running process. + await startTestValidator({ + additionalPrograms, + upgradeablePrograms, + skipSystemAccounts, + limitLedgerSize, + rpcPort, + gossipHost, + validatorArgs, + geyserConfig, + cloneNetwork, + verbose, + skipReset, + useSurfpool, + }); + // Surfpool only supports JSON-RPC POST, not GET /health. + await confirmRpcReadiness(`http://127.0.0.1:${rpcPort}`); + } else { + // We cannot await this promise directly because it will hang the process + startTestValidator({ + additionalPrograms, + upgradeablePrograms, + skipSystemAccounts, + limitLedgerSize, + rpcPort, + gossipHost, + validatorArgs, + geyserConfig, + cloneNetwork, + verbose, + skipReset, + useSurfpool, + }); + await waitForServers([{ port: rpcPort, path: "/health" }]); + await confirmServerStability(`http://127.0.0.1:${rpcPort}/health`); + await confirmRpcReadiness(`http://127.0.0.1:${rpcPort}`); + } if (prover) { const config = getConfig(); @@ -201,12 +228,22 @@ export async function initTestEnv({ const proverUrlForIndexer = prover ? `http://127.0.0.1:${proverPort}` : undefined; + + // Surfpool's first available block may not be slot 0. + // Query the RPC so Photon starts from the correct slot. + let startSlot: number | undefined; + if (useSurfpool) { + const conn = new Connection(`http://127.0.0.1:${rpcPort}`); + startSlot = await conn.getFirstAvailableBlock(); + } + await startIndexer( `http://127.0.0.1:${rpcPort}`, indexerPort, checkPhotonVersion, photonDatabaseUrl, proverUrlForIndexer, + startSlot, ); } } @@ -400,6 +437,89 @@ export async function getSolanaArgs({ return solanaArgs; } +export async function getSurfpoolArgs({ + additionalPrograms, + skipSystemAccounts, + rpcPort, + gossipHost, + downloadBinaries = true, +}: { + additionalPrograms?: { address: string; path: string }[]; + skipSystemAccounts?: boolean; + rpcPort?: number; + gossipHost?: string; + downloadBinaries?: boolean; +}): Promise> { + const dirPath = programsDirPath(); + + const args = ["start", "--offline", "--no-tui", "--no-deploy"]; + args.push("--port", String(rpcPort)); + args.push("--host", String(gossipHost)); + + // Load system programs + for (const program of SYSTEM_PROGRAMS) { + const localFilePath = programFilePath(program.name!); + if (program.name === "spl_noop.so" || downloadBinaries) { + await downloadBinIfNotExists({ + localFilePath, + dirPath, + owner: "Lightprotocol", + repoName: "light-protocol", + remoteFileName: program.name!, + tag: program.tag, + }); + } + args.push("--bpf-program", program.id, localFilePath); + } + + // Load additional programs + if (additionalPrograms) { + for (const program of additionalPrograms) { + args.push("--bpf-program", program.address, program.path); + } + } + + // Load system accounts + if (!skipSystemAccounts) { + const accountsRelPath = "../../accounts"; + const accountsPath = path.resolve(__dirname, accountsRelPath); + args.push("--account-dir", accountsPath); + } + + return args; +} + +function getSurfpoolAssetName(): string { + const platform = process.platform; // "darwin" | "linux" + const arch = process.arch; // "arm64" | "x64" + return `surfpool-${platform}-${arch}.tar.gz`; +} + +function getSurfpoolBinDir(): string { + return path.join(os.homedir(), ".config", "light", "bin"); +} + +function getSurfpoolBinaryPath(): string { + return path.join(getSurfpoolBinDir(), "surfpool"); +} + +async function ensureSurfpoolBinary(): Promise { + const binPath = getSurfpoolBinaryPath(); + const dirPath = getSurfpoolBinDir(); + const assetName = getSurfpoolAssetName(); + + await downloadBinIfNotExists({ + localFilePath: binPath, + dirPath, + owner: "Lightprotocol", + repoName: "surfpool", + remoteFileName: assetName, + tag: SURFPOOL_RELEASE_TAG, + }); + + return binPath; +} + export async function startTestValidator({ additionalPrograms, upgradeablePrograms, @@ -412,6 +532,7 @@ export async function startTestValidator({ cloneNetwork, verbose, skipReset, + useSurfpool, }: { additionalPrograms?: { address: string; path: string }[]; upgradeablePrograms?: { @@ -428,41 +549,60 @@ export async function startTestValidator({ cloneNetwork?: "devnet" | "mainnet"; verbose?: boolean; skipReset?: boolean; + useSurfpool?: boolean; }) { - const command = "solana-test-validator"; - const solanaArgs = await getSolanaArgs({ - additionalPrograms, - upgradeablePrograms, - skipSystemAccounts, - limitLedgerSize, - rpcPort, - gossipHost, - cloneNetwork, - verbose, - skipReset, - }); + if (useSurfpool) { + const command = await ensureSurfpoolBinary(); + const surfpoolArgs = await getSurfpoolArgs({ + additionalPrograms, + skipSystemAccounts, + rpcPort, + gossipHost, + }); - await killTestValidator(); + await killTestValidator(); + await killProcess("surfpool"); + await new Promise((r) => setTimeout(r, 1000)); - await new Promise((r) => setTimeout(r, 1000)); + console.log("Starting surfpool..."); + spawnBinary(command, surfpoolArgs, process.env); + } else { + const command = "solana-test-validator"; + const solanaArgs = await getSolanaArgs({ + additionalPrograms, + upgradeablePrograms, + skipSystemAccounts, + limitLedgerSize, + rpcPort, + gossipHost, + cloneNetwork, + verbose, + skipReset, + }); - // Add geyser config if provided - if (geyserConfig) { - solanaArgs.push("--geyser-plugin-config", geyserConfig); - } + await killTestValidator(); - // Add custom validator args last - if (validatorArgs) { - solanaArgs.push(...validatorArgs.split(" ")); + await new Promise((r) => setTimeout(r, 1000)); + + // Add geyser config if provided + if (geyserConfig) { + solanaArgs.push("--geyser-plugin-config", geyserConfig); + } + + // Add custom validator args last + if (validatorArgs) { + solanaArgs.push(...validatorArgs.split(" ")); + } + console.log("Starting test validator..."); + // Use spawnBinary instead of executeCommand to properly detach the process. + // This ensures the validator survives when the CLI exits (executeCommand uses + // piped stdio which causes SIGPIPE when parent exits). + // Pass process.env directly to maintain same env behavior as before. + spawnBinary(command, solanaArgs, process.env); } - console.log("Starting test validator..."); - // Use spawnBinary instead of executeCommand to properly detach the process. - // This ensures the validator survives when the CLI exits (executeCommand uses - // piped stdio which causes SIGPIPE when parent exits). - // Pass process.env directly to maintain same env behavior as before. - spawnBinary(command, solanaArgs, process.env); } export async function killTestValidator() { await killProcess("solana-test-validator"); + await killProcess("surfpool"); } diff --git a/cli/src/utils/process.ts b/cli/src/utils/process.ts index 14d555beea..2cfaa233a9 100644 --- a/cli/src/utils/process.ts +++ b/cli/src/utils/process.ts @@ -52,7 +52,8 @@ export async function killProcess(processName: string) { const processList = await find("name", processName); const targetProcesses = processList.filter( - (proc) => proc.name.includes(processName) || proc.cmd.includes(processName), + (proc) => + proc.pid !== process.pid && proc.name.includes(processName), ); for (const proc of targetProcesses) { diff --git a/cli/src/utils/processPhotonIndexer.ts b/cli/src/utils/processPhotonIndexer.ts index 1c883a121d..f64788b190 100644 --- a/cli/src/utils/processPhotonIndexer.ts +++ b/cli/src/utils/processPhotonIndexer.ts @@ -42,6 +42,7 @@ export async function startIndexer( checkPhotonVersion: boolean = true, photonDatabaseUrl?: string, proverUrl?: string, + startSlot?: number, ) { await killIndexer(); const resolvedOrNull = which.sync("photon", { nothrow: true }); @@ -65,6 +66,9 @@ export async function startIndexer( if (proverUrl) { args.push("--prover-url", proverUrl); } + if (startSlot !== undefined) { + args.push("--start-slot", startSlot.toString()); + } spawnBinary(INDEXER_PROCESS_NAME, args); await waitForServers([{ port: indexerPort, path: "/getIndexerHealth" }]); diff --git a/forester/tests/e2e_test.rs b/forester/tests/e2e_test.rs index 0f0387bb0c..c4db026a4d 100644 --- a/forester/tests/e2e_test.rs +++ b/forester/tests/e2e_test.rs @@ -276,6 +276,7 @@ async fn e2e_test() { )], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; spawn_prover().await; diff --git a/forester/tests/legacy/address_v2_test.rs b/forester/tests/legacy/address_v2_test.rs index aa3a821152..71ee957010 100644 --- a/forester/tests/legacy/address_v2_test.rs +++ b/forester/tests/legacy/address_v2_test.rs @@ -62,6 +62,7 @@ async fn test_create_v2_address() { )], upgradeable_programs: vec![], limit_ledger_size: Some(500000), + use_surfpool: true, })) .await; diff --git a/forester/tests/legacy/batched_address_test.rs b/forester/tests/legacy/batched_address_test.rs index fe5fe363d0..fc6c0af838 100644 --- a/forester/tests/legacy/batched_address_test.rs +++ b/forester/tests/legacy/batched_address_test.rs @@ -43,6 +43,7 @@ async fn test_address_batched() { )], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; let tree_params = InitAddressTreeAccountsInstructionData::test_default(); diff --git a/forester/tests/legacy/batched_state_async_indexer_test.rs b/forester/tests/legacy/batched_state_async_indexer_test.rs index adc84a823c..b74c865a61 100644 --- a/forester/tests/legacy/batched_state_async_indexer_test.rs +++ b/forester/tests/legacy/batched_state_async_indexer_test.rs @@ -83,6 +83,7 @@ async fn test_state_indexer_async_batched() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; spawn_prover().await; diff --git a/forester/tests/legacy/batched_state_indexer_test.rs b/forester/tests/legacy/batched_state_indexer_test.rs index 2b9600a7f8..4eb6a5b02d 100644 --- a/forester/tests/legacy/batched_state_indexer_test.rs +++ b/forester/tests/legacy/batched_state_indexer_test.rs @@ -44,6 +44,7 @@ async fn test_state_indexer_batched() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; diff --git a/forester/tests/legacy/batched_state_test.rs b/forester/tests/legacy/batched_state_test.rs index 3067ea3a3d..134ecc67ec 100644 --- a/forester/tests/legacy/batched_state_test.rs +++ b/forester/tests/legacy/batched_state_test.rs @@ -48,6 +48,7 @@ async fn test_state_batched() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; diff --git a/forester/tests/legacy/e2e_test.rs b/forester/tests/legacy/e2e_test.rs index 69dadc8b39..b413894361 100644 --- a/forester/tests/legacy/e2e_test.rs +++ b/forester/tests/legacy/e2e_test.rs @@ -40,6 +40,7 @@ async fn test_epoch_monitor_with_2_foresters() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; let forester_keypair1 = Keypair::new(); @@ -387,6 +388,7 @@ async fn test_epoch_double_registration() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; diff --git a/forester/tests/legacy/e2e_v1_test.rs b/forester/tests/legacy/e2e_v1_test.rs index ffe207dbea..4687dc33f6 100644 --- a/forester/tests/legacy/e2e_v1_test.rs +++ b/forester/tests/legacy/e2e_v1_test.rs @@ -41,6 +41,7 @@ async fn test_e2e_v1() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; let forester_keypair1 = Keypair::new(); @@ -384,6 +385,7 @@ async fn test_epoch_double_registration() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; diff --git a/forester/tests/test_batch_append_spent.rs b/forester/tests/test_batch_append_spent.rs index b923662ca5..bc5a71b94b 100644 --- a/forester/tests/test_batch_append_spent.rs +++ b/forester/tests/test_batch_append_spent.rs @@ -51,6 +51,7 @@ async fn test_batch_sequence() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, })) .await; diff --git a/forester/tests/test_compressible_ctoken.rs b/forester/tests/test_compressible_ctoken.rs index 4bd135b9b5..76d1cd4afe 100644 --- a/forester/tests/test_compressible_ctoken.rs +++ b/forester/tests/test_compressible_ctoken.rs @@ -194,6 +194,7 @@ async fn test_compressible_ctoken_compression() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }) .await; let mut rpc = LightClient::new(LightClientConfig::local()) @@ -365,6 +366,7 @@ async fn test_compressible_ctoken_bootstrap() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }) .await; diff --git a/forester/tests/test_compressible_mint.rs b/forester/tests/test_compressible_mint.rs index 9ace18f774..cb16391cc1 100644 --- a/forester/tests/test_compressible_mint.rs +++ b/forester/tests/test_compressible_mint.rs @@ -106,6 +106,7 @@ async fn test_compressible_mint_bootstrap() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }) .await; @@ -261,6 +262,7 @@ async fn test_compressible_mint_compression() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }) .await; @@ -436,6 +438,7 @@ async fn test_compressible_mint_subscription() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }) .await; diff --git a/forester/tests/test_compressible_pda.rs b/forester/tests/test_compressible_pda.rs index 5e32ab4e67..7335221429 100644 --- a/forester/tests/test_compressible_pda.rs +++ b/forester/tests/test_compressible_pda.rs @@ -266,6 +266,7 @@ async fn test_compressible_pda_bootstrap() { payer_pubkey_string(), )], limit_ledger_size: None, + use_surfpool: true, }) .await; @@ -456,6 +457,7 @@ async fn test_compressible_pda_compression() { payer_pubkey_string(), )], limit_ledger_size: None, + use_surfpool: true, }) .await; @@ -690,6 +692,7 @@ async fn test_compressible_pda_subscription() { payer_pubkey_string(), )], limit_ledger_size: None, + use_surfpool: true, }) .await; diff --git a/program-tests/compressed-token-test/tests/v1.rs b/program-tests/compressed-token-test/tests/v1.rs index 9c4f55d2af..5b9072b0c2 100644 --- a/program-tests/compressed-token-test/tests/v1.rs +++ b/program-tests/compressed-token-test/tests/v1.rs @@ -4892,6 +4892,7 @@ async fn test_transfer_with_photon_and_batched_tree() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }) .await; diff --git a/program-tests/system-cpi-v2-test/tests/event.rs b/program-tests/system-cpi-v2-test/tests/event.rs index 9ed2aae062..d25554354c 100644 --- a/program-tests/system-cpi-v2-test/tests/event.rs +++ b/program-tests/system-cpi-v2-test/tests/event.rs @@ -539,6 +539,7 @@ async fn generate_photon_test_data_multiple_events() { )], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }) .await; diff --git a/sdk-libs/client/src/local_test_validator.rs b/sdk-libs/client/src/local_test_validator.rs index b0b7dfbcbc..a6413aa591 100644 --- a/sdk-libs/client/src/local_test_validator.rs +++ b/sdk-libs/client/src/local_test_validator.rs @@ -13,6 +13,8 @@ pub struct LightValidatorConfig { /// Use this when the program needs a valid upgrade authority (e.g., for compression config) pub upgradeable_programs: Vec<(String, String, String)>, pub limit_ledger_size: Option, + /// Use surfpool instead of solana-test-validator + pub use_surfpool: bool, } impl Default for LightValidatorConfig { @@ -24,6 +26,7 @@ impl Default for LightValidatorConfig { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, } } } @@ -58,20 +61,37 @@ pub async fn spawn_validator(config: LightValidatorConfig) { path.push_str(" --skip-prover"); } - println!("Starting validator with command: {}", path); - - let child = Command::new("sh") - .arg("-c") - .arg(path) - .stdin(Stdio::null()) // Detach from stdin - .stdout(Stdio::null()) // Detach from stdout - .stderr(Stdio::null()) // Detach from stderr - .spawn() - .expect("Failed to start server process"); + if config.use_surfpool { + path.push_str(" --use-surfpool"); + } - // Explicitly `drop` the process to ensure we don't wait on it - std::mem::drop(child); + println!("Starting validator with command: {}", path); - tokio::time::sleep(tokio::time::Duration::from_secs(config.wait_time)).await; + if config.use_surfpool { + // The CLI starts surfpool, prover, and photon, then exits once all + // services are ready. Wait for it to finish so we know everything + // is up before the test proceeds. + let mut child = Command::new("sh") + .arg("-c") + .arg(path) + .stdin(Stdio::null()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to start server process"); + let status = child.wait().expect("Failed to wait for CLI process"); + assert!(status.success(), "CLI exited with error: {}", status); + } else { + let child = Command::new("sh") + .arg("-c") + .arg(path) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("Failed to start server process"); + std::mem::drop(child); + tokio::time::sleep(tokio::time::Duration::from_secs(config.wait_time)).await; + } } } diff --git a/sdk-tests/client-test/tests/light_client.rs b/sdk-tests/client-test/tests/light_client.rs index 22d799c888..9207e0f69c 100644 --- a/sdk-tests/client-test/tests/light_client.rs +++ b/sdk-tests/client-test/tests/light_client.rs @@ -55,6 +55,7 @@ async fn test_all_endpoints() { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }; spawn_validator(config).await; From d863b1acd57c5c10f61cc1b3daeda70675562751 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 13:11:00 +0000 Subject: [PATCH 02/15] chore: add --all-targets to clippy lint rule --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index e3624721d5..cb686dc191 100644 --- a/justfile +++ b/justfile @@ -36,7 +36,7 @@ lint: lint-rust js::lint lint-rust: cargo +nightly fmt --all -- --check - cargo clippy --workspace --all-features -- -D warnings + cargo clippy --workspace --all-features --all-targets -- -D warnings format: cargo +nightly fmt --all From f64b7dfcaf30bf9eb83f35ec7ecd2897b0209cb3 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 14:59:50 +0000 Subject: [PATCH 03/15] feat: add surfpool upgradeable program support and async warp_to_slot - Forward upgradeable programs to surfpool via --upgradeable-program flag instead of --bpf-program, enabling proper programdata account creation - Add async warp_to_slot to LightClient using reqwest for surfpool's surfnet_timeTravel RPC method - Update forester tests to use async warp_to_slot - Add local surfpool binary discovery in ensureSurfpoolBinary for development --- Cargo.lock | 2 ++ cli/src/commands/test-validator/index.ts | 5 ++-- cli/src/utils/initTestEnv.ts | 19 +++++++++++++ .../batched_state_async_indexer_test.rs | 12 +++++++++ forester/tests/test_compressible_ctoken.rs | 20 ++++++++------ forester/tests/test_compressible_pda.rs | 16 ++++++----- forester/tests/test_utils.rs | 14 ++++++++++ sdk-libs/client/Cargo.toml | 2 ++ sdk-libs/client/src/lib.rs | 1 + sdk-libs/client/src/rpc/client.rs | 27 +++++++++++++++++++ 10 files changed, 102 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4ff150b24..e4719b95fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3575,6 +3575,8 @@ dependencies = [ "num-bigint 0.4.6", "photon-api", "rand 0.8.5", + "reqwest 0.12.26", + "serde_json", "smallvec", "solana-account", "solana-account-decoder-client-types", diff --git a/cli/src/commands/test-validator/index.ts b/cli/src/commands/test-validator/index.ts index f62849d8f8..e61796d0c1 100644 --- a/cli/src/commands/test-validator/index.ts +++ b/cli/src/commands/test-validator/index.ts @@ -143,8 +143,9 @@ class SetupCommand extends Command { }), "use-surfpool": Flags.boolean({ description: - "Use surfpool instead of solana-test-validator. Requires surfpool binary in PATH.", - default: false, + "Use surfpool instead of solana-test-validator (default). Pass --no-use-surfpool to use solana-test-validator.", + default: true, + allowNo: true, }), }; diff --git a/cli/src/utils/initTestEnv.ts b/cli/src/utils/initTestEnv.ts index d168623cf3..a573f0b05e 100644 --- a/cli/src/utils/initTestEnv.ts +++ b/cli/src/utils/initTestEnv.ts @@ -439,12 +439,18 @@ export async function getSolanaArgs({ export async function getSurfpoolArgs({ additionalPrograms, + upgradeablePrograms, skipSystemAccounts, rpcPort, gossipHost, downloadBinaries = true, }: { additionalPrograms?: { address: string; path: string }[]; + upgradeablePrograms?: { + address: string; + path: string; + upgradeAuthority: string; + }[]; skipSystemAccounts?: boolean; rpcPort?: number; gossipHost?: string; @@ -479,6 +485,18 @@ export async function getSurfpoolArgs({ } } + // Load upgradeable programs with full BPF upgradeable loader account layout + if (upgradeablePrograms) { + for (const program of upgradeablePrograms) { + args.push( + "--upgradeable-program", + program.address, + program.path, + program.upgradeAuthority, + ); + } + } + // Load system accounts if (!skipSystemAccounts) { const accountsRelPath = "../../accounts"; @@ -555,6 +573,7 @@ export async function startTestValidator({ const command = await ensureSurfpoolBinary(); const surfpoolArgs = await getSurfpoolArgs({ additionalPrograms, + upgradeablePrograms, skipSystemAccounts, rpcPort, gossipHost, diff --git a/forester/tests/legacy/batched_state_async_indexer_test.rs b/forester/tests/legacy/batched_state_async_indexer_test.rs index b74c865a61..9e94fd8079 100644 --- a/forester/tests/legacy/batched_state_async_indexer_test.rs +++ b/forester/tests/legacy/batched_state_async_indexer_test.rs @@ -299,6 +299,18 @@ async fn setup_forester_pipeline( } async fn wait_for_slot(rpc: &mut LightClient, target_slot: u64) { + match rpc.warp_to_slot(target_slot).await { + Ok(_) => { + println!("warped to slot {}", target_slot); + return; + } + Err(e) => { + println!( + "warp_to_slot unavailable ({}), falling back to polling", + e + ); + } + } while rpc.get_slot().await.unwrap() < target_slot { println!( "waiting for active phase slot: {}, current slot: {}", diff --git a/forester/tests/test_compressible_ctoken.rs b/forester/tests/test_compressible_ctoken.rs index 76d1cd4afe..dd2bea5b91 100644 --- a/forester/tests/test_compressible_ctoken.rs +++ b/forester/tests/test_compressible_ctoken.rs @@ -37,8 +37,8 @@ struct ForesterContext { } /// Register a forester for epoch 0 and wait for registration phase to complete -async fn register_forester( - rpc: &mut R, +async fn register_forester( + rpc: &mut LightClient, ) -> Result> { // Create forester keypair let forester_keypair = Keypair::new(); @@ -97,9 +97,11 @@ async fn register_forester( println!("phases {:?}", phases); println!("current_slot {}", current_slot); - // Wait for registration phase - while rpc.get_slot().await? < register_phase_start { - sleep(Duration::from_millis(400)).await; + // Warp to registration phase + if rpc.get_slot().await? < register_phase_start { + rpc.warp_to_slot(register_phase_start) + .await + .expect("warp_to_slot to registration phase"); } // Register for epoch 0 @@ -123,9 +125,11 @@ async fn register_forester( current_slot, active_phase_start ); - // Wait for active phase - while rpc.get_slot().await? < active_phase_start { - sleep(Duration::from_millis(400)).await; + // Warp to active phase + if rpc.get_slot().await? < active_phase_start { + rpc.warp_to_slot(active_phase_start) + .await + .expect("warp_to_slot to active phase"); } println!("Active phase reached"); diff --git a/forester/tests/test_compressible_pda.rs b/forester/tests/test_compressible_pda.rs index 7335221429..d9a2030589 100644 --- a/forester/tests/test_compressible_pda.rs +++ b/forester/tests/test_compressible_pda.rs @@ -58,8 +58,8 @@ struct ForesterContext { } /// Register a forester for epoch 0 and wait for registration phase to complete -async fn register_forester( - rpc: &mut R, +async fn register_forester( + rpc: &mut LightClient, ) -> Result> { let forester_keypair = Keypair::new(); let forester_pubkey = forester_keypair.pubkey(); @@ -169,8 +169,10 @@ async fn register_forester( ) }; - while rpc.get_slot().await? < register_phase_start { - sleep(Duration::from_millis(400)).await; + if rpc.get_slot().await? < register_phase_start { + rpc.warp_to_slot(register_phase_start) + .await + .expect("warp_to_slot to registration phase"); } // Register for the target epoch @@ -191,8 +193,10 @@ async fn register_forester( println!("Registered for epoch {}", target_epoch); - while rpc.get_slot().await? < active_phase_start { - sleep(Duration::from_millis(400)).await; + if rpc.get_slot().await? < active_phase_start { + rpc.warp_to_slot(active_phase_start) + .await + .expect("warp_to_slot to active phase"); } println!("Active phase reached for epoch {}", target_epoch); diff --git a/forester/tests/test_utils.rs b/forester/tests/test_utils.rs index 1ae2f159d7..6e28f5f3e9 100644 --- a/forester/tests/test_utils.rs +++ b/forester/tests/test_utils.rs @@ -362,6 +362,20 @@ pub async fn get_next_active_phase_with_time( #[allow(dead_code)] pub async fn wait_for_slot(rpc: &mut LightClient, target_slot: u64) { + // Try surfpool's instant time-travel first; fall back to polling if not + // running against surfpool. + match rpc.warp_to_slot(target_slot).await { + Ok(_) => { + println!("warped to slot {}", target_slot); + return; + } + Err(e) => { + println!( + "warp_to_slot unavailable ({}), falling back to polling", + e + ); + } + } while rpc.get_slot().await.unwrap() < target_slot { println!( "waiting for active phase slot: {}, current slot: {}", diff --git a/sdk-libs/client/Cargo.toml b/sdk-libs/client/Cargo.toml index fa614d97fe..eac878dc51 100644 --- a/sdk-libs/client/Cargo.toml +++ b/sdk-libs/client/Cargo.toml @@ -66,9 +66,11 @@ num-bigint = { workspace = true } base64 = { workspace = true } bs58 = { workspace = true } tokio = { workspace = true, features = ["rt", "time"] } +reqwest = { workspace = true } futures = { workspace = true } smallvec = { workspace = true } +serde_json = { workspace = true } tracing = { workspace = true } lazy_static = { workspace = true } rand = { workspace = true } diff --git a/sdk-libs/client/src/lib.rs b/sdk-libs/client/src/lib.rs index c8e4c9fcca..3d228f3de8 100644 --- a/sdk-libs/client/src/lib.rs +++ b/sdk-libs/client/src/lib.rs @@ -42,6 +42,7 @@ //! sbf_programs: vec![], //! upgradeable_programs: vec![], //! limit_ledger_size: None, +//! use_surfpool: true, //! }; //! spawn_validator(config).await; //! diff --git a/sdk-libs/client/src/rpc/client.rs b/sdk-libs/client/src/rpc/client.rs index 09dabfa7cb..2f5c5cca0f 100644 --- a/sdk-libs/client/src/rpc/client.rs +++ b/sdk-libs/client/src/rpc/client.rs @@ -430,6 +430,33 @@ impl LightClient { "Failed to find any parseable inner instructions".to_string(), )) } + + /// Instantly advances the validator to the given slot using surfpool's + /// `surfnet_timeTravel` RPC method. This is much faster than polling + /// `get_slot` in a loop and is intended for testing against surfpool. + /// + /// Returns the `EpochInfo` after the time travel, or an error if the + /// RPC call fails (e.g. when not running against surfpool). + pub async fn warp_to_slot(&self, slot: Slot) -> Result { + let url = self.client.url(); + let body = serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "surfnet_timeTravel", + "params": [{ "absoluteSlot": slot }] + }); + let response = reqwest::Client::new() + .post(url) + .json(&body) + .send() + .await + .map_err(|e| RpcError::CustomError(format!("warp_to_slot failed: {e}")))?; + let result: serde_json::Value = response + .json() + .await + .map_err(|e| RpcError::CustomError(format!("warp_to_slot response error: {e}")))?; + Ok(result) + } } #[async_trait] From 1f316bc63a416d2023bf592314ad687a555fcf70 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 16:08:17 +0000 Subject: [PATCH 04/15] fix: rustfmt formatting in test_utils --- forester/tests/test_utils.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/forester/tests/test_utils.rs b/forester/tests/test_utils.rs index 6e28f5f3e9..c77d417a64 100644 --- a/forester/tests/test_utils.rs +++ b/forester/tests/test_utils.rs @@ -370,10 +370,7 @@ pub async fn wait_for_slot(rpc: &mut LightClient, target_slot: u64) { return; } Err(e) => { - println!( - "warp_to_slot unavailable ({}), falling back to polling", - e - ); + println!("warp_to_slot unavailable ({}), falling back to polling", e); } } while rpc.get_slot().await.unwrap() < target_slot { From 8d886a7962bab22c7e2c340f8451dddc48990b65 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 16:31:11 +0000 Subject: [PATCH 05/15] fix: add surfpool binary version checking to prevent stale binaries Check installed surfpool version before use. If the version doesn't match the expected SURFPOOL_VERSION, re-download from the release. Also update README example with use_surfpool field. --- cli/src/utils/initTestEnv.ts | 53 +++++++++++++++++++++++++++++++++++- sdk-libs/client/README.md | 1 + 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/cli/src/utils/initTestEnv.ts b/cli/src/utils/initTestEnv.ts index a573f0b05e..cbf35d78fe 100644 --- a/cli/src/utils/initTestEnv.ts +++ b/cli/src/utils/initTestEnv.ts @@ -8,7 +8,9 @@ import { LIGHT_SYSTEM_PROGRAM_TAG, SPL_NOOP_PROGRAM_TAG, SURFPOOL_RELEASE_TAG, + SURFPOOL_VERSION, } from "./constants"; +import fs from "fs"; import path from "path"; import os from "os"; import { downloadBinIfNotExists } from "../psp-utils"; @@ -22,6 +24,7 @@ import { import { killProver, startProver } from "./processProverServer"; import { killIndexer, startIndexer } from "./processPhotonIndexer"; import { Connection, PublicKey } from "@solana/web3.js"; +import { execSync } from "child_process"; type Program = { id: string; name?: string; tag?: string; path?: string }; export const SYSTEM_PROGRAMS: Program[] = [ @@ -521,11 +524,39 @@ function getSurfpoolBinaryPath(): string { return path.join(getSurfpoolBinDir(), "surfpool"); } -async function ensureSurfpoolBinary(): Promise { +function getInstalledSurfpoolVersion(): string | null { + const binaryPath = getSurfpoolBinaryPath(); + + if (!fs.existsSync(binaryPath)) { + return null; + } + + try { + const output = execSync(`"${binaryPath}" --version`, { + encoding: "utf-8", + timeout: 5000, + }).trim(); + const match = output.match(/(\d+\.\d+\.\d+)/); + return match ? match[1] : null; + } catch { + return null; + } +} + +async function downloadSurfpoolBinary(): Promise { const binPath = getSurfpoolBinaryPath(); const dirPath = getSurfpoolBinDir(); const assetName = getSurfpoolAssetName(); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + + // Remove existing binary so downloadBinIfNotExists actually downloads + if (fs.existsSync(binPath)) { + fs.unlinkSync(binPath); + } + await downloadBinIfNotExists({ localFilePath: binPath, dirPath, @@ -534,7 +565,27 @@ async function ensureSurfpoolBinary(): Promise { remoteFileName: assetName, tag: SURFPOOL_RELEASE_TAG, }); +} + +async function ensureSurfpoolBinary(): Promise { + const binPath = getSurfpoolBinaryPath(); + const installedVersion = getInstalledSurfpoolVersion(); + + if (installedVersion === SURFPOOL_VERSION) { + return binPath; + } + + if (installedVersion) { + console.log( + `Surfpool version mismatch. Expected: ${SURFPOOL_VERSION}, Found: ${installedVersion}. Downloading correct version...`, + ); + } else if (fs.existsSync(binPath)) { + console.log( + "Surfpool binary found but version could not be determined. Downloading latest version...", + ); + } + await downloadSurfpoolBinary(); return binPath; } diff --git a/sdk-libs/client/README.md b/sdk-libs/client/README.md index dca731877c..8c46cbc68c 100644 --- a/sdk-libs/client/README.md +++ b/sdk-libs/client/README.md @@ -44,6 +44,7 @@ async fn main() -> Result<(), Box> { sbf_programs: vec![], upgradeable_programs: vec![], limit_ledger_size: None, + use_surfpool: true, }; spawn_validator(config).await; From 7f7bc950a2d7f29e5ce750a0933984959b345f93 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 18:29:00 +0000 Subject: [PATCH 06/15] fix: add --no-studio flag to surfpool startup args Prevents surfpool from trying to start its explorer/studio web UI during tests, avoiding "Address already in use" errors. --- cli/src/utils/initTestEnv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/utils/initTestEnv.ts b/cli/src/utils/initTestEnv.ts index cbf35d78fe..5cb2632c31 100644 --- a/cli/src/utils/initTestEnv.ts +++ b/cli/src/utils/initTestEnv.ts @@ -461,7 +461,7 @@ export async function getSurfpoolArgs({ }): Promise> { const dirPath = programsDirPath(); - const args = ["start", "--offline", "--no-tui", "--no-deploy"]; + const args = ["start", "--offline", "--no-tui", "--no-deploy", "--no-studio"]; args.push("--port", String(rpcPort)); args.push("--host", String(gossipHost)); From a337291af5aac157c0502fdf6982ebe235bdf00e Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 20:13:07 +0000 Subject: [PATCH 07/15] fix: print full log contents when spawned binary exits with error Helps debug photon/surfpool crashes on CI by printing the log file contents instead of only the exit code. --- cli/src/utils/process.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cli/src/utils/process.ts b/cli/src/utils/process.ts index 2cfaa233a9..00d0cfee81 100644 --- a/cli/src/utils/process.ts +++ b/cli/src/utils/process.ts @@ -225,9 +225,19 @@ export function spawnBinary( spawnedProcess.on("close", async (code) => { console.log(`${binaryName} process exited with code ${code}`); - if (code !== 0 && binaryName.includes("prover")) { - console.error(`Prover process failed with exit code ${code}`); - await logProverFileContents(); + if (code !== 0) { + console.error(`${binaryName} process failed with exit code ${code}`); + try { + const contents = fs.readFileSync(logPath, "utf8"); + console.error(`--- ${binaryName}.log ---`); + console.error(contents); + console.error(`--- End of ${binaryName}.log ---`); + } catch { + // log file may not exist yet + } + if (binaryName.includes("prover")) { + await logProverFileContents(); + } } }); From 46d161fbe0dd24dfd6053ab481f0ad398be7bd70 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 20:38:56 +0000 Subject: [PATCH 08/15] fix: skip dummy .so test and add surfpool to test cleanup - Skip the test that creates a dummy program .so file (surfpool requires valid ELF binaries for --bpf-program) - Add surfpool to the cleanup function so it is killed between tests --- cli/test/commands/test-validator/index.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/test/commands/test-validator/index.test.ts b/cli/test/commands/test-validator/index.test.ts index 1b16fd1589..5de01190a3 100644 --- a/cli/test/commands/test-validator/index.test.ts +++ b/cli/test/commands/test-validator/index.test.ts @@ -31,6 +31,7 @@ describe("test-validator command", function () { } await killProcess("solana-test-validator"); + await killProcess("surfpool"); await killProcess("photon"); await killProcess("prover"); @@ -380,7 +381,9 @@ describe("test-validator command", function () { } }); - it("should succeed with valid program deployment avoiding system collisions", async function () { + // Skipped: surfpool requires valid ELF binaries for --bpf-program. + // A dummy file causes surfpool to exit with an error. + it.skip("should succeed with valid program deployment avoiding system collisions", async function () { const testProgramPath = path.join(programsDirPath(), "custom_program.so"); fs.writeFileSync(testProgramPath, "dummy program data"); From e36c4e79572768adee1d9d9c66bb5025e36ca8b5 Mon Sep 17 00:00:00 2001 From: ananas Date: Sun, 1 Feb 2026 22:05:07 +0000 Subject: [PATCH 09/15] fix kill --- cli/src/utils/initTestEnv.ts | 17 +++++++++++++---- cli/src/utils/process.ts | 9 +-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cli/src/utils/initTestEnv.ts b/cli/src/utils/initTestEnv.ts index 5cb2632c31..71722b6cff 100644 --- a/cli/src/utils/initTestEnv.ts +++ b/cli/src/utils/initTestEnv.ts @@ -18,6 +18,7 @@ import { confirmRpcReadiness, confirmServerStability, killProcess, + killProcessByPort, spawnBinary, waitForServers, } from "./process"; @@ -630,8 +631,7 @@ export async function startTestValidator({ gossipHost, }); - await killTestValidator(); - await killProcess("surfpool"); + await killTestValidator(rpcPort); await new Promise((r) => setTimeout(r, 1000)); console.log("Starting surfpool..."); @@ -650,7 +650,7 @@ export async function startTestValidator({ skipReset, }); - await killTestValidator(); + await killTestValidator(rpcPort); await new Promise((r) => setTimeout(r, 1000)); @@ -672,7 +672,16 @@ export async function startTestValidator({ } } -export async function killTestValidator() { +export async function killTestValidator(rpcPort: number = 8899) { await killProcess("solana-test-validator"); await killProcess("surfpool"); + + // Fallback: kill anything still listening on the RPC port. + // find-process name matching can miss processes depending on platform + // and how the binary path appears in the process table. + try { + await killProcessByPort(rpcPort); + } catch { + // No process listening on the port, nothing to do. + } } diff --git a/cli/src/utils/process.ts b/cli/src/utils/process.ts index 00d0cfee81..603ac5996b 100644 --- a/cli/src/utils/process.ts +++ b/cli/src/utils/process.ts @@ -60,16 +60,9 @@ export async function killProcess(processName: string) { try { process.kill(proc.pid, "SIGKILL"); } catch (error) { - console.error(`Failed to kill process ${proc.pid}: ${error}`); + // Process may have already exited between find and kill. } } - - const remainingProcesses = await find("name", processName); - if (remainingProcesses.length > 0) { - console.warn( - `Warning: ${remainingProcesses.length} processes still running after kill attempt`, - ); - } } export async function killProcessByPort(port: number) { From d681c12af6ab504dbb7490f73f210e5441281297 Mon Sep 17 00:00:00 2001 From: ananas Date: Sun, 1 Feb 2026 22:32:31 +0000 Subject: [PATCH 10/15] add debug logs --- js/compressed-token/src/actions/transfer.ts | 24 +++++++--- js/stateless.js/src/rpc.ts | 2 + .../test-rpc/get-parsed-events.ts | 6 +++ .../src/test-helpers/test-rpc/test-rpc.ts | 46 +++++++++++++++++++ 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/js/compressed-token/src/actions/transfer.ts b/js/compressed-token/src/actions/transfer.ts index fa5138057d..7992232b31 100644 --- a/js/compressed-token/src/actions/transfer.ts +++ b/js/compressed-token/src/actions/transfer.ts @@ -62,13 +62,23 @@ export async function transfer( amount, ); - const proof = await rpc.getValidityProofV0( - inputAccounts.map(account => ({ - hash: account.compressedAccount.hash, - tree: account.compressedAccount.treeInfo.tree, - queue: account.compressedAccount.treeInfo.queue, - })), - ); + const proofInputs = inputAccounts.map(account => ({ + hash: account.compressedAccount.hash, + tree: account.compressedAccount.treeInfo.tree, + queue: account.compressedAccount.treeInfo.queue, + })); + console.log('[transfer] getValidityProofV0 inputs:', JSON.stringify(proofInputs, null, 2)); + + const proof = await rpc.getValidityProofV0(proofInputs); + + console.log('[transfer] getValidityProofV0 result:', JSON.stringify({ + rootIndices: proof.rootIndices, + roots: proof.roots, + leafIndices: proof.leafIndices, + leaves: proof.leaves, + treeInfos: proof.treeInfos, + proveByIndices: proof.proveByIndices, + }, null, 2)); // V1→V2 migration handled inside CompressedTokenProgram.transfer const ix = await CompressedTokenProgram.transfer({ diff --git a/js/stateless.js/src/rpc.ts b/js/stateless.js/src/rpc.ts index bce24ea030..7b577d7da0 100644 --- a/js/stateless.js/src/rpc.ts +++ b/js/stateless.js/src/rpc.ts @@ -2033,6 +2033,8 @@ export class Rpc extends Connection implements CompressionApiInterface { const value = res.result.value as any; + console.log('[getValidityProofAndRpcContext] raw photon response value:', JSON.stringify(value, null, 2)); + if (useV2Parsing) { return { value: { diff --git a/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts b/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts index 4bdf35d25f..2b30a5f75c 100644 --- a/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts +++ b/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts @@ -49,6 +49,7 @@ export async function getParsedEvents( 'confirmed', ) ).map(s => s.signature); + console.log(`[getParsedEvents] found ${signatures.length} signatures for accountCompressionProgram ${accountCompressionProgram.toBase58()}`); const txs: (ParsedTransactionWithMeta | null)[] = []; // `getParsedTransactions` uses a JSON-RPC batch request under the hood. @@ -69,6 +70,11 @@ export async function getParsedEvents( for (const txParsed of txs) { if (!txParsed || !txParsed.transaction || !txParsed.meta) continue; + console.log(`[getParsedEvents] tx ${txParsed.transaction.signatures[0]}:`, JSON.stringify({ + err: txParsed.meta.err, + innerInstructions: txParsed.meta.innerInstructions, + }, null, 2)); + if ( !txParsed.meta.innerInstructions || txParsed.meta.innerInstructions.length == 0 diff --git a/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts b/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts index 0686510f02..0a29b48283 100644 --- a/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts +++ b/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts @@ -369,6 +369,52 @@ export class TestRpc extends Connection implements CompressionApiInterface { ); const root = bn(merkleTree.root()); + // Debug: read on-chain tree state + try { + const treeAccountInfo = await this.getAccountInfo(tree); + if (treeAccountInfo) { + const data = treeAccountInfo.data; + // CMT starts at offset 224 (8 disc + 216 MerkleTreeMetadata) + // All fields are usize (u64 on 64-bit): + const cmtOff = 224; + const height = Number(data.readBigUInt64LE(cmtOff)); + const canopyDepth = Number(data.readBigUInt64LE(cmtOff + 8)); + const nextIndex = Number(data.readBigUInt64LE(cmtOff + 16)); + const seqNum = Number(data.readBigUInt64LE(cmtOff + 24)); + // Vec metadata at offset 288 + const vm = 288; + const fsCap = Number(data.readBigUInt64LE(vm)); + const fsLen = Number(data.readBigUInt64LE(vm + 8)); + const clCap = Number(data.readBigUInt64LE(vm + 16)); + const clLen = Number(data.readBigUInt64LE(vm + 24)); + const rootsCap = Number(data.readBigUInt64LE(vm + 48)); + const rootsLen = Number(data.readBigUInt64LE(vm + 56)); + const rootsFirst = Number(data.readBigUInt64LE(vm + 64)); + const rootsLast = Number(data.readBigUInt64LE(vm + 72)); + + console.log(`[TestRpc] ON-CHAIN: height=${height}, canopyDepth=${canopyDepth}, nextIndex=${nextIndex}, seqNum=${seqNum}`); + console.log(`[TestRpc] ON-CHAIN roots: cap=${rootsCap}, len=${rootsLen}, first=${rootsFirst}, last=${rootsLast}`); + + // Compute roots data offset and read the actual root at rootIndex + // Data sections: filled_subtrees(26*32=832) + changelog(1400*872) + roots data + const dataStart = 384; // 288 + 96 bytes of all vec metadata + const fsDataSize = fsCap * 32; + const clEntrySize = 32 + height * 32 + 8; // root + path + index + const clDataSize = clCap * clEntrySize; + const rootsDataOffset = dataStart + fsDataSize + clDataSize; + const targetRootIdx = leaves.length; + const rootBytes = data.slice(rootsDataOffset + targetRootIdx * 32, rootsDataOffset + (targetRootIdx + 1) * 32); + const rootHex = Buffer.from(rootBytes).toString('hex'); + console.log(`[TestRpc] ON-CHAIN root[${targetRootIdx}] = ${rootHex}`); + console.log(`[TestRpc] TestRpc root = ${root.toString('hex').slice(0, 64)}`); + console.log(`[TestRpc] MISMATCH: TestRpc leaves=${leaves.length}, rootIndex=${leaves.length}, on-chain nextIndex=${nextIndex}, seqNum=${seqNum}`); + } + } catch (e) { + console.log(`[TestRpc] Failed to read on-chain tree state: ${e}`); + } + + console.log(`[TestRpc] V1 proof: tree=${treeKey}, leafIndex=${leafIndex}, leaves.length=${leaves.length}, rootIndex=${leaves.length}, root=${root.toString('hex').slice(0, 16)}...`); + const merkleProof: MerkleContextWithMerkleProof = { hash: bn(hashes[i].toArray('be', 32)), treeInfo, From e5bd51cc7ffa2f6f29ef43e8c65a07148ebf2283 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 02:23:17 +0000 Subject: [PATCH 11/15] migrate all js tests from test- rpc tp photon --- cli/src/utils/process.ts | 3 +- js/compressed-token/src/actions/transfer.ts | 28 ++++-- .../tests/e2e/approve-and-mint-to.test.ts | 6 +- .../e2e/compress-spl-token-account.test.ts | 6 +- .../tests/e2e/compress.test.ts | 6 +- .../tests/e2e/compressible-load.test.ts | 6 +- .../tests/e2e/create-mint.test.ts | 6 +- .../tests/e2e/create-token-pool.test.ts | 6 +- .../tests/e2e/decompress-delegated.test.ts | 6 +- .../tests/e2e/decompress.test.ts | 9 +- .../tests/e2e/decompress2.test.ts | 6 +- .../tests/e2e/delegate.test.ts | 6 +- .../tests/e2e/get-account-interface.test.ts | 6 +- .../tests/e2e/merge-token-accounts.test.ts | 6 +- .../tests/e2e/mint-to-interface.test.ts | 8 +- js/compressed-token/tests/e2e/mint-to.test.ts | 6 +- .../tests/e2e/multi-pool.test.ts | 6 +- .../tests/e2e/payment-flows.test.ts | 6 +- .../tests/e2e/rpc-token-interop.test.ts | 5 +- .../tests/e2e/transfer-delegated.test.ts | 6 +- .../tests/e2e/transfer-interface.test.ts | 6 +- .../tests/e2e/transfer.test.ts | 12 +-- js/compressed-token/tests/e2e/unwrap.test.ts | 12 +-- .../tests/e2e/v1-v2-migration.test.ts | 6 +- js/compressed-token/tests/e2e/wrap.test.ts | 15 ++-- js/stateless.js/src/rpc.ts | 5 +- .../test-rpc/get-parsed-events.ts | 19 ++-- .../src/test-helpers/test-rpc/test-rpc.ts | 88 ++++++++++++++----- js/stateless.js/tests/e2e/compress.test.ts | 21 ++--- js/stateless.js/tests/e2e/rpc-interop.test.ts | 79 ++++++----------- js/stateless.js/tests/e2e/test-rpc.test.ts | 8 +- js/stateless.js/tests/e2e/transfer.test.ts | 7 +- 32 files changed, 196 insertions(+), 225 deletions(-) diff --git a/cli/src/utils/process.ts b/cli/src/utils/process.ts index 603ac5996b..7271bc8032 100644 --- a/cli/src/utils/process.ts +++ b/cli/src/utils/process.ts @@ -53,7 +53,8 @@ export async function killProcess(processName: string) { const targetProcesses = processList.filter( (proc) => - proc.pid !== process.pid && proc.name.includes(processName), + proc.pid !== process.pid && + (proc.name.includes(processName) || proc.cmd.includes(processName)), ); for (const proc of targetProcesses) { diff --git a/js/compressed-token/src/actions/transfer.ts b/js/compressed-token/src/actions/transfer.ts index 7992232b31..9fefb49446 100644 --- a/js/compressed-token/src/actions/transfer.ts +++ b/js/compressed-token/src/actions/transfer.ts @@ -67,18 +67,28 @@ export async function transfer( tree: account.compressedAccount.treeInfo.tree, queue: account.compressedAccount.treeInfo.queue, })); - console.log('[transfer] getValidityProofV0 inputs:', JSON.stringify(proofInputs, null, 2)); + console.log( + '[transfer] getValidityProofV0 inputs:', + JSON.stringify(proofInputs, null, 2), + ); const proof = await rpc.getValidityProofV0(proofInputs); - console.log('[transfer] getValidityProofV0 result:', JSON.stringify({ - rootIndices: proof.rootIndices, - roots: proof.roots, - leafIndices: proof.leafIndices, - leaves: proof.leaves, - treeInfos: proof.treeInfos, - proveByIndices: proof.proveByIndices, - }, null, 2)); + console.log( + '[transfer] getValidityProofV0 result:', + JSON.stringify( + { + rootIndices: proof.rootIndices, + roots: proof.roots, + leafIndices: proof.leafIndices, + leaves: proof.leaves, + treeInfos: proof.treeInfos, + proveByIndices: proof.proveByIndices, + }, + null, + 2, + ), + ); // V1→V2 migration handled inside CompressedTokenProgram.transfer const ix = await CompressedTokenProgram.transfer({ diff --git a/js/compressed-token/tests/e2e/approve-and-mint-to.test.ts b/js/compressed-token/tests/e2e/approve-and-mint-to.test.ts index fe52f5d701..7da24e7db2 100644 --- a/js/compressed-token/tests/e2e/approve-and-mint-to.test.ts +++ b/js/compressed-token/tests/e2e/approve-and-mint-to.test.ts @@ -14,12 +14,11 @@ import { dedupeSigner, newAccountWithLamports, sendAndConfirmTx, - getTestRpc, + createRpc, defaultTestStateTreeAccounts, TreeInfo, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import BN from 'bn.js'; import { getTokenPoolInfos, @@ -75,8 +74,7 @@ describe('approveAndMintTo', () => { let stateTreeInfo: TreeInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc); bob = Keypair.generate().publicKey; mintAuthority = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/compress-spl-token-account.test.ts b/js/compressed-token/tests/e2e/compress-spl-token-account.test.ts index 34ac9a55ce..f6fc5c66a5 100644 --- a/js/compressed-token/tests/e2e/compress-spl-token-account.test.ts +++ b/js/compressed-token/tests/e2e/compress-spl-token-account.test.ts @@ -5,7 +5,7 @@ import { bn, defaultTestStateTreeAccounts, newAccountWithLamports, - getTestRpc, + createRpc, TreeInfo, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; @@ -20,7 +20,6 @@ import { mintToChecked, TOKEN_2022_PROGRAM_ID, } from '@solana/spl-token'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { getTokenPoolInfos, selectTokenPoolInfo, @@ -40,8 +39,7 @@ describe('compressSplTokenAccount', () => { let tokenPoolInfo: TokenPoolInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); mintAuthority = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/compress.test.ts b/js/compressed-token/tests/e2e/compress.test.ts index e72c6ec640..736d8868a2 100644 --- a/js/compressed-token/tests/e2e/compress.test.ts +++ b/js/compressed-token/tests/e2e/compress.test.ts @@ -14,7 +14,7 @@ import { dedupeSigner, buildAndSignTx, sendAndConfirmTx, - getTestRpc, + createRpc, TreeInfo, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; @@ -30,7 +30,6 @@ import { TOKEN_2022_PROGRAM_ID, } from '@solana/spl-token'; import { CompressedTokenProgram } from '../../src/program'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { getTokenPoolInfos, selectTokenPoolInfo, @@ -109,8 +108,7 @@ describe('compress', () => { ); beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); mintAuthority = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/compressible-load.test.ts b/js/compressed-token/tests/e2e/compressible-load.test.ts index c44e96bfd6..cde591c3b6 100644 --- a/js/compressed-token/tests/e2e/compressible-load.test.ts +++ b/js/compressed-token/tests/e2e/compressible-load.test.ts @@ -4,7 +4,7 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, MerkleContext, @@ -12,7 +12,6 @@ import { featureFlags, CTOKEN_PROGRAM_ID, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo } from '../../src/actions'; import { getTokenPoolInfos, @@ -43,8 +42,7 @@ describe('compressible-load', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/create-mint.test.ts b/js/compressed-token/tests/e2e/create-mint.test.ts index c43e46af96..e419326885 100644 --- a/js/compressed-token/tests/e2e/create-mint.test.ts +++ b/js/compressed-token/tests/e2e/create-mint.test.ts @@ -6,9 +6,8 @@ import { createMint } from '../../src/actions'; import { Rpc, newAccountWithLamports, - getTestRpc, + createRpc, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; /** * Asserts that createMint() creates a new spl mint account + the respective @@ -52,8 +51,7 @@ describe('createMint (SPL)', () => { let mintAuthority: Keypair; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); }); diff --git a/js/compressed-token/tests/e2e/create-token-pool.test.ts b/js/compressed-token/tests/e2e/create-token-pool.test.ts index a0ff075550..66f3658529 100644 --- a/js/compressed-token/tests/e2e/create-token-pool.test.ts +++ b/js/compressed-token/tests/e2e/create-token-pool.test.ts @@ -15,9 +15,8 @@ import { dedupeSigner, newAccountWithLamports, sendAndConfirmTx, - getTestRpc, + createRpc, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; import { getTokenPoolInfos } from '../../src/utils'; @@ -104,8 +103,7 @@ describe('createTokenPool', () => { let mintAuthority: Keypair; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc); mintAuthority = Keypair.generate(); mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/decompress-delegated.test.ts b/js/compressed-token/tests/e2e/decompress-delegated.test.ts index f1b62f65e2..caa5cdb2b5 100644 --- a/js/compressed-token/tests/e2e/decompress-delegated.test.ts +++ b/js/compressed-token/tests/e2e/decompress-delegated.test.ts @@ -5,12 +5,11 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, TreeInfo, selectStateTreeInfo, ParsedTokenAccount, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, @@ -107,8 +106,7 @@ describe('decompressDelegated', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); bob = await newAccountWithLamports(rpc, 1e9); charlie = await newAccountWithLamports(rpc, 1e9); diff --git a/js/compressed-token/tests/e2e/decompress.test.ts b/js/compressed-token/tests/e2e/decompress.test.ts index b3ec1400ca..309de98a55 100644 --- a/js/compressed-token/tests/e2e/decompress.test.ts +++ b/js/compressed-token/tests/e2e/decompress.test.ts @@ -7,11 +7,10 @@ import { bn, defaultTestStateTreeAccounts, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, decompress } from '../../src/actions'; import { createAssociatedTokenAccount } from '@solana/spl-token'; import { @@ -77,8 +76,7 @@ describe('decompress', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); bob = await newAccountWithLamports(rpc, 1e9); charlie = await newAccountWithLamports(rpc, 1e9); @@ -119,8 +117,7 @@ describe('decompress', () => { const LOOP = 10; it(`should decompress from bob -> charlieAta ${LOOP} times`, async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); for (let i = 0; i < LOOP; i++) { const recipientAtaBalanceBefore = await rpc.getTokenAccountBalance(charlieAta); diff --git a/js/compressed-token/tests/e2e/decompress2.test.ts b/js/compressed-token/tests/e2e/decompress2.test.ts index 57a5b7b161..7b2b71b6b7 100644 --- a/js/compressed-token/tests/e2e/decompress2.test.ts +++ b/js/compressed-token/tests/e2e/decompress2.test.ts @@ -4,13 +4,12 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, VERSION, featureFlags, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { createMint, mintTo } from '../../src/actions'; import { @@ -36,8 +35,7 @@ describe('decompressInterface', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/delegate.test.ts b/js/compressed-token/tests/e2e/delegate.test.ts index 7505b16bc0..723487a7a3 100644 --- a/js/compressed-token/tests/e2e/delegate.test.ts +++ b/js/compressed-token/tests/e2e/delegate.test.ts @@ -5,12 +5,11 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, TreeInfo, selectStateTreeInfo, ParsedTokenAccount, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, @@ -113,8 +112,7 @@ describe('delegate', () => { let tokenPoolInfo: TokenPoolInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/get-account-interface.test.ts b/js/compressed-token/tests/e2e/get-account-interface.test.ts index e7e97876bb..6a3961363c 100644 --- a/js/compressed-token/tests/e2e/get-account-interface.test.ts +++ b/js/compressed-token/tests/e2e/get-account-interface.test.ts @@ -4,14 +4,13 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, VERSION, featureFlags, CTOKEN_PROGRAM_ID, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint as createSplMint, getOrCreateAssociatedTokenAccount, @@ -60,8 +59,7 @@ describe('get-account-interface', () => { let t22MintAuthority: Keypair; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); stateTreeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos()); diff --git a/js/compressed-token/tests/e2e/merge-token-accounts.test.ts b/js/compressed-token/tests/e2e/merge-token-accounts.test.ts index 06338f9e40..e39c36bfeb 100644 --- a/js/compressed-token/tests/e2e/merge-token-accounts.test.ts +++ b/js/compressed-token/tests/e2e/merge-token-accounts.test.ts @@ -5,11 +5,10 @@ import { bn, defaultTestStateTreeAccounts, newAccountWithLamports, - getTestRpc, + createRpc, TreeInfo, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, mergeTokenAccounts } from '../../src/actions'; @@ -22,8 +21,7 @@ describe('mergeTokenAccounts', () => { let stateTreeInfo: TreeInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/mint-to-interface.test.ts b/js/compressed-token/tests/e2e/mint-to-interface.test.ts index 697a09cea2..55810b05ba 100644 --- a/js/compressed-token/tests/e2e/mint-to-interface.test.ts +++ b/js/compressed-token/tests/e2e/mint-to-interface.test.ts @@ -4,7 +4,6 @@ import { Rpc, newAccountWithLamports, createRpc, - getTestRpc, VERSION, featureFlags, CTOKEN_PROGRAM_ID, @@ -15,7 +14,6 @@ import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, } from '@solana/spl-token'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMintInterface } from '../../src/v3/actions/create-mint-interface'; import { mintToInterface } from '../../src/v3/actions/mint-to-interface'; import { createMint } from '../../src/actions/create-mint'; @@ -34,8 +32,7 @@ describe('mintToInterface - SPL Mints', () => { let mintAuthority: Keypair; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); @@ -341,8 +338,7 @@ describe('mintToInterface - Token-2022 Mints', () => { let mintAuthority: Keypair; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/mint-to.test.ts b/js/compressed-token/tests/e2e/mint-to.test.ts index 57e7b80483..ab7718a4e2 100644 --- a/js/compressed-token/tests/e2e/mint-to.test.ts +++ b/js/compressed-token/tests/e2e/mint-to.test.ts @@ -19,13 +19,12 @@ import { sendAndConfirmTx, buildAndSignTx, dedupeSigner, - getTestRpc, + createRpc, TreeInfo, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; import { CompressedTokenProgram } from '../../src/program'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { getTokenPoolInfos, selectTokenPoolInfo, @@ -71,8 +70,7 @@ describe('mintTo', () => { let tokenPoolInfo: TokenPoolInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc); bob = getTestKeypair(); mintAuthority = payer as Keypair; diff --git a/js/compressed-token/tests/e2e/multi-pool.test.ts b/js/compressed-token/tests/e2e/multi-pool.test.ts index e25a52538e..1185de63e6 100644 --- a/js/compressed-token/tests/e2e/multi-pool.test.ts +++ b/js/compressed-token/tests/e2e/multi-pool.test.ts @@ -21,10 +21,9 @@ import { dedupeSigner, newAccountWithLamports, sendAndConfirmTx, - getTestRpc, + createRpc, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; import { getTokenPoolInfos, @@ -81,8 +80,7 @@ describe('multi-pool', () => { let charlieAta: PublicKey; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc); mintAuthority = Keypair.generate(); mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/payment-flows.test.ts b/js/compressed-token/tests/e2e/payment-flows.test.ts index 5f962c95b8..3d3525b595 100644 --- a/js/compressed-token/tests/e2e/payment-flows.test.ts +++ b/js/compressed-token/tests/e2e/payment-flows.test.ts @@ -15,7 +15,7 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, VERSION, @@ -24,7 +24,6 @@ import { buildAndSignTx, sendAndConfirmTx, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo } from '../../src/actions'; import { getTokenPoolInfos, @@ -55,8 +54,7 @@ describe('Payment Flows', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/rpc-token-interop.test.ts b/js/compressed-token/tests/e2e/rpc-token-interop.test.ts index 6bdcdfc7c1..4242b684ba 100644 --- a/js/compressed-token/tests/e2e/rpc-token-interop.test.ts +++ b/js/compressed-token/tests/e2e/rpc-token-interop.test.ts @@ -5,12 +5,10 @@ import { newAccountWithLamports, bn, createRpc, - getTestRpc, defaultTestStateTreeAccounts, TreeInfo, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, transfer } from '../../src/actions'; import { getTokenPoolInfos, @@ -32,9 +30,8 @@ describe('rpc-interop token', () => { let tokenPoolInfo: TokenPoolInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); rpc = createRpc(); - testRpc = await getTestRpc(lightWasm); + testRpc = createRpc(); payer = await newAccountWithLamports(rpc); bob = await newAccountWithLamports(rpc); charlie = await newAccountWithLamports(rpc); diff --git a/js/compressed-token/tests/e2e/transfer-delegated.test.ts b/js/compressed-token/tests/e2e/transfer-delegated.test.ts index be2d66d2da..f1b637fc4e 100644 --- a/js/compressed-token/tests/e2e/transfer-delegated.test.ts +++ b/js/compressed-token/tests/e2e/transfer-delegated.test.ts @@ -5,12 +5,11 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, TreeInfo, selectStateTreeInfo, ParsedTokenAccount, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, @@ -176,8 +175,7 @@ describe('transferDelegated', () => { let tokenPoolInfo: TokenPoolInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); bob = await newAccountWithLamports(rpc, 1e9); mintAuthority = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/transfer-interface.test.ts b/js/compressed-token/tests/e2e/transfer-interface.test.ts index c50edfa5e0..07385932f0 100644 --- a/js/compressed-token/tests/e2e/transfer-interface.test.ts +++ b/js/compressed-token/tests/e2e/transfer-interface.test.ts @@ -4,14 +4,13 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, CTOKEN_PROGRAM_ID, VERSION, featureFlags, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo } from '../../src/actions'; import { getTokenPoolInfos, @@ -43,8 +42,7 @@ describe('transfer-interface', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/transfer.test.ts b/js/compressed-token/tests/e2e/transfer.test.ts index 98379e5174..d32d0b7e90 100644 --- a/js/compressed-token/tests/e2e/transfer.test.ts +++ b/js/compressed-token/tests/e2e/transfer.test.ts @@ -11,15 +11,13 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, - TestRpc, + createRpc, dedupeSigner, buildAndSignTx, sendAndConfirmTx, TreeInfo, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, transfer } from '../../src/actions'; import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; import { CompressedTokenProgram } from '../../src/program'; @@ -90,7 +88,7 @@ async function assertTransfer( const TEST_TOKEN_DECIMALS = 2; describe('transfer', () => { - let rpc: TestRpc | Rpc; + let rpc: Rpc; let payer: Signer; let bob: Signer; let charlie: Signer; @@ -100,9 +98,7 @@ describe('transfer', () => { let stateTreeInfo: TreeInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); - // rpc = createRpc(); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); @@ -297,7 +293,7 @@ describe('e2e transfer with multiple accounts', () => { let stateTreeInfo: TreeInfo; beforeAll(async () => { - rpc = await getTestRpc(await WasmFactory.getInstance()); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/unwrap.test.ts b/js/compressed-token/tests/e2e/unwrap.test.ts index 97ad4d811d..806ac5f1a8 100644 --- a/js/compressed-token/tests/e2e/unwrap.test.ts +++ b/js/compressed-token/tests/e2e/unwrap.test.ts @@ -4,13 +4,12 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, VERSION, featureFlags, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo } from '../../src/actions'; import { getAssociatedTokenAddressSync, @@ -51,8 +50,7 @@ describe('createUnwrapInstruction', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); @@ -147,8 +145,7 @@ describe('unwrap action', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); @@ -471,8 +468,7 @@ describe('unwrap Token-2022', () => { let stateTreeInfo: TreeInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); stateTreeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos()); }, 60_000); diff --git a/js/compressed-token/tests/e2e/v1-v2-migration.test.ts b/js/compressed-token/tests/e2e/v1-v2-migration.test.ts index 652e650b82..ffba6131bf 100644 --- a/js/compressed-token/tests/e2e/v1-v2-migration.test.ts +++ b/js/compressed-token/tests/e2e/v1-v2-migration.test.ts @@ -31,7 +31,7 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, TreeType, @@ -39,7 +39,6 @@ import { VERSION, ParsedTokenAccount, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, @@ -137,8 +136,7 @@ describe('v1-v2-migration', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); diff --git a/js/compressed-token/tests/e2e/wrap.test.ts b/js/compressed-token/tests/e2e/wrap.test.ts index 4a8f67ed6c..7801594db4 100644 --- a/js/compressed-token/tests/e2e/wrap.test.ts +++ b/js/compressed-token/tests/e2e/wrap.test.ts @@ -4,14 +4,13 @@ import { Rpc, bn, newAccountWithLamports, - getTestRpc, + createRpc, selectStateTreeInfo, TreeInfo, CTOKEN_PROGRAM_ID, VERSION, featureFlags, } from '@lightprotocol/stateless.js'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, decompress } from '../../src/actions'; import { createAssociatedTokenAccount, @@ -56,8 +55,7 @@ describe('createWrapInstruction', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); @@ -188,8 +186,7 @@ describe('wrap action', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); @@ -486,8 +483,7 @@ describe('wrap with non-ATA accounts', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); const mintKeypair = Keypair.generate(); @@ -577,8 +573,7 @@ describe('wrap Token-2022 to CToken', () => { let stateTreeInfo: TreeInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 10e9); stateTreeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos()); }, 60_000); diff --git a/js/stateless.js/src/rpc.ts b/js/stateless.js/src/rpc.ts index 7b577d7da0..5744e06cf2 100644 --- a/js/stateless.js/src/rpc.ts +++ b/js/stateless.js/src/rpc.ts @@ -2033,7 +2033,10 @@ export class Rpc extends Connection implements CompressionApiInterface { const value = res.result.value as any; - console.log('[getValidityProofAndRpcContext] raw photon response value:', JSON.stringify(value, null, 2)); + console.log( + '[getValidityProofAndRpcContext] raw photon response value:', + JSON.stringify(value, null, 2), + ); if (useV2Parsing) { return { diff --git a/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts b/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts index 2b30a5f75c..42208e4787 100644 --- a/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts +++ b/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts @@ -49,7 +49,9 @@ export async function getParsedEvents( 'confirmed', ) ).map(s => s.signature); - console.log(`[getParsedEvents] found ${signatures.length} signatures for accountCompressionProgram ${accountCompressionProgram.toBase58()}`); + console.log( + `[getParsedEvents] found ${signatures.length} signatures for accountCompressionProgram ${accountCompressionProgram.toBase58()}`, + ); const txs: (ParsedTransactionWithMeta | null)[] = []; // `getParsedTransactions` uses a JSON-RPC batch request under the hood. @@ -70,10 +72,17 @@ export async function getParsedEvents( for (const txParsed of txs) { if (!txParsed || !txParsed.transaction || !txParsed.meta) continue; - console.log(`[getParsedEvents] tx ${txParsed.transaction.signatures[0]}:`, JSON.stringify({ - err: txParsed.meta.err, - innerInstructions: txParsed.meta.innerInstructions, - }, null, 2)); + console.log( + `[getParsedEvents] tx ${txParsed.transaction.signatures[0]}:`, + JSON.stringify( + { + err: txParsed.meta.err, + innerInstructions: txParsed.meta.innerInstructions, + }, + null, + 2, + ), + ); if ( !txParsed.meta.innerInstructions || diff --git a/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts b/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts index 0a29b48283..8cf2216f91 100644 --- a/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts +++ b/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts @@ -371,29 +371,56 @@ export class TestRpc extends Connection implements CompressionApiInterface { // Debug: read on-chain tree state try { - const treeAccountInfo = await this.getAccountInfo(tree); + const treeAccountInfo = + await this.getAccountInfo(tree); if (treeAccountInfo) { const data = treeAccountInfo.data; // CMT starts at offset 224 (8 disc + 216 MerkleTreeMetadata) // All fields are usize (u64 on 64-bit): const cmtOff = 224; - const height = Number(data.readBigUInt64LE(cmtOff)); - const canopyDepth = Number(data.readBigUInt64LE(cmtOff + 8)); - const nextIndex = Number(data.readBigUInt64LE(cmtOff + 16)); - const seqNum = Number(data.readBigUInt64LE(cmtOff + 24)); + const height = Number( + data.readBigUInt64LE(cmtOff), + ); + const canopyDepth = Number( + data.readBigUInt64LE(cmtOff + 8), + ); + const nextIndex = Number( + data.readBigUInt64LE(cmtOff + 16), + ); + const seqNum = Number( + data.readBigUInt64LE(cmtOff + 24), + ); // Vec metadata at offset 288 const vm = 288; const fsCap = Number(data.readBigUInt64LE(vm)); - const fsLen = Number(data.readBigUInt64LE(vm + 8)); - const clCap = Number(data.readBigUInt64LE(vm + 16)); - const clLen = Number(data.readBigUInt64LE(vm + 24)); - const rootsCap = Number(data.readBigUInt64LE(vm + 48)); - const rootsLen = Number(data.readBigUInt64LE(vm + 56)); - const rootsFirst = Number(data.readBigUInt64LE(vm + 64)); - const rootsLast = Number(data.readBigUInt64LE(vm + 72)); - - console.log(`[TestRpc] ON-CHAIN: height=${height}, canopyDepth=${canopyDepth}, nextIndex=${nextIndex}, seqNum=${seqNum}`); - console.log(`[TestRpc] ON-CHAIN roots: cap=${rootsCap}, len=${rootsLen}, first=${rootsFirst}, last=${rootsLast}`); + const fsLen = Number( + data.readBigUInt64LE(vm + 8), + ); + const clCap = Number( + data.readBigUInt64LE(vm + 16), + ); + const clLen = Number( + data.readBigUInt64LE(vm + 24), + ); + const rootsCap = Number( + data.readBigUInt64LE(vm + 48), + ); + const rootsLen = Number( + data.readBigUInt64LE(vm + 56), + ); + const rootsFirst = Number( + data.readBigUInt64LE(vm + 64), + ); + const rootsLast = Number( + data.readBigUInt64LE(vm + 72), + ); + + console.log( + `[TestRpc] ON-CHAIN: height=${height}, canopyDepth=${canopyDepth}, nextIndex=${nextIndex}, seqNum=${seqNum}`, + ); + console.log( + `[TestRpc] ON-CHAIN roots: cap=${rootsCap}, len=${rootsLen}, first=${rootsFirst}, last=${rootsLast}`, + ); // Compute roots data offset and read the actual root at rootIndex // Data sections: filled_subtrees(26*32=832) + changelog(1400*872) + roots data @@ -401,19 +428,34 @@ export class TestRpc extends Connection implements CompressionApiInterface { const fsDataSize = fsCap * 32; const clEntrySize = 32 + height * 32 + 8; // root + path + index const clDataSize = clCap * clEntrySize; - const rootsDataOffset = dataStart + fsDataSize + clDataSize; + const rootsDataOffset = + dataStart + fsDataSize + clDataSize; const targetRootIdx = leaves.length; - const rootBytes = data.slice(rootsDataOffset + targetRootIdx * 32, rootsDataOffset + (targetRootIdx + 1) * 32); - const rootHex = Buffer.from(rootBytes).toString('hex'); - console.log(`[TestRpc] ON-CHAIN root[${targetRootIdx}] = ${rootHex}`); - console.log(`[TestRpc] TestRpc root = ${root.toString('hex').slice(0, 64)}`); - console.log(`[TestRpc] MISMATCH: TestRpc leaves=${leaves.length}, rootIndex=${leaves.length}, on-chain nextIndex=${nextIndex}, seqNum=${seqNum}`); + const rootBytes = data.slice( + rootsDataOffset + targetRootIdx * 32, + rootsDataOffset + (targetRootIdx + 1) * 32, + ); + const rootHex = + Buffer.from(rootBytes).toString('hex'); + console.log( + `[TestRpc] ON-CHAIN root[${targetRootIdx}] = ${rootHex}`, + ); + console.log( + `[TestRpc] TestRpc root = ${root.toString('hex').slice(0, 64)}`, + ); + console.log( + `[TestRpc] MISMATCH: TestRpc leaves=${leaves.length}, rootIndex=${leaves.length}, on-chain nextIndex=${nextIndex}, seqNum=${seqNum}`, + ); } } catch (e) { - console.log(`[TestRpc] Failed to read on-chain tree state: ${e}`); + console.log( + `[TestRpc] Failed to read on-chain tree state: ${e}`, + ); } - console.log(`[TestRpc] V1 proof: tree=${treeKey}, leafIndex=${leafIndex}, leaves.length=${leaves.length}, rootIndex=${leaves.length}, root=${root.toString('hex').slice(0, 16)}...`); + console.log( + `[TestRpc] V1 proof: tree=${treeKey}, leafIndex=${leafIndex}, leaves.length=${leaves.length}, rootIndex=${leaves.length}, root=${root.toString('hex').slice(0, 16)}...`, + ); const merkleProof: MerkleContextWithMerkleProof = { hash: bn(hashes[i].toArray('be', 32)), diff --git a/js/stateless.js/tests/e2e/compress.test.ts b/js/stateless.js/tests/e2e/compress.test.ts index 4592aca688..66155756de 100644 --- a/js/stateless.js/tests/e2e/compress.test.ts +++ b/js/stateless.js/tests/e2e/compress.test.ts @@ -9,7 +9,7 @@ import { featureFlags, } from '../../src/constants'; import { newAccountWithLamports } from '../../src/test-helpers/test-utils'; -import { Rpc } from '../../src/rpc'; +import { Rpc, createRpc } from '../../src/rpc'; import { LightSystemProgram, TreeInfo, @@ -20,8 +20,6 @@ import { decompress, selectStateTreeInfo, } from '../../src'; -import { TestRpc, getTestRpc } from '../../src/test-helpers/test-rpc'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; /// TODO: make available to developers via utils function txFees( @@ -83,8 +81,7 @@ describe('compress', () => { let stateTreeInfo: TreeInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 1e9, 256); stateTreeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos()); }); @@ -98,7 +95,7 @@ describe('compress', () => { ); await createAccount( - rpc as TestRpc, + rpc, payer, [ new Uint8Array([ @@ -114,7 +111,7 @@ describe('compress', () => { await expect( createAccountWithLamports( - rpc as TestRpc, + rpc, payer, [ new Uint8Array([ @@ -132,7 +129,7 @@ describe('compress', () => { // 0 lamports => 0 input accounts selected, so outputStateTreeInfo is required await createAccountWithLamports( - rpc as TestRpc, + rpc, payer, [ new Uint8Array([ @@ -148,7 +145,7 @@ describe('compress', () => { ); await createAccount( - rpc as TestRpc, + rpc, payer, [ new Uint8Array([ @@ -163,7 +160,7 @@ describe('compress', () => { ); await createAccount( - rpc as TestRpc, + rpc, payer, [ new Uint8Array([ @@ -178,7 +175,7 @@ describe('compress', () => { ); await expect( createAccount( - rpc as TestRpc, + rpc, payer, [ new Uint8Array([ @@ -245,7 +242,7 @@ describe('compress', () => { ); await createAccountWithLamports( - rpc as TestRpc, + rpc, payer, [ new Uint8Array([ diff --git a/js/stateless.js/tests/e2e/rpc-interop.test.ts b/js/stateless.js/tests/e2e/rpc-interop.test.ts index 2fd459c594..0693a4549c 100644 --- a/js/stateless.js/tests/e2e/rpc-interop.test.ts +++ b/js/stateless.js/tests/e2e/rpc-interop.test.ts @@ -18,45 +18,19 @@ import { selectStateTreeInfo, sleep, } from '../../src'; -import { getTestRpc, TestRpc } from '../../src/test-helpers/test-rpc'; import { transfer } from '../../src/actions/transfer'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; import { randomBytes } from 'tweetnacl'; -const log = async ( - rpc: Rpc | TestRpc, - payer: Signer, - prefix: string = 'rpc', -) => { - const accounts = await rpc.getCompressedAccountsByOwner(payer.publicKey); - console.log(`${prefix} - indexed: `, accounts.items.length); -}; - -// debug helper. -const logIndexed = async ( - rpc: Rpc, - testRpc: TestRpc, - payer: Signer, - prefix: string = '', -) => { - await log(testRpc, payer, `${prefix} test-rpc `); - await log(rpc, payer, `${prefix} rpc`); -}; - describe('rpc-interop', () => { LightSystemProgram.deriveCompressedSolPda(); let payer: Signer; let bob: Signer; let rpc: Rpc; - let testRpc: TestRpc; let executedTxs = 0; let stateTreeInfo: TreeInfo; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); rpc = createRpc(); - testRpc = await getTestRpc(lightWasm); - /// These are constant test accounts in between test runs payer = await newAccountWithLamports(rpc, 10e9, 256); bob = await newAccountWithLamports(rpc, 10e9, 256); @@ -96,7 +70,7 @@ describe('rpc-interop', () => { const senderAccounts = await rpc.getCompressedAccountsByOwner( payer.publicKey, ); - const senderAccountsTest = await testRpc.getCompressedAccountsByOwner( + const senderAccountsTest = await rpc.getCompressedAccountsByOwner( payer.publicKey, ); @@ -107,7 +81,7 @@ describe('rpc-interop', () => { assert.isTrue(hash.eq(hashTest)); const validityProof = await rpc.getValidityProof([hash]); - const validityProofTest = await testRpc.getValidityProof([hashTest]); + const validityProofTest = await rpc.getValidityProof([hashTest]); validityProof.leafIndices.forEach((leafIndex, index) => { assert.equal(leafIndex, validityProofTest.leafIndices[index]); @@ -140,7 +114,7 @@ describe('rpc-interop', () => { executedTxs++; /// Executes a transfer using a 'validityProof' directly from a prover. - await transfer(testRpc, payer, 1e5, payer, bob.publicKey); + await transfer(rpc, payer, 1e5, payer, bob.publicKey); executedTxs++; }); @@ -160,7 +134,7 @@ describe('rpc-interop', () => { /// consistent proof metadata for same address const validityProof = await rpc.getValidityProof([], [newAddress]); - const validityProofTest = await testRpc.getValidityProof( + const validityProofTest = await rpc.getValidityProof( [], [newAddress], ); @@ -205,7 +179,7 @@ describe('rpc-interop', () => { /// Creates a compressed account with address using a (non-inclusion) /// 'validityProof' directly from a prover. await createAccount( - testRpc, + rpc, payer, newAddressSeeds, LightSystemProgram.programId, @@ -220,8 +194,9 @@ describe('rpc-interop', () => { it.skipIf(featureFlags.isV2())( 'getValidityProof [noforester] (combined) should match', async () => { - const senderAccountsTest = - await testRpc.getCompressedAccountsByOwner(payer.publicKey); + const senderAccountsTest = await rpc.getCompressedAccountsByOwner( + payer.publicKey, + ); // wait for photon to be in sync await sleep(3000); const senderAccounts = await rpc.getCompressedAccountsByOwner( @@ -246,7 +221,7 @@ describe('rpc-interop', () => { [hash], [newAddress], ); - const validityProofTest = await testRpc.getValidityProof( + const validityProofTest = await rpc.getValidityProof( [hashTest], [newAddress], ); @@ -256,7 +231,7 @@ describe('rpc-interop', () => { await rpc.getMultipleCompressedAccountProofs([hash]) )[0]; const compressedAccountProofTest = ( - await testRpc.getMultipleCompressedAccountProofs([hashTest]) + await rpc.getMultipleCompressedAccountProofs([hashTest]) )[0]; compressedAccountProof.merkleProof.forEach((proof, index) => { @@ -270,7 +245,7 @@ describe('rpc-interop', () => { await rpc.getMultipleNewAddressProofs([newAddress]) )[0]; const newAddressProofTest = ( - await testRpc.getMultipleNewAddressProofs([newAddress]) + await rpc.getMultipleNewAddressProofs([newAddress]) )[0]; assert.isTrue( @@ -369,7 +344,7 @@ describe('rpc-interop', () => { await rpc.getMultipleNewAddressProofs([newAddress]) )[0]; const newAddressProofTest = ( - await testRpc.getMultipleNewAddressProofs([newAddress]) + await rpc.getMultipleNewAddressProofs([newAddress]) )[0]; assert.isTrue( @@ -444,10 +419,9 @@ describe('rpc-interop', () => { ); /// get reference proofs for sender - const testProofs = - await testRpc.getMultipleCompressedAccountProofs( - prePayerAccounts.items.map(account => bn(account.hash)), - ); + const testProofs = await rpc.getMultipleCompressedAccountProofs( + prePayerAccounts.items.map(account => bn(account.hash)), + ); /// get photon proofs for sender const proofs = await rpc.getMultipleCompressedAccountProofs( @@ -515,7 +489,7 @@ describe('rpc-interop', () => { payer.publicKey, ); - const senderAccountsTest = await testRpc.getCompressedAccountsByOwner( + const senderAccountsTest = await rpc.getCompressedAccountsByOwner( payer.publicKey, ); @@ -559,7 +533,7 @@ describe('rpc-interop', () => { const receiverAccounts = await rpc.getCompressedAccountsByOwner( bob.publicKey, ); - const receiverAccountsTest = await testRpc.getCompressedAccountsByOwner( + const receiverAccountsTest = await rpc.getCompressedAccountsByOwner( bob.publicKey, ); @@ -595,7 +569,7 @@ describe('rpc-interop', () => { undefined, bn(senderAccounts.items[0].hash), ); - const compressedAccountTest = await testRpc.getCompressedAccount( + const compressedAccountTest = await rpc.getCompressedAccount( undefined, bn(senderAccounts.items[0].hash), ); @@ -621,10 +595,9 @@ describe('rpc-interop', () => { const compressedAccounts = await rpc.getMultipleCompressedAccounts( senderAccounts.items.map(account => bn(account.hash)), ); - const compressedAccountsTest = - await testRpc.getMultipleCompressedAccounts( - senderAccounts.items.map(account => bn(account.hash)), - ); + const compressedAccountsTest = await rpc.getMultipleCompressedAccounts( + senderAccounts.items.map(account => bn(account.hash)), + ); assert.equal(compressedAccounts.length, compressedAccountsTest.length); @@ -739,8 +712,9 @@ describe('rpc-interop', () => { payer.publicKey, ); - const allAccountsTestRpc = - await testRpc.getCompressedAccountsByOwner(payer.publicKey); + const allAccountsTestRpc = await rpc.getCompressedAccountsByOwner( + payer.publicKey, + ); const allAccountsRpc = await rpc.getCompressedAccountsByOwner( payer.publicKey, ); @@ -805,10 +779,7 @@ describe('rpc-interop', () => { ); await expect( - testRpc.getCompressedAccount( - bn(latestAccount.address!), - undefined, - ), + rpc.getCompressedAccount(bn(latestAccount.address!), undefined), ).rejects.toThrow(); assert.isTrue( diff --git a/js/stateless.js/tests/e2e/test-rpc.test.ts b/js/stateless.js/tests/e2e/test-rpc.test.ts index 6a5b137299..e837cc7536 100644 --- a/js/stateless.js/tests/e2e/test-rpc.test.ts +++ b/js/stateless.js/tests/e2e/test-rpc.test.ts @@ -9,13 +9,12 @@ import { import { newAccountWithLamports } from '../../src/test-helpers/test-utils'; import { compress, decompress, transfer } from '../../src/actions'; import { bn, CompressedAccountWithMerkleContext } from '../../src/state'; -import { getTestRpc, TestRpc } from '../../src/test-helpers/test-rpc'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; +import { Rpc, createRpc } from '../../src/rpc'; /// TODO: add test case for payer != address describe('test-rpc', () => { const { merkleTree } = defaultTestStateTreeAccounts(); - let rpc: TestRpc; + let rpc: Rpc; let payer: Signer; let preCompressBalance: number; @@ -26,8 +25,7 @@ describe('test-rpc', () => { const refCompressLamports = 1e7; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); refPayer = await newAccountWithLamports(rpc, 1e9, 256); payer = await newAccountWithLamports(rpc, 1e9, 256); diff --git a/js/stateless.js/tests/e2e/transfer.test.ts b/js/stateless.js/tests/e2e/transfer.test.ts index b2fbcb6997..4c5db7d87a 100644 --- a/js/stateless.js/tests/e2e/transfer.test.ts +++ b/js/stateless.js/tests/e2e/transfer.test.ts @@ -1,11 +1,9 @@ import { describe, it, assert, beforeAll } from 'vitest'; import { Signer } from '@solana/web3.js'; import { newAccountWithLamports } from '../../src/test-helpers/test-utils'; -import { Rpc } from '../../src/rpc'; +import { Rpc, createRpc } from '../../src/rpc'; import { bn, compress } from '../../src'; import { transfer } from '../../src/actions/transfer'; -import { getTestRpc } from '../../src/test-helpers/test-rpc'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; describe('transfer', () => { let rpc: Rpc; @@ -13,8 +11,7 @@ describe('transfer', () => { let bob: Signer; beforeAll(async () => { - const lightWasm = await WasmFactory.getInstance(); - rpc = await getTestRpc(lightWasm); + rpc = createRpc(); payer = await newAccountWithLamports(rpc, 2e9, 256); bob = await newAccountWithLamports(rpc, 2e9, 256); From 7a9d1c2ee0f925e709db258460eae36fb5131126 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 03:12:51 +0000 Subject: [PATCH 12/15] fix surfpool startup --- cli/src/utils/process.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cli/src/utils/process.ts b/cli/src/utils/process.ts index 7271bc8032..066d390230 100644 --- a/cli/src/utils/process.ts +++ b/cli/src/utils/process.ts @@ -54,6 +54,7 @@ export async function killProcess(processName: string) { const targetProcesses = processList.filter( (proc) => proc.pid !== process.pid && + proc.pid !== process.ppid && (proc.name.includes(processName) || proc.cmd.includes(processName)), ); @@ -217,6 +218,14 @@ export function spawnBinary( }, }); + // Close parent's copy of the file descriptors so the child owns them + // exclusively and node's event loop isn't held open. + fs.closeSync(out); + fs.closeSync(err); + + // Allow node to exit without waiting for the detached child. + spawnedProcess.unref(); + spawnedProcess.on("close", async (code) => { console.log(`${binaryName} process exited with code ${code}`); if (code !== 0) { From 78c111836e51362d9fb981d644aab5f5d84019d7 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 03:27:38 +0000 Subject: [PATCH 13/15] fix test rpc test --- js/stateless.js/tests/e2e/rpc-interop.test.ts | 79 +++++++++++++------ js/stateless.js/tests/e2e/test-rpc.test.ts | 7 +- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/js/stateless.js/tests/e2e/rpc-interop.test.ts b/js/stateless.js/tests/e2e/rpc-interop.test.ts index 0693a4549c..2fd459c594 100644 --- a/js/stateless.js/tests/e2e/rpc-interop.test.ts +++ b/js/stateless.js/tests/e2e/rpc-interop.test.ts @@ -18,19 +18,45 @@ import { selectStateTreeInfo, sleep, } from '../../src'; +import { getTestRpc, TestRpc } from '../../src/test-helpers/test-rpc'; import { transfer } from '../../src/actions/transfer'; +import { WasmFactory } from '@lightprotocol/hasher.rs'; import { randomBytes } from 'tweetnacl'; +const log = async ( + rpc: Rpc | TestRpc, + payer: Signer, + prefix: string = 'rpc', +) => { + const accounts = await rpc.getCompressedAccountsByOwner(payer.publicKey); + console.log(`${prefix} - indexed: `, accounts.items.length); +}; + +// debug helper. +const logIndexed = async ( + rpc: Rpc, + testRpc: TestRpc, + payer: Signer, + prefix: string = '', +) => { + await log(testRpc, payer, `${prefix} test-rpc `); + await log(rpc, payer, `${prefix} rpc`); +}; + describe('rpc-interop', () => { LightSystemProgram.deriveCompressedSolPda(); let payer: Signer; let bob: Signer; let rpc: Rpc; + let testRpc: TestRpc; let executedTxs = 0; let stateTreeInfo: TreeInfo; beforeAll(async () => { + const lightWasm = await WasmFactory.getInstance(); rpc = createRpc(); + testRpc = await getTestRpc(lightWasm); + /// These are constant test accounts in between test runs payer = await newAccountWithLamports(rpc, 10e9, 256); bob = await newAccountWithLamports(rpc, 10e9, 256); @@ -70,7 +96,7 @@ describe('rpc-interop', () => { const senderAccounts = await rpc.getCompressedAccountsByOwner( payer.publicKey, ); - const senderAccountsTest = await rpc.getCompressedAccountsByOwner( + const senderAccountsTest = await testRpc.getCompressedAccountsByOwner( payer.publicKey, ); @@ -81,7 +107,7 @@ describe('rpc-interop', () => { assert.isTrue(hash.eq(hashTest)); const validityProof = await rpc.getValidityProof([hash]); - const validityProofTest = await rpc.getValidityProof([hashTest]); + const validityProofTest = await testRpc.getValidityProof([hashTest]); validityProof.leafIndices.forEach((leafIndex, index) => { assert.equal(leafIndex, validityProofTest.leafIndices[index]); @@ -114,7 +140,7 @@ describe('rpc-interop', () => { executedTxs++; /// Executes a transfer using a 'validityProof' directly from a prover. - await transfer(rpc, payer, 1e5, payer, bob.publicKey); + await transfer(testRpc, payer, 1e5, payer, bob.publicKey); executedTxs++; }); @@ -134,7 +160,7 @@ describe('rpc-interop', () => { /// consistent proof metadata for same address const validityProof = await rpc.getValidityProof([], [newAddress]); - const validityProofTest = await rpc.getValidityProof( + const validityProofTest = await testRpc.getValidityProof( [], [newAddress], ); @@ -179,7 +205,7 @@ describe('rpc-interop', () => { /// Creates a compressed account with address using a (non-inclusion) /// 'validityProof' directly from a prover. await createAccount( - rpc, + testRpc, payer, newAddressSeeds, LightSystemProgram.programId, @@ -194,9 +220,8 @@ describe('rpc-interop', () => { it.skipIf(featureFlags.isV2())( 'getValidityProof [noforester] (combined) should match', async () => { - const senderAccountsTest = await rpc.getCompressedAccountsByOwner( - payer.publicKey, - ); + const senderAccountsTest = + await testRpc.getCompressedAccountsByOwner(payer.publicKey); // wait for photon to be in sync await sleep(3000); const senderAccounts = await rpc.getCompressedAccountsByOwner( @@ -221,7 +246,7 @@ describe('rpc-interop', () => { [hash], [newAddress], ); - const validityProofTest = await rpc.getValidityProof( + const validityProofTest = await testRpc.getValidityProof( [hashTest], [newAddress], ); @@ -231,7 +256,7 @@ describe('rpc-interop', () => { await rpc.getMultipleCompressedAccountProofs([hash]) )[0]; const compressedAccountProofTest = ( - await rpc.getMultipleCompressedAccountProofs([hashTest]) + await testRpc.getMultipleCompressedAccountProofs([hashTest]) )[0]; compressedAccountProof.merkleProof.forEach((proof, index) => { @@ -245,7 +270,7 @@ describe('rpc-interop', () => { await rpc.getMultipleNewAddressProofs([newAddress]) )[0]; const newAddressProofTest = ( - await rpc.getMultipleNewAddressProofs([newAddress]) + await testRpc.getMultipleNewAddressProofs([newAddress]) )[0]; assert.isTrue( @@ -344,7 +369,7 @@ describe('rpc-interop', () => { await rpc.getMultipleNewAddressProofs([newAddress]) )[0]; const newAddressProofTest = ( - await rpc.getMultipleNewAddressProofs([newAddress]) + await testRpc.getMultipleNewAddressProofs([newAddress]) )[0]; assert.isTrue( @@ -419,9 +444,10 @@ describe('rpc-interop', () => { ); /// get reference proofs for sender - const testProofs = await rpc.getMultipleCompressedAccountProofs( - prePayerAccounts.items.map(account => bn(account.hash)), - ); + const testProofs = + await testRpc.getMultipleCompressedAccountProofs( + prePayerAccounts.items.map(account => bn(account.hash)), + ); /// get photon proofs for sender const proofs = await rpc.getMultipleCompressedAccountProofs( @@ -489,7 +515,7 @@ describe('rpc-interop', () => { payer.publicKey, ); - const senderAccountsTest = await rpc.getCompressedAccountsByOwner( + const senderAccountsTest = await testRpc.getCompressedAccountsByOwner( payer.publicKey, ); @@ -533,7 +559,7 @@ describe('rpc-interop', () => { const receiverAccounts = await rpc.getCompressedAccountsByOwner( bob.publicKey, ); - const receiverAccountsTest = await rpc.getCompressedAccountsByOwner( + const receiverAccountsTest = await testRpc.getCompressedAccountsByOwner( bob.publicKey, ); @@ -569,7 +595,7 @@ describe('rpc-interop', () => { undefined, bn(senderAccounts.items[0].hash), ); - const compressedAccountTest = await rpc.getCompressedAccount( + const compressedAccountTest = await testRpc.getCompressedAccount( undefined, bn(senderAccounts.items[0].hash), ); @@ -595,9 +621,10 @@ describe('rpc-interop', () => { const compressedAccounts = await rpc.getMultipleCompressedAccounts( senderAccounts.items.map(account => bn(account.hash)), ); - const compressedAccountsTest = await rpc.getMultipleCompressedAccounts( - senderAccounts.items.map(account => bn(account.hash)), - ); + const compressedAccountsTest = + await testRpc.getMultipleCompressedAccounts( + senderAccounts.items.map(account => bn(account.hash)), + ); assert.equal(compressedAccounts.length, compressedAccountsTest.length); @@ -712,9 +739,8 @@ describe('rpc-interop', () => { payer.publicKey, ); - const allAccountsTestRpc = await rpc.getCompressedAccountsByOwner( - payer.publicKey, - ); + const allAccountsTestRpc = + await testRpc.getCompressedAccountsByOwner(payer.publicKey); const allAccountsRpc = await rpc.getCompressedAccountsByOwner( payer.publicKey, ); @@ -779,7 +805,10 @@ describe('rpc-interop', () => { ); await expect( - rpc.getCompressedAccount(bn(latestAccount.address!), undefined), + testRpc.getCompressedAccount( + bn(latestAccount.address!), + undefined, + ), ).rejects.toThrow(); assert.isTrue( diff --git a/js/stateless.js/tests/e2e/test-rpc.test.ts b/js/stateless.js/tests/e2e/test-rpc.test.ts index e837cc7536..fb0825131c 100644 --- a/js/stateless.js/tests/e2e/test-rpc.test.ts +++ b/js/stateless.js/tests/e2e/test-rpc.test.ts @@ -9,7 +9,9 @@ import { import { newAccountWithLamports } from '../../src/test-helpers/test-utils'; import { compress, decompress, transfer } from '../../src/actions'; import { bn, CompressedAccountWithMerkleContext } from '../../src/state'; -import { Rpc, createRpc } from '../../src/rpc'; +import { Rpc } from '../../src/rpc'; +import { getTestRpc } from '../../src/test-helpers/test-rpc/test-rpc'; +import { WasmFactory } from '@lightprotocol/hasher.rs'; /// TODO: add test case for payer != address describe('test-rpc', () => { @@ -25,7 +27,8 @@ describe('test-rpc', () => { const refCompressLamports = 1e7; beforeAll(async () => { - rpc = createRpc(); + const lightWasm = await WasmFactory.getInstance(); + rpc = await getTestRpc(lightWasm); refPayer = await newAccountWithLamports(rpc, 1e9, 256); payer = await newAccountWithLamports(rpc, 1e9, 256); From 0883eed0f93544022f3abbf3526e8a0abfb652c7 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 05:30:35 +0000 Subject: [PATCH 14/15] revert migration tests to test rpc --- js/compressed-token/tests/e2e/v1-v2-migration.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/compressed-token/tests/e2e/v1-v2-migration.test.ts b/js/compressed-token/tests/e2e/v1-v2-migration.test.ts index ffba6131bf..652e650b82 100644 --- a/js/compressed-token/tests/e2e/v1-v2-migration.test.ts +++ b/js/compressed-token/tests/e2e/v1-v2-migration.test.ts @@ -31,7 +31,7 @@ import { Rpc, bn, newAccountWithLamports, - createRpc, + getTestRpc, selectStateTreeInfo, TreeInfo, TreeType, @@ -39,6 +39,7 @@ import { VERSION, ParsedTokenAccount, } from '@lightprotocol/stateless.js'; +import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createMint, mintTo, @@ -136,7 +137,8 @@ describe('v1-v2-migration', () => { let tokenPoolInfos: TokenPoolInfo[]; beforeAll(async () => { - rpc = createRpc(); + const lightWasm = await WasmFactory.getInstance(); + rpc = await getTestRpc(lightWasm); payer = await newAccountWithLamports(rpc, 10e9); mintAuthority = Keypair.generate(); From ea19c6f9042ef3d8354e97b00d094cdc10e7eba4 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 06:02:10 +0000 Subject: [PATCH 15/15] cleanup test prints --- js/compressed-token/src/actions/transfer.ts | 32 ++----- js/stateless.js/src/rpc.ts | 5 -- .../test-rpc/get-parsed-events.ts | 15 ---- .../src/test-helpers/test-rpc/test-rpc.ts | 88 ------------------- 4 files changed, 6 insertions(+), 134 deletions(-) diff --git a/js/compressed-token/src/actions/transfer.ts b/js/compressed-token/src/actions/transfer.ts index 9fefb49446..fa5138057d 100644 --- a/js/compressed-token/src/actions/transfer.ts +++ b/js/compressed-token/src/actions/transfer.ts @@ -62,32 +62,12 @@ export async function transfer( amount, ); - const proofInputs = inputAccounts.map(account => ({ - hash: account.compressedAccount.hash, - tree: account.compressedAccount.treeInfo.tree, - queue: account.compressedAccount.treeInfo.queue, - })); - console.log( - '[transfer] getValidityProofV0 inputs:', - JSON.stringify(proofInputs, null, 2), - ); - - const proof = await rpc.getValidityProofV0(proofInputs); - - console.log( - '[transfer] getValidityProofV0 result:', - JSON.stringify( - { - rootIndices: proof.rootIndices, - roots: proof.roots, - leafIndices: proof.leafIndices, - leaves: proof.leaves, - treeInfos: proof.treeInfos, - proveByIndices: proof.proveByIndices, - }, - null, - 2, - ), + const proof = await rpc.getValidityProofV0( + inputAccounts.map(account => ({ + hash: account.compressedAccount.hash, + tree: account.compressedAccount.treeInfo.tree, + queue: account.compressedAccount.treeInfo.queue, + })), ); // V1→V2 migration handled inside CompressedTokenProgram.transfer diff --git a/js/stateless.js/src/rpc.ts b/js/stateless.js/src/rpc.ts index 5744e06cf2..bce24ea030 100644 --- a/js/stateless.js/src/rpc.ts +++ b/js/stateless.js/src/rpc.ts @@ -2033,11 +2033,6 @@ export class Rpc extends Connection implements CompressionApiInterface { const value = res.result.value as any; - console.log( - '[getValidityProofAndRpcContext] raw photon response value:', - JSON.stringify(value, null, 2), - ); - if (useV2Parsing) { return { value: { diff --git a/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts b/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts index 42208e4787..4bdf35d25f 100644 --- a/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts +++ b/js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts @@ -49,9 +49,6 @@ export async function getParsedEvents( 'confirmed', ) ).map(s => s.signature); - console.log( - `[getParsedEvents] found ${signatures.length} signatures for accountCompressionProgram ${accountCompressionProgram.toBase58()}`, - ); const txs: (ParsedTransactionWithMeta | null)[] = []; // `getParsedTransactions` uses a JSON-RPC batch request under the hood. @@ -72,18 +69,6 @@ export async function getParsedEvents( for (const txParsed of txs) { if (!txParsed || !txParsed.transaction || !txParsed.meta) continue; - console.log( - `[getParsedEvents] tx ${txParsed.transaction.signatures[0]}:`, - JSON.stringify( - { - err: txParsed.meta.err, - innerInstructions: txParsed.meta.innerInstructions, - }, - null, - 2, - ), - ); - if ( !txParsed.meta.innerInstructions || txParsed.meta.innerInstructions.length == 0 diff --git a/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts b/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts index 8cf2216f91..0686510f02 100644 --- a/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts +++ b/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts @@ -369,94 +369,6 @@ export class TestRpc extends Connection implements CompressionApiInterface { ); const root = bn(merkleTree.root()); - // Debug: read on-chain tree state - try { - const treeAccountInfo = - await this.getAccountInfo(tree); - if (treeAccountInfo) { - const data = treeAccountInfo.data; - // CMT starts at offset 224 (8 disc + 216 MerkleTreeMetadata) - // All fields are usize (u64 on 64-bit): - const cmtOff = 224; - const height = Number( - data.readBigUInt64LE(cmtOff), - ); - const canopyDepth = Number( - data.readBigUInt64LE(cmtOff + 8), - ); - const nextIndex = Number( - data.readBigUInt64LE(cmtOff + 16), - ); - const seqNum = Number( - data.readBigUInt64LE(cmtOff + 24), - ); - // Vec metadata at offset 288 - const vm = 288; - const fsCap = Number(data.readBigUInt64LE(vm)); - const fsLen = Number( - data.readBigUInt64LE(vm + 8), - ); - const clCap = Number( - data.readBigUInt64LE(vm + 16), - ); - const clLen = Number( - data.readBigUInt64LE(vm + 24), - ); - const rootsCap = Number( - data.readBigUInt64LE(vm + 48), - ); - const rootsLen = Number( - data.readBigUInt64LE(vm + 56), - ); - const rootsFirst = Number( - data.readBigUInt64LE(vm + 64), - ); - const rootsLast = Number( - data.readBigUInt64LE(vm + 72), - ); - - console.log( - `[TestRpc] ON-CHAIN: height=${height}, canopyDepth=${canopyDepth}, nextIndex=${nextIndex}, seqNum=${seqNum}`, - ); - console.log( - `[TestRpc] ON-CHAIN roots: cap=${rootsCap}, len=${rootsLen}, first=${rootsFirst}, last=${rootsLast}`, - ); - - // Compute roots data offset and read the actual root at rootIndex - // Data sections: filled_subtrees(26*32=832) + changelog(1400*872) + roots data - const dataStart = 384; // 288 + 96 bytes of all vec metadata - const fsDataSize = fsCap * 32; - const clEntrySize = 32 + height * 32 + 8; // root + path + index - const clDataSize = clCap * clEntrySize; - const rootsDataOffset = - dataStart + fsDataSize + clDataSize; - const targetRootIdx = leaves.length; - const rootBytes = data.slice( - rootsDataOffset + targetRootIdx * 32, - rootsDataOffset + (targetRootIdx + 1) * 32, - ); - const rootHex = - Buffer.from(rootBytes).toString('hex'); - console.log( - `[TestRpc] ON-CHAIN root[${targetRootIdx}] = ${rootHex}`, - ); - console.log( - `[TestRpc] TestRpc root = ${root.toString('hex').slice(0, 64)}`, - ); - console.log( - `[TestRpc] MISMATCH: TestRpc leaves=${leaves.length}, rootIndex=${leaves.length}, on-chain nextIndex=${nextIndex}, seqNum=${seqNum}`, - ); - } - } catch (e) { - console.log( - `[TestRpc] Failed to read on-chain tree state: ${e}`, - ); - } - - console.log( - `[TestRpc] V1 proof: tree=${treeKey}, leafIndex=${leafIndex}, leaves.length=${leaves.length}, rootIndex=${leaves.length}, root=${root.toString('hex').slice(0, 16)}...`, - ); - const merkleProof: MerkleContextWithMerkleProof = { hash: bn(hashes[i].toArray('be', 32)), treeInfo,