Skip to main content

kora_lib/
state.rs

1use once_cell::sync::Lazy;
2use parking_lot::RwLock;
3use std::sync::{
4    atomic::{AtomicPtr, Ordering},
5    Arc,
6};
7
8use crate::{config::Config, error::KoraError, signer::SignerPool};
9
10// Global signer pool (for multi-signer support)
11static GLOBAL_SIGNER_POOL: Lazy<RwLock<Option<Arc<SignerPool>>>> = Lazy::new(|| RwLock::new(None));
12
13// Global config with zero-cost reads and hot-reload capability
14static GLOBAL_CONFIG: AtomicPtr<Config> = AtomicPtr::new(std::ptr::null_mut());
15
16/// Get a request-scoped signer with optional signer_key for consistency across related calls
17pub fn get_request_signer_with_signer_key(
18    signer_key: Option<&str>,
19) -> Result<Arc<solana_keychain::Signer>, KoraError> {
20    let pool = get_signer_pool()?;
21
22    // If client provided a signer signer_key, try to use that specific signer
23    if let Some(signer_key) = signer_key {
24        return pool.get_signer_by_pubkey(signer_key);
25    }
26
27    // Use configured selection strategy (defaults to round-robin if not specified)
28    pool.get_next_signer()
29        .map_err(|e| KoraError::InternalServerError(format!("Failed to get signer from pool: {e}")))
30}
31
32/// Initialize the global signer pool with a SignerPool instance
33pub fn init_signer_pool(pool: SignerPool) -> Result<(), KoraError> {
34    let mut pool_guard = GLOBAL_SIGNER_POOL.write();
35    if pool_guard.is_some() {
36        return Err(KoraError::InternalServerError("Signer pool already initialized".to_string()));
37    }
38
39    log::info!(
40        "Initializing global signer pool with {} signers using {:?} strategy",
41        pool.len(),
42        pool.strategy()
43    );
44
45    *pool_guard = Some(Arc::new(pool));
46    Ok(())
47}
48
49/// Get a reference to the global signer pool
50pub fn get_signer_pool() -> Result<Arc<SignerPool>, KoraError> {
51    let pool_guard = GLOBAL_SIGNER_POOL.read();
52    match &*pool_guard {
53        Some(pool) => Ok(Arc::clone(pool)),
54        None => Err(KoraError::InternalServerError("Signer pool not initialized".to_string())),
55    }
56}
57
58/// Get information about all signers (for monitoring/debugging)
59pub fn get_signers_info() -> Result<Vec<crate::signer::SignerInfo>, KoraError> {
60    let pool = get_signer_pool()?;
61    Ok(pool.get_signers_info())
62}
63
64/// Update the global signer configs with a new config (test only)
65#[cfg(test)]
66pub fn update_signer_pool(new_pool: SignerPool) -> Result<(), KoraError> {
67    let mut pool_guard = GLOBAL_SIGNER_POOL.write();
68
69    *pool_guard = Some(Arc::new(new_pool));
70
71    Ok(())
72}
73
74/// Initialize the global config with a Config instance
75pub fn init_config(config: Config) -> Result<(), KoraError> {
76    let current_ptr = GLOBAL_CONFIG.load(Ordering::Acquire);
77    if !current_ptr.is_null() {
78        return Err(KoraError::InternalServerError("Config already initialized".to_string()));
79    }
80
81    let config_ptr = Box::into_raw(Box::new(config));
82    GLOBAL_CONFIG.store(config_ptr, Ordering::Release);
83    Ok(())
84}
85
86/// Get a reference to the global config (zero-cost read)
87pub fn get_config() -> Result<&'static Config, KoraError> {
88    let config_ptr = GLOBAL_CONFIG.load(Ordering::Acquire);
89    if config_ptr.is_null() {
90        return Err(KoraError::InternalServerError("Config not initialized".to_string()));
91    }
92
93    // SAFETY: We ensure the pointer is valid and the config lives for the duration of the program
94    Ok(unsafe { &*config_ptr })
95}
96
97/// Update the global config with a new full config (test only)
98#[cfg(test)]
99pub fn update_config(new_config: Config) -> Result<(), KoraError> {
100    let old_ptr = GLOBAL_CONFIG.load(Ordering::Acquire);
101    let new_ptr = Box::into_raw(Box::new(new_config));
102
103    GLOBAL_CONFIG.store(new_ptr, Ordering::Release);
104
105    // Clean up old config if it exists
106    if !old_ptr.is_null() {
107        unsafe {
108            let _ = Box::from_raw(old_ptr);
109        }
110    }
111
112    Ok(())
113}