Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cb00cdd
feat: add priority fee configuration and handling
sergeytimoshin Mar 10, 2026
b4dbcbb
fix: add Signature import to solana_sdk in epoch_manager
sergeytimoshin Mar 10, 2026
c96fb48
feat: add confirmation configuration for smart transactions and updat…
sergeytimoshin Mar 10, 2026
2e29f6d
format
sergeytimoshin Mar 10, 2026
06bf63c
refactor
sergeytimoshin Mar 10, 2026
9a2f947
fix: improve error handling
sergeytimoshin Mar 10, 2026
f8a91b7
refactor
sergeytimoshin Mar 10, 2026
6ace1dd
cleanup
sergeytimoshin Mar 11, 2026
87583f7
- refactored transaction sending logic in `send_transaction.rs` and `…
sergeytimoshin Mar 11, 2026
496a178
cleanup
sergeytimoshin Mar 11, 2026
c43fb18
cleanup
sergeytimoshin Mar 11, 2026
bd47fbd
format
sergeytimoshin Mar 11, 2026
b7717db
refactor error handling in transaction processing to include batch no…
sergeytimoshin Mar 11, 2026
1db4984
add logging to forester tests workflow
sergeytimoshin Mar 11, 2026
7a90bb2
dump photon.log on failure
sergeytimoshin Mar 11, 2026
9e61083
add indexer health checks and tracker wait functions in tests
sergeytimoshin Mar 11, 2026
6c8ce79
more logs
sergeytimoshin Mar 11, 2026
f69daa3
add local transaction dumping functionality and enhance test failure …
sergeytimoshin Mar 11, 2026
e838ba9
refactor transaction extraction and block fetching in local transacti…
sergeytimoshin Mar 11, 2026
596e618
refactor WorkReportError handling to use registry_error_code for impr…
sergeytimoshin Mar 12, 2026
f92f478
refactor ForesterError handling to use registry_error_code for NotEli…
sergeytimoshin Mar 12, 2026
0a1664f
wip
sergeytimoshin Mar 12, 2026
d6e98bf
refactor local transaction dumping to handle duplicates and improve o…
sergeytimoshin Mar 12, 2026
32b4197
cleanup
sergeytimoshin Mar 12, 2026
a13d98e
debugging
sergeytimoshin Mar 12, 2026
b3e7feb
unify test validator and photon commiment
sergeytimoshin Mar 12, 2026
011fd68
increase timeout for Forester e2e test to 120 minutes
sergeytimoshin Mar 12, 2026
d586836
custom surfpool branch
sergeytimoshin Mar 12, 2026
6905cd0
refactor: update surfpool version to 1.1.1 and remove unused binary p…
sergeytimoshin Mar 13, 2026
d057a33
fix: remove unnecessary environment variable from spawnBinary call
sergeytimoshin Mar 13, 2026
4e1e19e
refactor: remove unused dependencies and streamline eligibility check…
sergeytimoshin Mar 13, 2026
c69a618
format
sergeytimoshin Mar 13, 2026
ce1f5e8
format
sergeytimoshin Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/forester-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
test:
name: Forester e2e test
runs-on: ubuntu-latest
timeout-minutes: 60
timeout-minutes: 120

services:
redis:
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions cli/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export const LIGHT_PROVER_PROCESS_NAME = "light-prover";
export const INDEXER_PROCESS_NAME = "photon";
export const FORESTER_PROCESS_NAME = "forester";

export const SURFPOOL_VERSION = "1.0.1";
export const SURFPOOL_RELEASE_TAG = "v1.0.1-light";
export const SURFPOOL_VERSION = "1.1.1";
export const SURFPOOL_RELEASE_TAG = "v1.1.1-light";

// PHOTON_VERSION, PHOTON_GIT_COMMIT, and PHOTON_GIT_REPO are auto-generated
// from the external/photon submodule at build time. See photonVersion.generated.ts.
Expand Down
1 change: 0 additions & 1 deletion forester/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ light-token = { workspace = true }
light-compressed-token-sdk = { workspace = true }
solana-rpc-client-api = { workspace = true }
solana-transaction-status = { workspace = true }
bb8 = { workspace = true }
base64 = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true }
Expand Down
3 changes: 2 additions & 1 deletion forester/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ When a fallback RPC URL is configured, the pool automatically switches to it if
| `--legacy-ixs-per-tx` | `LEGACY_IXS_PER_TX` | 1 | Instructions per V1 transaction |
| `--cu-limit` | `CU_LIMIT` | 1000000 | Compute unit limit per transaction |
| `--enable-priority-fees` | `ENABLE_PRIORITY_FEES` | false | Enable dynamic priority fees |
| `--priority-fee-microlamports` | `PRIORITY_FEE_MICROLAMPORTS` | | Fixed priority fee in micro-lamports per compute unit |
| `--lookup-table-address` | `LOOKUP_TABLE_ADDRESS` | | Address lookup table for versioned transactions |
| `--helius-rpc` | `HELIUS_RPC` | false | Use Helius `getProgramAccountsV2` |

Expand Down Expand Up @@ -144,7 +145,7 @@ cargo run -- start \
--processor-mode v2 \
--max-concurrent-sends 200 \
--cu-limit 400000 \
--enable-priority-fees true
--priority-fee-microlamports 10000
```

## Status & Health
Expand Down
78 changes: 66 additions & 12 deletions forester/src/api_server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
use std::{collections::HashMap, net::SocketAddr, sync::Arc, thread::JoinHandle, time::Duration};
use std::{
collections::HashMap,
net::SocketAddr,
sync::{mpsc, Arc},
thread::JoinHandle,
time::Duration,
};

use serde::{Deserialize, Serialize};
use tokio::sync::{broadcast, oneshot, watch};
Expand Down Expand Up @@ -1025,21 +1031,25 @@ async fn run_compressible_provider(
///
/// # Returns
/// An `ApiServerHandle` that can be used to trigger graceful shutdown
pub fn spawn_api_server(config: ApiServerConfig) -> ApiServerHandle {
pub fn spawn_api_server(config: ApiServerConfig) -> anyhow::Result<ApiServerHandle> {
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let (startup_tx, startup_rx) = mpsc::sync_channel(1);
let run_id_for_handle = config.run_id.clone();

let thread_handle = std::thread::spawn(move || {
let run_id = config.run_id.clone();
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(e) => {
Err(error) => {
error!(
event = "api_server_runtime_create_failed",
run_id = %run_id,
error = %e,
error = %error,
"Failed to create tokio runtime for API server"
);
let _ = startup_tx.send(Err(anyhow::anyhow!(
"Failed to create tokio runtime for API server: {error}"
)));
return;
}
};
Expand All @@ -1063,10 +1073,24 @@ pub fn spawn_api_server(config: ApiServerConfig) -> ApiServerHandle {
);

// Shared HTTP client with timeout for external requests (Prometheus)
let http_client = reqwest::Client::builder()
let http_client = match reqwest::Client::builder()
.timeout(EXTERNAL_HTTP_TIMEOUT)
.build()
.expect("Failed to create HTTP client");
{
Ok(client) => client,
Err(error) => {
error!(
event = "api_server_http_client_create_failed",
run_id = %run_id,
error = %error,
"Failed to create shared HTTP client for API server"
);
let _ = startup_tx.send(Err(anyhow::anyhow!(
"Failed to create shared HTTP client for API server: {error}"
)));
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

// Build trackers from config
let trackers = config
Expand Down Expand Up @@ -1193,9 +1217,27 @@ pub fn spawn_api_server(config: ApiServerConfig) -> ApiServerHandle {
.or(compressible_route)
.with(cors);

let listener = match tokio::net::TcpListener::bind(addr).await {
Ok(listener) => listener,
Err(error) => {
error!(
event = "api_server_bind_failed",
run_id = %run_id,
address = %addr,
error = %error,
"Failed to bind API server socket"
);
let _ = startup_tx.send(Err(anyhow::anyhow!(
"Failed to bind API server to {addr}: {error}"
)));
return;
}
};

let _ = startup_tx.send(Ok(()));
Comment thread
sergeytimoshin marked this conversation as resolved.

warp::serve(routes)
.bind(addr)
.await
.incoming(listener)
.graceful({
let run_id_for_shutdown = run_id.clone();
async move {
Expand All @@ -1219,10 +1261,22 @@ pub fn spawn_api_server(config: ApiServerConfig) -> ApiServerHandle {
});
});

ApiServerHandle {
thread_handle,
shutdown_tx,
run_id: run_id_for_handle,
match startup_rx.recv() {
Ok(Ok(())) => Ok(ApiServerHandle {
thread_handle,
shutdown_tx,
run_id: run_id_for_handle,
}),
Ok(Err(error)) => {
let _ = thread_handle.join();
Err(error)
}
Err(_) => {
let _ = thread_handle.join();
Err(anyhow::anyhow!(
"API server startup acknowledgment channel closed before initialization completed"
))
}
}
}

Expand Down
45 changes: 44 additions & 1 deletion forester/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,24 @@ pub struct StartArgs {
#[arg(long, env = "CU_LIMIT", default_value = "1000000")]
pub cu_limit: u32,

#[arg(long, env = "ENABLE_PRIORITY_FEES", default_value = "false")]
#[arg(
long,
env = "ENABLE_PRIORITY_FEES",
default_value = "false",
conflicts_with = "priority_fee_microlamports",
help = "Enable dynamic priority fees via RPC estimation"
)]
pub enable_priority_fees: bool,

#[arg(
long,
env = "PRIORITY_FEE_MICROLAMPORTS",
value_parser = clap::value_parser!(u64).range(1..),
conflicts_with = "enable_priority_fees",
help = "Fixed priority fee in micro-lamports per compute unit"
)]
pub priority_fee_microlamports: Option<u64>,

#[arg(long, env = "RPC_POOL_SIZE", default_value = "100")]
pub rpc_pool_size: u32,

Expand Down Expand Up @@ -525,6 +540,34 @@ mod tests {
assert!(result.is_err());
}

#[test]
fn test_priority_fee_parsing() {
let args = StartArgs::try_parse_from([
"forester",
"--rpc-url", "http://test.com",
"--indexer-url", "http://indexer.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]",
"--priority-fee-microlamports", "25000"
]).unwrap();
assert_eq!(args.priority_fee_microlamports, Some(25_000));
assert!(!args.enable_priority_fees);
}

#[test]
fn test_priority_fee_modes_conflict() {
let result = StartArgs::try_parse_from([
"forester",
"--rpc-url", "http://test.com",
"--indexer-url", "http://indexer.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]",
"--enable-priority-fees", "true",
"--priority-fee-microlamports", "25000"
]);
Comment thread
sergeytimoshin marked this conversation as resolved.
assert!(result.is_err());
}

#[test]
fn test_processor_mode_display() {
assert_eq!(ProcessorMode::V1.to_string(), "v1");
Expand Down
6 changes: 6 additions & 0 deletions forester/src/compressible/ctoken/compressor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use tracing::debug;
use super::{state::CTokenAccountTracker, types::CTokenAccountState};
use crate::{
compressible::traits::{send_and_confirm_with_tracking, CompressibleTracker},
smart_transaction::TransactionPolicy,
Result,
};

Expand All @@ -30,6 +31,7 @@ pub struct CTokenCompressor<R: Rpc + Indexer> {
rpc_pool: Arc<SolanaRpcPool<R>>,
tracker: Arc<CTokenAccountTracker>,
payer_keypair: Keypair,
transaction_policy: TransactionPolicy,
}

impl<R: Rpc + Indexer> Clone for CTokenCompressor<R> {
Expand All @@ -38,6 +40,7 @@ impl<R: Rpc + Indexer> Clone for CTokenCompressor<R> {
rpc_pool: Arc::clone(&self.rpc_pool),
tracker: Arc::clone(&self.tracker),
payer_keypair: self.payer_keypair.insecure_clone(),
transaction_policy: self.transaction_policy,
}
}
}
Expand All @@ -47,11 +50,13 @@ impl<R: Rpc + Indexer> CTokenCompressor<R> {
rpc_pool: Arc<SolanaRpcPool<R>>,
tracker: Arc<CTokenAccountTracker>,
payer_keypair: Keypair,
transaction_policy: TransactionPolicy,
) -> Self {
Self {
rpc_pool,
tracker,
payer_keypair,
transaction_policy,
}
}

Expand Down Expand Up @@ -249,6 +254,7 @@ impl<R: Rpc + Indexer> CTokenCompressor<R> {
&mut *rpc,
&[ix],
&self.payer_keypair,
self.transaction_policy,
&*self.tracker,
&pubkeys,
"compress_and_close",
Expand Down
Loading
Loading