|
| 1 | +#!/usr/bin/env ts-node |
| 2 | +/* eslint-disable @typescript-eslint/no-explicit-any */ |
| 3 | + |
| 4 | +import { program } from "commander"; |
| 5 | +import winston from "winston"; |
| 6 | +import { CachedSolanaRpcFactory } from "../src/providers/solana/cachedRpcFactory"; |
| 7 | +import { ClusterUrl, type Commitment } from "@solana/kit"; |
| 8 | +import { getSlot } from "../src/arch/svm/SpokeUtils"; |
| 9 | + |
| 10 | +/** |
| 11 | + * USAGE EXAMPLES: |
| 12 | + * |
| 13 | + * Basic usage (default settings): |
| 14 | + * npx ts-node testGetSlot.e2e.ts |
| 15 | + * |
| 16 | + * Test with specific endpoint: |
| 17 | + * npx ts-node testGetSlot.e2e.ts -e https://api.devnet.solana.com |
| 18 | + * |
| 19 | + * Test with more iterations: |
| 20 | + * npx ts-node testGetSlot.e2e.ts -n 20 |
| 21 | + * |
| 22 | + * Test with different commitment level: |
| 23 | + * npx ts-node testGetSlot.e2e.ts -c finalized |
| 24 | + */ |
| 25 | + |
| 26 | +// Configure winston logger |
| 27 | +const logger = winston.createLogger({ |
| 28 | + level: "debug", |
| 29 | + format: winston.format.combine( |
| 30 | + winston.format.timestamp(), |
| 31 | + winston.format.colorize(), |
| 32 | + winston.format.printf(({ timestamp, level, message, ...meta }) => { |
| 33 | + const metaStr = Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ""; |
| 34 | + return `${timestamp} [${level}]: ${message} ${metaStr}`; |
| 35 | + }) |
| 36 | + ), |
| 37 | + transports: [new winston.transports.Console()], |
| 38 | +}); |
| 39 | + |
| 40 | +interface TestOptions { |
| 41 | + endpoint: string; |
| 42 | + retries: number; |
| 43 | + retryDelay: number; |
| 44 | + chainId: number; |
| 45 | + iterations: number; |
| 46 | + commitment: Commitment; |
| 47 | +} |
| 48 | + |
| 49 | +async function testGetSlot( |
| 50 | + rpcClient: any, |
| 51 | + commitment: Commitment, |
| 52 | + iteration: number |
| 53 | +): Promise<{ |
| 54 | + iteration: number; |
| 55 | + slot: string; |
| 56 | + success: boolean; |
| 57 | + commitment: Commitment; |
| 58 | + time: number; |
| 59 | + error?: string; |
| 60 | +}> { |
| 61 | + console.log(`--- Iteration ${iteration} (commitment: ${commitment}) ---`); |
| 62 | + const startTime = Date.now(); |
| 63 | + |
| 64 | + try { |
| 65 | + const slot = await getSlot(rpcClient, commitment, logger); |
| 66 | + const elapsedTime = Date.now() - startTime; |
| 67 | + |
| 68 | + console.log(`✅ Slot ${slot.toString()} (commitment: ${commitment}) (${elapsedTime}ms)`); |
| 69 | + return { |
| 70 | + iteration, |
| 71 | + slot: slot.toString(), |
| 72 | + success: true, |
| 73 | + commitment, |
| 74 | + time: elapsedTime, |
| 75 | + }; |
| 76 | + } catch (error: unknown) { |
| 77 | + const elapsedTime = Date.now() - startTime; |
| 78 | + const errorMsg = error instanceof Error ? error.message : String(error); |
| 79 | + console.log(`❌ Failed: ${errorMsg} (${elapsedTime}ms)`); |
| 80 | + return { |
| 81 | + iteration, |
| 82 | + slot: "unknown", |
| 83 | + success: false, |
| 84 | + commitment, |
| 85 | + error: errorMsg, |
| 86 | + time: elapsedTime, |
| 87 | + }; |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +async function runTest(options: TestOptions) { |
| 92 | + console.log("🚀 Starting getSlot E2E Test"); |
| 93 | + console.log("Configuration:", { |
| 94 | + endpoint: options.endpoint, |
| 95 | + retries: options.retries, |
| 96 | + retryDelay: options.retryDelay, |
| 97 | + iterations: options.iterations, |
| 98 | + commitment: options.commitment, |
| 99 | + }); |
| 100 | + |
| 101 | + // Create the RPC factory |
| 102 | + const rpcFactory = new CachedSolanaRpcFactory( |
| 103 | + "test-get-slot", |
| 104 | + undefined, // redisClient |
| 105 | + options.retries, |
| 106 | + options.retryDelay, |
| 107 | + 10, // maxConcurrency |
| 108 | + 0, // pctRpcCallsLogged |
| 109 | + logger, |
| 110 | + options.endpoint as ClusterUrl, |
| 111 | + options.chainId |
| 112 | + ); |
| 113 | + |
| 114 | + const rpcClient = rpcFactory.createRpcClient(); |
| 115 | + |
| 116 | + console.log(`\n📡 Running ${options.iterations} sequential tests...\n`); |
| 117 | + |
| 118 | + const testStartTime = Date.now(); |
| 119 | + const results: Array<{ |
| 120 | + iteration: number; |
| 121 | + slot: string; |
| 122 | + success: boolean; |
| 123 | + commitment: Commitment; |
| 124 | + time: number; |
| 125 | + error?: string; |
| 126 | + }> = []; |
| 127 | + |
| 128 | + for (let i = 0; i < options.iterations; i++) { |
| 129 | + const result = await testGetSlot(rpcClient, options.commitment, i + 1); |
| 130 | + results.push(result); |
| 131 | + } |
| 132 | + |
| 133 | + const totalTime = Date.now() - testStartTime; |
| 134 | + |
| 135 | + // Print summary |
| 136 | + console.log("\n📊 Test Summary:"); |
| 137 | + console.log("================"); |
| 138 | + const successful = results.filter((r) => r.success).length; |
| 139 | + const failed = results.filter((r) => !r.success).length; |
| 140 | + const retried = results.filter((r) => r.time > 1000); // We know an attempt retried if it took > 1 second |
| 141 | + const retryCount = retried.length; |
| 142 | + const retriedSuccessfully = retried.filter((r) => r.success).length; |
| 143 | + const avgTime = results.reduce((sum, r) => sum + r.time, 0) / results.length; |
| 144 | + const longestTime = results.reduce((max, r) => Math.max(max, r.time), 0); |
| 145 | + const retriedAvgTime = retried.reduce((sum, r) => sum + r.time, 0) / retried.length; |
| 146 | + const retriedLongestTime = retried.reduce((max, r) => Math.max(max, r.time), 0); |
| 147 | + |
| 148 | + console.log(`Successful: ${successful} / ${options.iterations}`); |
| 149 | + console.log(`Failed: ${failed} / ${options.iterations}`); |
| 150 | + console.log(`Average time per call: ${avgTime.toFixed(0)}ms`); |
| 151 | + console.log(`Longest time per call: ${longestTime.toFixed(0)}ms`); |
| 152 | + console.log(`Retried: ${retryCount} / ${options.iterations}`); |
| 153 | + console.log(`Retried successfully: ${retriedSuccessfully} / ${retryCount}`); |
| 154 | + console.log(`Retried unsuccessfully: ${retryCount - retriedSuccessfully} / ${retryCount}`); |
| 155 | + console.log(`Retried average time: ${retriedAvgTime.toFixed(0)}ms`); |
| 156 | + console.log(`Retried longest time: ${retriedLongestTime.toFixed(0)}ms`); |
| 157 | + console.log(`Total test time: ${totalTime}ms`); |
| 158 | + |
| 159 | + if (failed > 0) { |
| 160 | + console.log("\n❌ Failed tests:"); |
| 161 | + results |
| 162 | + .filter((r) => !r.success) |
| 163 | + .forEach((r) => { |
| 164 | + console.log(` Iteration ${r.iteration}: ${r.error}`); |
| 165 | + }); |
| 166 | + |
| 167 | + // Show error patterns |
| 168 | + const errorPatterns = new Map<string, number>(); |
| 169 | + results |
| 170 | + .filter((r) => !r.success && r.error) |
| 171 | + .forEach((r) => { |
| 172 | + const errorType = r.error!.split(":")[0].trim(); |
| 173 | + errorPatterns.set(errorType, (errorPatterns.get(errorType) || 0) + 1); |
| 174 | + }); |
| 175 | + |
| 176 | + console.log("\n🔍 Error patterns:"); |
| 177 | + errorPatterns.forEach((count, pattern) => { |
| 178 | + console.log(` ${pattern}: ${count} occurrences`); |
| 179 | + }); |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +// CLI setup |
| 184 | +program.name("test-get-slot").description("Test getSlot function with configurable commitment parameter"); |
| 185 | + |
| 186 | +program |
| 187 | + .option("-e, --endpoint <url>", "Solana RPC endpoint URL", "https://api.mainnet-beta.solana.com") |
| 188 | + .option("-r, --retries <number>", "Number of retries on failure", "2") |
| 189 | + .option("-d, --retry-delay <seconds>", "Delay between retries in seconds", "1") |
| 190 | + .option("-i, --chain-id <number>", "Chain ID for Solana", "101") |
| 191 | + .option("-n, --iterations <number>", "Number of test iterations", "10") |
| 192 | + .option("-c, --commitment <commitment>", "Commitment level (processed, confirmed, finalized)", "confirmed") |
| 193 | + .action(async (options) => { |
| 194 | + // Validate commitment parameter |
| 195 | + const validCommitments: Commitment[] = ["processed", "confirmed", "finalized"]; |
| 196 | + if (!validCommitments.includes(options.commitment as Commitment)) { |
| 197 | + console.error(`Invalid commitment level: ${options.commitment}. Valid options: ${validCommitments.join(", ")}`); |
| 198 | + process.exit(1); |
| 199 | + } |
| 200 | + |
| 201 | + const testOptions: TestOptions = { |
| 202 | + endpoint: options.endpoint, |
| 203 | + retries: parseInt(options.retries), |
| 204 | + retryDelay: parseFloat(options.retryDelay), |
| 205 | + chainId: parseInt(options.chainId), |
| 206 | + iterations: parseInt(options.iterations), |
| 207 | + commitment: options.commitment as Commitment, |
| 208 | + }; |
| 209 | + |
| 210 | + await runTest(testOptions); |
| 211 | + }); |
| 212 | + |
| 213 | +program.parse(); |
0 commit comments