mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
feat: wire chain clients into AppState (feature-gated)
Add ChainClients (infra/chains.rs): per-chain data-source handles built once at startup from config and shared via AppState. Each client is present only when its chain feature flag is on, and a malformed chain endpoint now fails startup (fail-closed) rather than the first request. Cloneable (grin's session state is shared via Arc) so it lives in the cloneable AppState. Foundation for the wallet-handlers phase — handlers will read these clients from State. No endpoints yet (openapi unchanged).
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
//! Per-chain client handles, constructed once at startup and shared via
|
||||||
|
//! [`crate::AppState`]. Each chain's client is config-pointed at an external node
|
||||||
|
//! service (Fulcrum/Electrum, monero/wownero-lws, grin-wallet + grin node) and is
|
||||||
|
//! present only when that chain's feature flag is enabled. A malformed endpoint
|
||||||
|
//! fails startup (fail-closed), not the first request.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::error::AppError;
|
||||||
|
use crate::infra::electrum::ElectrumClient;
|
||||||
|
use crate::infra::grin::GrinClient;
|
||||||
|
use crate::infra::lws::LwsClient;
|
||||||
|
|
||||||
|
/// The enabled chains' data-source clients. Cloneable (the heavyweight grin
|
||||||
|
/// session state is shared via `Arc`) so it can live in the cloneable `AppState`.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ChainClients {
|
||||||
|
pub btc: Option<ElectrumClient>,
|
||||||
|
pub ltc: Option<ElectrumClient>,
|
||||||
|
pub xmr: Option<LwsClient>,
|
||||||
|
pub wow: Option<LwsClient>,
|
||||||
|
pub grin: Option<Arc<GrinClient>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChainClients {
|
||||||
|
/// Build clients for the enabled chains from config.
|
||||||
|
pub fn from_config(cfg: &Config) -> Result<Self, AppError> {
|
||||||
|
let flags = &cfg.features.chains;
|
||||||
|
Ok(Self {
|
||||||
|
btc: flags
|
||||||
|
.btc
|
||||||
|
.then(|| ElectrumClient::bitcoin(&cfg.chains.btc))
|
||||||
|
.transpose()?,
|
||||||
|
ltc: flags
|
||||||
|
.ltc
|
||||||
|
.then(|| ElectrumClient::litecoin(&cfg.chains.ltc))
|
||||||
|
.transpose()?,
|
||||||
|
xmr: flags
|
||||||
|
.xmr
|
||||||
|
.then(|| LwsClient::monero(&cfg.chains.xmr))
|
||||||
|
.transpose()?,
|
||||||
|
wow: flags
|
||||||
|
.wow
|
||||||
|
.then(|| LwsClient::wownero(&cfg.chains.wow))
|
||||||
|
.transpose()?,
|
||||||
|
grin: flags
|
||||||
|
.grin
|
||||||
|
.then(|| GrinClient::new(&cfg.chains.grin).map(Arc::new))
|
||||||
|
.transpose()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Infrastructure layer: adapters for external services (database, chain nodes,
|
//! Infrastructure layer: adapters for external services (database, chain nodes,
|
||||||
//! light-wallet servers). Chain clients are wired in as they land.
|
//! light-wallet servers). Chain clients are wired in as they land.
|
||||||
|
|
||||||
|
pub mod chains;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod electrum;
|
pub mod electrum;
|
||||||
pub mod grin;
|
pub mod grin;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use tokio::sync::RwLock;
|
|||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::core::session::{SessionManager, WebChallenge};
|
use crate::core::session::{SessionManager, WebChallenge};
|
||||||
|
use crate::infra::chains::ChainClients;
|
||||||
use crate::infra::db::Database;
|
use crate::infra::db::Database;
|
||||||
|
|
||||||
/// Shared application state injected into handlers via `State<Arc<AppState>>`.
|
/// Shared application state injected into handlers via `State<Arc<AppState>>`.
|
||||||
@@ -31,6 +32,8 @@ pub struct AppState {
|
|||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub db: Database,
|
pub db: Database,
|
||||||
pub sessions: SessionManager,
|
pub sessions: SessionManager,
|
||||||
|
/// Per-chain data-source clients (present only for enabled chains).
|
||||||
|
pub chains: ChainClients,
|
||||||
/// In-memory website-auth challenges, keyed by nonce. Single-node store;
|
/// In-memory website-auth challenges, keyed by nonce. Single-node store;
|
||||||
/// a shared/stateless variant is the load-balanced-fleet path.
|
/// a shared/stateless variant is the load-balanced-fleet path.
|
||||||
pub web_challenges: Arc<RwLock<HashMap<String, WebChallenge>>>,
|
pub web_challenges: Arc<RwLock<HashMap<String, WebChallenge>>>,
|
||||||
|
|||||||
+4
-1
@@ -9,7 +9,8 @@ use std::sync::Arc;
|
|||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
|
|
||||||
use smirk_backend_core::{
|
use smirk_backend_core::{
|
||||||
build_router, config::Config, core::session::SessionManager, infra::db::Database, AppState,
|
build_router, config::Config, core::session::SessionManager, infra::chains::ChainClients,
|
||||||
|
infra::db::Database, AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -33,12 +34,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
config.secrets.ip_salt.clone(),
|
config.secrets.ip_salt.clone(),
|
||||||
);
|
);
|
||||||
let sessions = SessionManager::new(&config.auth.jwt_secret, config.auth.jwt_expiry_hours);
|
let sessions = SessionManager::new(&config.auth.jwt_secret, config.auth.jwt_expiry_hours);
|
||||||
|
let chains = ChainClients::from_config(&config)?;
|
||||||
|
|
||||||
let addr = format!("{}:{}", config.server_host, config.server_port);
|
let addr = format!("{}:{}", config.server_host, config.server_port);
|
||||||
let state = Arc::new(AppState {
|
let state = Arc::new(AppState {
|
||||||
config,
|
config,
|
||||||
db,
|
db,
|
||||||
sessions,
|
sessions,
|
||||||
|
chains,
|
||||||
web_challenges: Arc::default(),
|
web_challenges: Arc::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -21,7 +21,8 @@ use tower::ServiceExt;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use smirk_backend_core::{
|
use smirk_backend_core::{
|
||||||
build_router, config::Config, core::session::SessionManager, infra::db::Database, AppState,
|
build_router, config::Config, core::session::SessionManager, infra::chains::ChainClients,
|
||||||
|
infra::db::Database, AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
static INIT_ENV: Once = Once::new();
|
static INIT_ENV: Once = Once::new();
|
||||||
@@ -68,10 +69,12 @@ pub async fn try_app() -> Option<TestApp> {
|
|||||||
config.secrets.ip_salt.clone(),
|
config.secrets.ip_salt.clone(),
|
||||||
);
|
);
|
||||||
let sessions = SessionManager::new(&config.auth.jwt_secret, config.auth.jwt_expiry_hours);
|
let sessions = SessionManager::new(&config.auth.jwt_secret, config.auth.jwt_expiry_hours);
|
||||||
|
let chains = ChainClients::from_config(&config).expect("build chain clients");
|
||||||
let state = Arc::new(AppState {
|
let state = Arc::new(AppState {
|
||||||
config,
|
config,
|
||||||
db,
|
db,
|
||||||
sessions,
|
sessions,
|
||||||
|
chains,
|
||||||
web_challenges: Arc::default(),
|
web_challenges: Arc::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user