diff --git a/.github/workflows/forester-tests.yml b/.github/workflows/forester-tests.yml index 80bc622e43..ddaf015326 100644 --- a/.github/workflows/forester-tests.yml +++ b/.github/workflows/forester-tests.yml @@ -43,7 +43,7 @@ jobs: test: name: Forester e2e test runs-on: warp-ubuntu-latest-x64-4x - timeout-minutes: 30 + timeout-minutes: 45 services: redis: diff --git a/forester/package.json b/forester/package.json index 071126b739..5113c31257 100644 --- a/forester/package.json +++ b/forester/package.json @@ -4,7 +4,7 @@ "license": "GPL-3.0", "scripts": { "build": "cargo build", - "test": "source .env && RUST_LOG=forester=debug,forester_utils=debug cargo test --package forester test_e2e_v2 -- --nocapture", + "test": "source .env && RUST_LOG=forester=debug,forester_utils=debug cargo test --package forester e2e_test -- --nocapture", "docker:build": "docker build --tag forester -f Dockerfile .." }, "devDependencies": { diff --git a/forester/src/cli.rs b/forester/src/cli.rs index 1ed573038c..1e80cbaf24 100644 --- a/forester/src/cli.rs +++ b/forester/src/cli.rs @@ -1,4 +1,4 @@ -use clap::{Parser, Subcommand}; +use clap::{Parser, Subcommand, ValueEnum}; #[derive(Parser)] #[clap(author, version, about, long_about = None)] @@ -173,6 +173,14 @@ pub struct StartArgs { #[arg(long, env = "FORESTER_SEND_TRANSACTION_RATE_LIMIT")] pub send_tx_rate_limit: Option, + + #[arg( + long, + env = "FORESTER_PROCESSOR_MODE", + default_value_t = ProcessorMode::All, + help = "Processor mode: v1 (process only v1 trees), v2 (process only v2 trees), all (process all trees)" + )] + pub processor_mode: ProcessorMode, } #[derive(Parser, Clone, Debug)] @@ -204,3 +212,80 @@ impl StatusArgs { self.push_gateway_url.is_some() } } + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +pub enum ProcessorMode { + #[clap(name = "v1")] + V1, + #[clap(name = "v2")] + V2, + #[clap(name = "all")] + #[default] + All, +} + +impl std::fmt::Display for ProcessorMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProcessorMode::V1 => write!(f, "v1"), + ProcessorMode::V2 => write!(f, "v2"), + ProcessorMode::All => write!(f, "all"), + } + } +} + +#[cfg(test)] +mod tests { + use clap::Parser; + + use super::*; + + #[test] + fn test_processor_mode_parsing() { + // Test v1-only + let args = StartArgs::try_parse_from([ + "forester", + "--processor-mode", "v1", + "--rpc-url", "http://test.com", + "--payer", "[1,2,3]", + "--derivation", "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]" + ]).unwrap(); + assert_eq!(args.processor_mode, ProcessorMode::V1); + + // Test v2-only + let args = StartArgs::try_parse_from([ + "forester", + "--processor-mode", "v2", + "--rpc-url", "http://test.com", + "--payer", "[1,2,3]", + "--derivation", "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]" + ]).unwrap(); + assert_eq!(args.processor_mode, ProcessorMode::V2); + + // Test all (default) + let args = StartArgs::try_parse_from([ + "forester", + "--rpc-url", "http://test.com", + "--payer", "[1,2,3]", + "--derivation", "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]" + ]).unwrap(); + assert_eq!(args.processor_mode, ProcessorMode::All); + + // Test invalid mode should fail + let result = StartArgs::try_parse_from([ + "forester", + "--processor-mode", "invalid-mode", + "--rpc-url", "http://test.com", + "--payer", "[1,2,3]", + "--derivation", "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]" + ]); + assert!(result.is_err()); + } + + #[test] + fn test_processor_mode_display() { + assert_eq!(ProcessorMode::V1.to_string(), "v1"); + assert_eq!(ProcessorMode::V2.to_string(), "v2"); + assert_eq!(ProcessorMode::All.to_string(), "all"); + } +} diff --git a/forester/src/config.rs b/forester/src/config.rs index 8317aeae15..d4ae01f1e9 100644 --- a/forester/src/config.rs +++ b/forester/src/config.rs @@ -8,7 +8,7 @@ use light_registry::{EpochPda, ForesterEpochPda}; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use crate::{ - cli::{StartArgs, StatusArgs}, + cli::{ProcessorMode, StartArgs, StatusArgs}, errors::ConfigError, Result, }; @@ -245,10 +245,10 @@ impl ForesterConfig { slot_update_interval_seconds: args.slot_update_interval_seconds, tree_discovery_interval_seconds: args.tree_discovery_interval_seconds, enable_metrics: args.enable_metrics(), - skip_v1_state_trees: false, - skip_v2_state_trees: false, - skip_v1_address_trees: false, - skip_v2_address_trees: false, + skip_v1_state_trees: args.processor_mode == ProcessorMode::V2, + skip_v2_state_trees: args.processor_mode == ProcessorMode::V1, + skip_v1_address_trees: args.processor_mode == ProcessorMode::V2, + skip_v2_address_trees: args.processor_mode == ProcessorMode::V1, }, rpc_pool_config: RpcPoolConfig { max_size: args.rpc_pool_size, diff --git a/forester/src/epoch_manager.rs b/forester/src/epoch_manager.rs index 90cb9a397f..3f0e4599f6 100644 --- a/forester/src/epoch_manager.rs +++ b/forester/src/epoch_manager.rs @@ -1453,6 +1453,19 @@ pub async fn run_service( ) -> Result<()> { info_span!("run_service", forester = %config.payer_keypair.pubkey()) .in_scope(|| async { + let processor_mode_str = match ( + config.general_config.skip_v1_state_trees + && config.general_config.skip_v1_address_trees, + config.general_config.skip_v2_state_trees + && config.general_config.skip_v2_address_trees, + ) { + (true, false) => "v2", + (false, true) => "v1", + (false, false) => "all", + _ => "unknown", + }; + info!("Starting forester in {} mode", processor_mode_str); + const INITIAL_RETRY_DELAY: Duration = Duration::from_secs(1); const MAX_RETRY_DELAY: Duration = Duration::from_secs(30); diff --git a/forester/tests/priority_fee_test.rs b/forester/tests/priority_fee_test.rs index 5b17b90660..1cce599015 100644 --- a/forester/tests/priority_fee_test.rs +++ b/forester/tests/priority_fee_test.rs @@ -1,5 +1,5 @@ use forester::{ - cli::StartArgs, + cli::{ProcessorMode, StartArgs}, processor::v1::{ config::CapConfig, helpers::{get_capped_priority_fee, request_priority_fee_estimate}, @@ -75,6 +75,7 @@ async fn test_priority_fee_request() { rpc_rate_limit: None, photon_rate_limit: None, send_tx_rate_limit: None, + processor_mode: ProcessorMode::All, }; let config = ForesterConfig::new_for_start(&args).expect("Failed to create config"); diff --git a/sdk-libs/client/src/rpc/client.rs b/sdk-libs/client/src/rpc/client.rs index 99557f6739..05419e8a2b 100644 --- a/sdk-libs/client/src/rpc/client.rs +++ b/sdk-libs/client/src/rpc/client.rs @@ -175,7 +175,6 @@ impl LightClient { self.retry_config.max_retries, e ); - tokio::task::yield_now().await; sleep(self.retry_config.retry_delay).await; } else { return Err(e);