diff --git a/server/Cargo.lock b/server/Cargo.lock index c25ae8e..f052b4b 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -28,6 +28,18 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -203,6 +215,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-prometheus" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb15221c30bbb32e99873d348d89d7bc2138d6199520aa473a1bdb0d8e5721e8" +dependencies = [ + "axum", + "bytes", + "futures-core", + "http", + "http-body", + "matchit", + "metrics", + "metrics-exporter-prometheus", + "pin-project-lite", + "tokio", + "tower", + "tower-http", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -242,6 +274,27 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bb8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d8b8e1a22743d9241575c6ba822cf9c8fef34771c86ab7e477a4fbfd254e5" +dependencies = [ + "futures-util", + "parking_lot", + "tokio", +] + +[[package]] +name = "bb8-redis" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5143936af5e1eea1a881e3e3d21b6777da6315e5e307bc3d0c2301c44fa37da9" +dependencies = [ + "bb8", + "redis", +] + [[package]] name = "bigdecimal" version = "0.4.8" @@ -363,6 +416,20 @@ dependencies = [ "inout", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -448,6 +515,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -1569,6 +1645,46 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "metrics" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" +dependencies = [ + "base64 0.22.1", + "indexmap 2.10.0", + "metrics", + "metrics-util", + "quanta", + "thiserror 2.0.16", +] + +[[package]] +name = "metrics-util" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe8db7a05415d0f919ffb905afa37784f71901c9a773188876984b4f769ab986" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "metrics", + "quanta", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" @@ -1863,6 +1979,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "potential_utf" version = "0.1.2" @@ -1949,6 +2071,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quinn" version = "0.11.8" @@ -2084,6 +2221,70 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redis" +version = "0.32.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "014cc767fefab6a3e798ca45112bccad9c6e0e218fbd49720042716c73cfef44" +dependencies = [ + "bytes", + "cfg-if", + "combine", + "futures-util", + "itoa", + "num-bigint", + "percent-encoding", + "pin-project-lite", + "ryu", + "sha1_smol", + "socket2 0.6.0", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "redis-macros" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac3609707f8c68aac6f3871c9a9596a7a9e30e3cc42ebba741d13b39e27c6d8" +dependencies = [ + "redis", + "redis-macros-derive", + "serde", + "serde_json", +] + +[[package]] +name = "redis-macros-derive" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923db076e23f014fd5c5504fba397d3dd06b29724a3dd8e21aa7a3423c00b129" +dependencies = [ + "proc-macro2", + "quote", + "redis", + "syn", +] + [[package]] name = "redox_syscall" version = "0.5.17" @@ -2536,9 +2737,13 @@ dependencies = [ "argon2", "axum", "axum-extra", + "axum-prometheus", + "bb8-redis", "bigdecimal", "dotenvy", "jsonwebtoken", + "redis", + "redis-macros", "serde", "serde_json", "sha2", @@ -2569,6 +2774,12 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -2642,6 +2853,12 @@ dependencies = [ "time", ] +[[package]] +name = "sketches-ddsketch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" + [[package]] name = "slab" version = "0.4.11" @@ -3314,6 +3531,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -3360,6 +3590,7 @@ dependencies = [ "http-body", "iri-string", "pin-project-lite", + "tokio", "tower", "tower-layer", "tower-service", @@ -3842,6 +4073,22 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -3851,6 +4098,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.61.2" diff --git a/server/Cargo.toml b/server/Cargo.toml index 5081027..6a808f1 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -24,7 +24,7 @@ serde = {version="1.0.219", features=["derive"]} tokio = { version = "1.45.1", features = ["full"] } starknet = "0.17.0" dotenvy = "0.15.7" -tower-http = {version="0.6.6", features=["cors","trace"]} +tower-http = { version = "0.6.6", features = ["cors", "timeout", "trace"] } sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio-rustls", "chrono", "bigdecimal", "time", "uuid"] } thiserror = "2.0.16" serde_json = "1.0.143" @@ -42,3 +42,7 @@ utoipa-axum = "0.2.0" utoipa-scalar = { version = "0.3.0", features = ["axum"] } sha2 = "0.10.9" uuid = { version = "1.18.1", features = ["v4"] } +bb8-redis = "0.24" +redis-macros = "0.5.6" +redis = "0.32.7" +axum-prometheus = "0.9.0" diff --git a/server/docker-compose.yml b/server/docker-compose.yml index 97171b0..45b0bff 100644 --- a/server/docker-compose.yml +++ b/server/docker-compose.yml @@ -16,10 +16,12 @@ services: JWT_SECRET: "${JWT_SECRET}" JWT_EXPIRED_IN: "${JWT_EXPIRED_IN}" JWT_MAXAGE: "${JWT_MAXAGE}" + REDIS_URL: "${REDIS_URL}" ports: - "8080:8080" depends_on: - db + - redis db: image: postgres:16 @@ -31,5 +33,35 @@ services: volumes: - pgdata:/var/lib/postgresql/data + redis: + image: redis:7-alpine + restart: always + ports: + - "6379:6379" + + prometheus: + image: prom/prometheus + container_name: prometheus + ports: + - "9091:9090" + command: --config.file=/etc/prometheus/prometheus.yml + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + extra_hosts: + - "hosts.docker.internal:host-gateway" + + grafana: + image: grafana/grafana:latest + container_name: grafana + restart: always + ports: + - "3000:3000" + environment: + GF_SECURITY_ADMIN_USER: "${GRAFANA_ADMIN_USER}" + GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_ADMIN_PASSWORD}" + depends_on: + - prometheus + + volumes: pgdata: diff --git a/server/prometheus.yml b/server/prometheus.yml new file mode 100644 index 0000000..4593f50 --- /dev/null +++ b/server/prometheus.yml @@ -0,0 +1,8 @@ +global: + scrape_interval: 5s + evaluation_interval: 30s + +scrape_configs: + - job_name: "paymesh_app_monitoring" + static_configs: + - targets: ["host.docker.internal:8080"] \ No newline at end of file diff --git a/server/src/lib.rs b/server/src/lib.rs index ef8fe58..cff1fc6 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,11 +1,11 @@ pub mod libs { pub mod auth; - pub mod cache; pub mod config; pub mod db; pub mod error; pub mod logging; pub mod middleware; + pub mod redis; pub mod router; pub mod utopia; } @@ -28,13 +28,74 @@ pub mod util { pub mod validate_address; } -use crate::libs::{cache::Cache, config::Env}; +use crate::libs::{config::Env, error::ApiError, redis::RedisPool}; +use redis::AsyncCommands; use sqlx::PgPool; #[derive(Clone)] pub struct AppState { pub db: PgPool, - pub cache: Cache, + pub redis: RedisPool, pub env: Env, } + +impl AppState { + async fn add_group_address(&self, addr: &str) -> Result<(), ApiError> { + let mut conn = self.redis.get().await.expect("Error connecting to redis"); + conn.sadd("group_funding_address", addr).await.map_err(|e| { + tracing::error!("Error adding group address to redis: {:?}", e); + ApiError::Internal("Error adding group address to redis") + }) + } + async fn has_group_address(&self, addr: &str) -> Result { + let mut conn = self.redis.get().await.expect("Error connecting to redis"); + conn.sismember("group_funding_address", addr) + .await + .map_err(|e| { + tracing::error!("Error checking group address in redis: {:?}", e); + ApiError::Internal("Error checking group address in redis") + }) + } + + async fn get_all_group_addresses(&self) -> Result, ApiError> { + let mut conn = self.redis.get().await.expect("Error connecting to redis"); + let addresses: Vec = + conn.smembers("group_funding_addresses") + .await + .map_err(|e| { + tracing::error!("Error getting group addresses from redis: {:?}", e); + ApiError::Internal("Error getting group addresses from redis") + })?; + Ok(addresses) + } + + async fn add_crowd_funding_address(&self, addr: &str) -> Result<(), ApiError> { + let mut conn = self.redis.get().await.expect("Error connecting to redis"); + conn.sadd("crowd_funding_address", addr).await.map_err(|e| { + tracing::error!("Error adding crowd funding address to redis: {:?}", e); + ApiError::Internal("Error adding crowd funding address to redis") + }) + } + async fn _has_crowd_funding_address(&self, addr: &str) -> Result { + let mut conn = self.redis.get().await.expect("Error connecting to redis"); + conn.sismember("crowd_funding_address", addr) + .await + .map_err(|e| { + tracing::error!("Error checking crowd funding address in redis: {:?}", e); + ApiError::Internal("Error checking crowd funding address in redis") + }) + } + + async fn get_all_crowd_funding_addresses(&self) -> Result, ApiError> { + let mut conn = self.redis.get().await.expect("Error connecting to redis"); + let addresses: Vec = + conn.smembers("crowd_funding_addresses") + .await + .map_err(|e| { + tracing::error!("Error getting crowd funding addresses from redis: {:?}", e); + ApiError::Internal("Error getting crowd funding addresses from redis") + })?; + Ok(addresses) + } +} diff --git a/server/src/libs/redis.rs b/server/src/libs/redis.rs new file mode 100644 index 0000000..f4cb1a1 --- /dev/null +++ b/server/src/libs/redis.rs @@ -0,0 +1,78 @@ +use std::env; + +use bb8_redis::{RedisConnectionManager, bb8}; +use redis::AsyncCommands; +use sqlx::PgPool; + +pub type RedisPool = bb8::Pool; + +pub async fn init_redis(pool: &PgPool) -> RedisPool { + let redis_url = env::var("REDIS_URL").unwrap_or_else(|_| "redis://localhost:6379".to_string()); + let manager = RedisConnectionManager::new(redis_url).expect("init redis failed"); + + let redis_pool = bb8::Pool::builder() + .max_size(100) + .build(manager) + .await + .expect("Failed to build Redis pool"); + + { + let crowd_funding_addresses: Vec = + sqlx::query_scalar("SELECT pool_address FROM crowd_funding") + .fetch_all(pool) + .await + .expect("Failed to get crowd funding addresses"); + + let group_funding_addresses: Vec = + sqlx::query_scalar("SELECT group_address FROM groups") + .fetch_all(pool) + .await + .expect("Failed to get group addresses"); + + let mut conn = redis_pool + .get() + .await + .expect("Failed to get Redis connection"); + + let _: () = conn + .sadd("crowd_funding_addresses", &crowd_funding_addresses) + .await + .expect("Failed to store crowd funding addresses"); + + let _: () = conn + .sadd("group_funding_addresses", &group_funding_addresses) + .await + .expect("Failed to store group addresses"); + } + + redis_pool +} + +pub async fn refresh_cache(pool: &PgPool, redis_pool: &RedisPool) { + let crowd_funding_addresses: Vec = + sqlx::query_scalar("SELECT pool_address FROM crowd_funding") + .fetch_all(pool) + .await + .expect("Failed to get crowd funding addresses"); + + let group_funding_addresses: Vec = + sqlx::query_scalar("SELECT group_address FROM groups") + .fetch_all(pool) + .await + .expect("Failed to get group addresses"); + + let mut conn = redis_pool + .get() + .await + .expect("Failed to get Redis connection"); + + let _: () = conn + .sadd("crowd_funding_addresses", &crowd_funding_addresses) + .await + .expect("Failed to store crowd funding addresses"); + + let _: () = conn + .sadd("group_funding_addresses", &group_funding_addresses) + .await + .expect("Failed to store group addresses"); +} diff --git a/server/src/libs/router.rs b/server/src/libs/router.rs index 8eb72db..c2a0364 100644 --- a/server/src/libs/router.rs +++ b/server/src/libs/router.rs @@ -1,11 +1,15 @@ +use std::time::Duration; + use axum::{ Router, http::{ HeaderName, Method, StatusCode, header::{AUTHORIZATION, CONTENT_TYPE}, }, + routing::get, }; -use tower_http::cors::CorsLayer; +use axum_prometheus::PrometheusMetricLayer; +use tower_http::{cors::CorsLayer, timeout::TimeoutLayer, trace::TraceLayer}; use utoipa::OpenApi; use utoipa_axum::router::OpenApiRouter; use utoipa_axum::routes; @@ -29,14 +33,22 @@ pub fn router(state: AppState) -> Router { HeaderName::from_static("paymesh-api-key"), ]); + let (prometheus_layer, metric_handle) = PrometheusMetricLayer::pair(); + let (router, api) = OpenApiRouter::with_openapi(ApiDoc::openapi()) .routes(routes!(health::health_check)) .nest("/users", routes::user::router()) .nest("/groups", routes::groups::router()) .nest("/admin", routes::admin::router()) .nest("/crowdfunding", routes::crowd_funding::router()) + .route("/metrics", get(|| async move { metric_handle.render() })) .with_state(state) + .layer(prometheus_layer) .layer(cors) + .layer(( + TraceLayer::new_for_http(), + TimeoutLayer::new(Duration::from_secs(10)), + )) .fallback(|| async { (StatusCode::UNAUTHORIZED, "UNAUTHORIZED ORIGIN") }) .split_for_parts(); diff --git a/server/src/main.rs b/server/src/main.rs index b0b9dde..d890b67 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,8 +1,8 @@ use server::{ AppState, - libs::{cache::init_cache, config::Env, db::Db, logging::init_tracing, router::router}, + libs::{config::Env, db::Db, logging::init_tracing, redis, router::router}, }; -use tokio::net::TcpListener; +use tokio::{net::TcpListener, signal}; #[tokio::main] async fn main() { @@ -16,27 +16,27 @@ async fn main() { tracing::debug!("Initializing db"); let db = Db::new().await.expect("Failed to initialize DB"); - tracing::debug!("Initializing cache"); - let cache = init_cache(&db.pool.clone()).await; + tracing::debug!("Initializing redis"); + let redis_pool = redis::init_redis(&db.pool.clone()).await; let config = AppState { db: db.pool.clone(), - cache, + redis: redis_pool, env, }; { - let cache = config.cache.clone(); + let redis_pool = config.redis.clone(); let db = config.db.clone(); tokio::spawn(async move { let mut interval = tokio::time::interval(std::time::Duration::from_secs(300)); loop { - let new_cache = init_cache(&db).await; - *cache.write().await = new_cache.read().await.clone(); + tracing::info!("Refreshing redis cache"); + redis::refresh_cache(&db, &redis_pool).await; interval.tick().await; } }); - tracing::info!("Cache Refreshed"); + tracing::info!("Started cache refresher task"); } tracing::debug!("Running Migrations"); @@ -53,6 +53,31 @@ async fn main() { let router = router(config); axum::serve(listener, router) + .with_graceful_shutdown(shutdown_signal()) .await .expect("Failed to start server") } + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} diff --git a/server/src/routes/crowd_funding/crowd_funding_routes.rs b/server/src/routes/crowd_funding/crowd_funding_routes.rs index 6d9c5dc..000ef3c 100644 --- a/server/src/routes/crowd_funding/crowd_funding_routes.rs +++ b/server/src/routes/crowd_funding/crowd_funding_routes.rs @@ -141,6 +141,8 @@ pub async fn create_crowd_funding( ApiError::Internal("Failed to commit transaction") })?; + state.add_crowd_funding_address(&pool_address).await?; + tracing::info!("Crowd funding created: {}", pool_address); Ok(StatusCode::CREATED) @@ -473,10 +475,7 @@ pub async fn resolve_crowd_funding( pub async fn get_all_crowd_funding_addresses( State(state): State, ) -> Result { - let crowd_funding_addresses = sqlx::query_scalar!(r#"SELECT pool_address FROM crowd_funding"#,) - .fetch_all(&state.db) - .await - .map_err(|e| map_sqlx_error(&e))?; + let crowd_funding_addresses = state.get_all_crowd_funding_addresses().await?; Ok(Json(crowd_funding_addresses)) } diff --git a/server/src/routes/groups/group.rs b/server/src/routes/groups/group.rs index 5f56e2a..b2b8898 100644 --- a/server/src/routes/groups/group.rs +++ b/server/src/routes/groups/group.rs @@ -65,11 +65,7 @@ pub async fn create_group( map_sqlx_error(&e) })?; - { - let mut cache = state.cache.write().await; - cache.insert(group_address.to_string()); - } - + state.add_group_address(group_address).await?; for group_member in payload.members { let member_percentage: BigDecimal = group_member.percentage.into(); sqlx::query!( @@ -314,8 +310,6 @@ pub async fn get_groups(State(state): State) -> Result, ) -> Result { - let cache = RwLockReadGuard::map(state.cache.read().await, |f| f).clone(); - let vec_cache: Vec = cache.into_iter().collect(); - + let vec_cache = state.get_all_group_addresses().await?; Ok((StatusCode::OK, Json(vec_cache))) } diff --git a/server/src/routes/groups/pay_group.rs b/server/src/routes/groups/pay_group.rs index 69a38c9..2349ce2 100644 --- a/server/src/routes/groups/pay_group.rs +++ b/server/src/routes/groups/pay_group.rs @@ -49,7 +49,7 @@ pub async fn pay_group( ApiError::BadRequest("Invalid token amount") })?; - if !state.cache.read().await.contains(&group_address) { + if !state.has_group_address(&group_address).await? { return Err(ApiError::NotFound("Group doesnt exist")); }