diff --git a/.github/workflows/rust-tests-workflow.yml b/.github/workflows/rust-tests-workflow.yml index 3fd70a2..2f52e41 100644 --- a/.github/workflows/rust-tests-workflow.yml +++ b/.github/workflows/rust-tests-workflow.yml @@ -26,7 +26,7 @@ jobs: - name: Start Docker Compose services run: | cd testing - nohup /bin/sh -c 'docker compose up -d --build --wait; touch ./compose-ready' > compose.log 2>&1 & + nohup /bin/sh -c 'docker compose --env-file ./testing.env up -d --build --wait; touch ./compose-ready' > compose.log 2>&1 & cd .. - name: Rust cache @@ -57,14 +57,14 @@ jobs: exit 1 fi - . ./.env && docker network connect $SERVICES_NETWORK_NAME $(hostname) + . ./testing.env && docker network connect $SERVICES_NETWORK_NAME $(hostname) cd .. - name: Run tests run: | - cp testing/.env . + cp testing/testing.env ./testing.env cargo test - name: Stop Docker Compose services if: always() - run: docker compose -f testing/docker-compose.yml down -v + run: docker compose --env-file testing/testing.env -f testing/docker-compose.yml down -v diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5950c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/.env +**/.idea/ +**/target/ +**/.DS_Store +testing.env diff --git a/Cargo.lock b/Cargo.lock index 560a59c..5830da6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2073,9 +2073,9 @@ dependencies = [ [[package]] name = "lightning-invoice" -version = "0.33.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11209f386879b97198b2bfc9e9c1e5d42870825c6bd4376f17f95357244d6600" +checksum = "b85e5e14bcdb30d746e9785b04f27938292e8944f78f26517e01e91691f6b3f2" dependencies = [ "bech32", "bitcoin", @@ -2085,9 +2085,9 @@ dependencies = [ [[package]] name = "lightning-types" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cd84d4e71472035903e43caded8ecc123066ce466329ccd5ae537a8d5488c7" +checksum = "5681708d3075bdff3a1b4daa400590e2703e7871bdc14e94ee7334fb6314ae40" dependencies = [ "bitcoin", ] @@ -4156,11 +4156,52 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "switchgear" -version = "0.1.22" +version = "0.1.23" + +[[package]] +name = "switchgear-components" +version = "0.1.23" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "base64", + "bitcoin_hashes", + "chrono", + "client-ip", + "hex", + "jsonwebtoken", + "lightning-invoice", + "log", + "p256", + "pkcs8", + "prost", + "rand 0.8.5", + "reqwest", + "rustls", + "sea-orm", + "secp256k1 0.31.1", + "serde", + "serde_json", + "sha2", + "sqlx", + "switchgear-migration", + "switchgear-service-api", + "switchgear-testing", + "tempfile", + "thiserror 2.0.17", + "tokio", + "tonic", + "tonic-prost", + "tonic-prost-build", + "tower", + "url", + "uuid", +] [[package]] name = "switchgear-migration" -version = "0.1.22" +version = "0.1.23" dependencies = [ "sea-orm-migration", "tokio", @@ -4168,7 +4209,7 @@ dependencies = [ [[package]] name = "switchgear-pingora" -version = "0.1.22" +version = "0.1.23" dependencies = [ "arc-swap", "async-trait", @@ -4181,16 +4222,15 @@ dependencies = [ "pingora-load-balancing", "rand 0.8.5", "secp256k1 0.31.1", - "switchgear-service", + "switchgear-service-api", "thiserror 2.0.17", "tokio", - "url", "uuid", ] [[package]] name = "switchgear-server" -version = "0.1.22" +version = "0.1.23" dependencies = [ "anyhow", "async-trait", @@ -4222,8 +4262,10 @@ dependencies = [ "signal-hook-tokio", "simplelog", "strfmt", + "switchgear-components", "switchgear-pingora", "switchgear-service", + "switchgear-service-api", "switchgear-testing", "sysinfo", "tempfile", @@ -4235,62 +4277,61 @@ dependencies = [ [[package]] name = "switchgear-service" -version = "0.1.22" +version = "0.1.23" dependencies = [ - "anyhow", "async-trait", "axum", "axum-extra", "axum-test", - "backoff", - "base64", "bech32", - "bitcoin_hashes", "chrono", - "client-ip", - "email_address", - "hex", "http", - "hyper-rustls", - "hyper-timeout", - "hyper-util", "image", "jsonwebtoken", - "lightning-invoice", "log", "p256", "pkcs8", "png", - "prost", "qrcode", "rand 0.8.5", - "reqwest", "rqrr", - "rustls", - "rustls-pemfile", - "sea-orm", - "secp256k1 0.29.1", "secp256k1 0.31.1", "serde", "serde_json", - "sha2", "sqlx", - "switchgear-migration", - "switchgear-testing", - "tempfile", + "switchgear-components", + "switchgear-service-api", "thiserror 2.0.17", "tokio", - "tonic", - "tonic-prost", - "tonic-prost-build", "tower", "url", "uuid", ] +[[package]] +name = "switchgear-service-api" +version = "0.1.23" +dependencies = [ + "async-trait", + "axum", + "base64", + "bitcoin_hashes", + "chrono", + "email_address", + "hex", + "lightning-invoice", + "secp256k1 0.29.1", + "secp256k1 0.31.1", + "serde", + "serde_json", + "tokio", + "url", + "uuid", +] + [[package]] name = "switchgear-testing" -version = "0.1.22" +version = "0.1.23" dependencies = [ "anyhow", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index ff02744..d9065fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,13 @@ [workspace] members = [ + "components", + "migration", + "pingora", "server", "service", - "pingora", - "testing", - "migration", - "switchgear" + "service-api", + "switchgear", + "testing" ] resolver = "2" @@ -14,15 +16,16 @@ resolver = "2" version = "0.1.23" [workspace.dependencies] +switchgear-components = { version = "0.1.23", path = "components" } +switchgear-migration = { version = "0.1.23", path = "migration" } switchgear-pingora = { version = "0.1.23", path = "pingora" } switchgear-service = { version = "0.1.23", path = "service" } -switchgear-migration = { version = "0.1.23", path = "migration" } +switchgear-service-api = { version = "0.1.23", path = "service-api" } switchgear-testing = { version = "0.1.23", path = "testing" } - [profile.release] -lto = true codegen-units = 1 +lto = true panic = 'abort' strip = true diff --git a/README.md b/README.md index e17e076..6c418c6 100644 --- a/README.md +++ b/README.md @@ -1235,34 +1235,3 @@ Metadata can include images in PNG or JPEG format, base64 encoded: Metadata identifiers can be: - Email: `{"email": "contact@example.com"}` - Text: `{"text": "contact@example.com"}` - -## SDK - -### Service - -The [switchgear-service](./service) crate defines all services and their trait dependencies. See the `api` module for trait definitions and data models: [service/src/api](./service/src/api) - -![image](./doc/service_traits_component_diagram-Service_Layer_Trait_Relationships.png) - - -### Pingora - -`PingoraLnBalancer` is the default `LnBalancer` implementation. The [switchgear-pingora](./pingora) crate holds the complete implementation, plus trait definitions it uses for itself. - -![image](./doc/pingora_traits_component_diagram-PingoraLnBalancer_Trait_Dependencies.png) - - -### Components - -The `components` module in [switchgear-service](./service/src/components) is a collection self-defined traits and implementations useful for implementing a complete `LnBalancer`. The module also holds different implementations of `DiscoveryBackendStore`, `OfferStore` and `OfferMetadataStore`. - -#### Service Components - -![image](./doc/service_components_traits_diagram-Service_Components_Trait_Dependencies.png) - -#### Data Store Implementations - -![image](./doc/service_discovery_traits_diagram-Discovery_Components_Trait_Dependencies.png) - -![image](./doc/service_offer_traits_diagram-Offer_Components_Trait_Dependencies.png) - diff --git a/components/Cargo.toml b/components/Cargo.toml new file mode 100644 index 0000000..834932e --- /dev/null +++ b/components/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "switchgear-components" +version.workspace = true +edition = "2021" +authors = ["Bitshock "] +description = "Component APIs and implementations for Switchgear LNURL load balancer" +documentation = "https://github.com/bitshock-src/switchgear" +homepage = "https://bitshock.com" +repository = "https://github.com/bitshock-src/switchgear" +license = "Apache-2.0" +keywords = ["bitcoin", "lightning", "lnurl", "api", "service"] +categories = ["network-programming", "web-programming", "cryptography::cryptocurrencies"] +publish = true + +[dependencies] +async-trait = "0.1" +axum = { version = "0.8", features = ["macros"] } +base64 = "0.22" +chrono = { version = "0.4", features = ["serde"] } +client-ip = { version = "0.1", features = ["forwarded-header"] } +hex = "0.4" +jsonwebtoken = { version = "10", features = ["aws_lc_rs"] } +log = "0.4" +prost = { version = "0.14" } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots-no-provider"] } +rustls = { version = "0.23", default-features = false } +sea-orm = { version = "1", default-features = false, features = ["with-chrono", "with-uuid", "with-json"] } +secp256k1 = { version = "0.31", features = ["recovery", "serde"] } +serde = "1" +serde_json = "1" +sha2 = "0.10" +sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "tls-rustls-aws-lc-rs", "sqlite", "postgres", "mysql"] } +switchgear-migration.workspace = true +switchgear-service-api.workspace = true +tempfile = "3" +thiserror = "2" +tokio = { version = "1", features = ["full"] } +tonic = { version = "0.14", default-features = false, features = ["codegen", "transport", "tls-native-roots"] } +tonic-prost = "0.14" +tower = { version = "0.5", features = ["balance"] } +url = { version = "2", features = ["serde"] } +uuid = { version = "1", features = ["v4", "serde"] } + +[build-dependencies] +tonic-prost-build = { version = "0.14" } + +[dev-dependencies] +anyhow = "1" +bitcoin_hashes = "0.14" +lightning-invoice = { version = "0.34", features = ["serde", "std"] } +p256 = { version = "0.13", features = ["ecdsa"] } +pkcs8 = { version = "0.10", features = ["pem"] } +rand = "0.8" +rustls = { version = "0.23", features = ["aws-lc-rs"] } +switchgear-testing.workspace = true diff --git a/service/build.rs b/components/build.rs similarity index 100% rename from service/build.rs rename to components/build.rs diff --git a/service/proto/cln/node.proto b/components/proto/cln/node.proto similarity index 100% rename from service/proto/cln/node.proto rename to components/proto/cln/node.proto diff --git a/service/proto/cln/primitives.proto b/components/proto/cln/primitives.proto similarity index 100% rename from service/proto/cln/primitives.proto rename to components/proto/cln/primitives.proto diff --git a/service/proto/lnd/lightning.proto b/components/proto/lnd/lightning.proto similarity index 100% rename from service/proto/lnd/lightning.proto rename to components/proto/lnd/lightning.proto diff --git a/service/src/components/axum/middleware/logger.rs b/components/src/axum/middleware/logger.rs similarity index 100% rename from service/src/components/axum/middleware/logger.rs rename to components/src/axum/middleware/logger.rs diff --git a/service/src/components/axum/middleware/mod.rs b/components/src/axum/middleware/mod.rs similarity index 100% rename from service/src/components/axum/middleware/mod.rs rename to components/src/axum/middleware/mod.rs diff --git a/service/src/components/axum/mod.rs b/components/src/axum/mod.rs similarity index 100% rename from service/src/components/axum/mod.rs rename to components/src/axum/mod.rs diff --git a/service/src/components/discovery/db.rs b/components/src/discovery/db.rs similarity index 92% rename from service/src/components/discovery/db.rs rename to components/src/discovery/db.rs index 1b3b380..5f5ee78 100644 --- a/service/src/components/discovery/db.rs +++ b/components/src/discovery/db.rs @@ -1,9 +1,4 @@ -use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatch, - DiscoveryBackendSparse, DiscoveryBackendStore, DiscoveryBackends, -}; -use crate::api::service::ServiceErrorSource; -use crate::components::discovery::error::DiscoveryBackendStoreError; +use crate::discovery::error::DiscoveryBackendStoreError; use async_trait::async_trait; use chrono::Utc; use sea_orm::entity::prelude::*; @@ -16,6 +11,11 @@ use secp256k1::PublicKey; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use switchgear_migration::{MigratorTrait, DISCOVERY_BACKEND_GET_ALL_ETAG_ID}; +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendSparse, DiscoveryBackendStore, + DiscoveryBackends, +}; +use switchgear_service_api::service::ServiceErrorSource; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] pub struct DiscoveryBackendPartitions(BTreeSet); @@ -30,7 +30,7 @@ pub struct Model { pub name: Option, pub weight: i32, pub enabled: bool, - pub implementation: Json, + pub implementation: Vec, pub created_at: DateTimeWithTimeZone, pub updated_at: DateTimeWithTimeZone, } @@ -111,15 +111,6 @@ impl DbDiscoveryBackendStore { } fn model_to_domain(model: Model) -> Result { - let implementation: DiscoveryBackendImplementation = - serde_json::from_value(model.implementation).map_err(|e| { - DiscoveryBackendStoreError::json_serialization_error( - ServiceErrorSource::Internal, - "deserializing implementation from database", - e, - ) - })?; - Ok(DiscoveryBackend { public_key: PublicKey::from_slice(&model.id).map_err(|e| { DiscoveryBackendStoreError::internal_error( @@ -133,7 +124,7 @@ impl DbDiscoveryBackendStore { partitions: model.partitions.0, weight: model.weight as usize, enabled: model.enabled, - implementation, + implementation: model.implementation, }, }) } @@ -206,15 +197,6 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { } async fn post(&self, backend: DiscoveryBackend) -> Result, Self::Error> { - let implementation_json = - serde_json::to_value(&backend.backend.implementation).map_err(|e| { - DiscoveryBackendStoreError::json_serialization_error( - ServiceErrorSource::Internal, - "serializing implementation for database", - e, - ) - })?; - let now = Utc::now(); let active_model = ActiveModel { partitions: Set(DiscoveryBackendPartitions(backend.backend.partitions)), @@ -222,7 +204,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { name: Set(backend.backend.name), weight: Set(backend.backend.weight as i32), enabled: Set(backend.backend.enabled), - implementation: Set(implementation_json), + implementation: Set(backend.backend.implementation), created_at: Set(now.into()), updated_at: Set(now.into()), }; @@ -285,15 +267,6 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { } async fn put(&self, backend: DiscoveryBackend) -> Result { - let implementation_json = - serde_json::to_value(&backend.backend.implementation).map_err(|e| { - DiscoveryBackendStoreError::json_serialization_error( - ServiceErrorSource::Internal, - "serializing implementation for database", - e, - ) - })?; - let now = Utc::now(); let future_timestamp = now + chrono::Duration::seconds(1); @@ -304,7 +277,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { name: Set(backend.backend.name), weight: Set(backend.backend.weight as i32), enabled: Set(backend.backend.enabled), - implementation: Set(implementation_json), + implementation: Set(backend.backend.implementation), created_at: Set(now.into()), updated_at: Set(now.into()), }; diff --git a/service/src/components/discovery/error.rs b/components/src/discovery/error.rs similarity index 98% rename from service/src/components/discovery/error.rs rename to components/src/discovery/error.rs index 3f32b76..ba29a85 100644 --- a/service/src/components/discovery/error.rs +++ b/components/src/discovery/error.rs @@ -1,6 +1,6 @@ -use crate::api::service::{HasServiceErrorSource, ServiceErrorSource}; use std::borrow::Cow; use std::fmt::{Display, Formatter}; +use switchgear_service_api::service::{HasServiceErrorSource, ServiceErrorSource}; use thiserror::Error; #[derive(Error, Debug)] diff --git a/service/src/components/discovery/http.rs b/components/src/discovery/http.rs similarity index 98% rename from service/src/components/discovery/http.rs rename to components/src/discovery/http.rs index bf02124..7ad0902 100644 --- a/service/src/components/discovery/http.rs +++ b/components/src/discovery/http.rs @@ -1,15 +1,15 @@ -use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, - HttpDiscoveryBackendClient, -}; -use crate::api::service::ServiceErrorSource; -use crate::components::discovery::error::DiscoveryBackendStoreError; +use crate::discovery::error::DiscoveryBackendStoreError; use async_trait::async_trait; use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{Certificate, Client, ClientBuilder, IntoUrl, StatusCode}; use rustls::pki_types::CertificateDer; use secp256k1::PublicKey; use std::time::Duration; +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, + HttpDiscoveryBackendClient, +}; +use switchgear_service_api::service::ServiceErrorSource; use url::Url; #[derive(Clone, Debug)] @@ -358,7 +358,7 @@ impl HttpDiscoveryBackendClient for HttpDiscoveryBackendStore { #[cfg(test)] mod tests { - use crate::components::discovery::http::HttpDiscoveryBackendStore; + use crate::discovery::http::HttpDiscoveryBackendStore; use anyhow::anyhow; use rand::Rng; use secp256k1::{PublicKey, Secp256k1, SecretKey}; diff --git a/service/src/components/discovery/memory.rs b/components/src/discovery/memory.rs similarity index 97% rename from service/src/components/discovery/memory.rs rename to components/src/discovery/memory.rs index a4f349b..17811ce 100644 --- a/service/src/components/discovery/memory.rs +++ b/components/src/discovery/memory.rs @@ -1,12 +1,12 @@ -use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, -}; -use crate::components::discovery::error::DiscoveryBackendStoreError; +use crate::discovery::error::DiscoveryBackendStoreError; use async_trait::async_trait; use secp256k1::PublicKey; use std::collections::HashMap; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, +}; use tokio::sync::Mutex; #[derive(Clone, Debug)] diff --git a/service/src/components/discovery/mod.rs b/components/src/discovery/mod.rs similarity index 100% rename from service/src/components/discovery/mod.rs rename to components/src/discovery/mod.rs diff --git a/service/src/components/mod.rs b/components/src/lib.rs similarity index 78% rename from service/src/components/mod.rs rename to components/src/lib.rs index 9f2a8b6..55854ae 100644 --- a/service/src/components/mod.rs +++ b/components/src/lib.rs @@ -1,5 +1,4 @@ pub mod axum; -pub mod backoff; pub mod discovery; pub mod offer; pub mod pool; diff --git a/service/src/components/offer/db.rs b/components/src/offer/db.rs similarity index 97% rename from service/src/components/offer/db.rs rename to components/src/offer/db.rs index b878c4c..204676c 100644 --- a/service/src/components/offer/db.rs +++ b/components/src/offer/db.rs @@ -1,11 +1,7 @@ -use crate::api::offer::{ - Offer, OfferMetadata, OfferMetadataStore, OfferProvider, OfferRecord, OfferStore, -}; -use crate::api::service::ServiceErrorSource; -use crate::components::discovery::db::Column; -use crate::components::offer::db_orm::prelude::*; -use crate::components::offer::db_orm::{offer_metadata_table, offer_record_table}; -use crate::components::offer::error::OfferStoreError; +use crate::discovery::db::Column; +use crate::offer::db_orm::prelude::*; +use crate::offer::db_orm::{offer_metadata_table, offer_record_table}; +use crate::offer::error::OfferStoreError; use async_trait::async_trait; use chrono::Utc; use sea_orm::{ @@ -15,6 +11,12 @@ use sea_orm::{ use sha2::{Digest, Sha256}; use switchgear_migration::OnConflict; use switchgear_migration::{Expr, MigratorTrait}; +use switchgear_service_api::lnurl::LnUrlOfferMetadata; +use switchgear_service_api::offer::{ + Offer, OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferProvider, OfferRecord, + OfferRecordSparse, OfferStore, +}; +use switchgear_service_api::service::ServiceErrorSource; use uuid::Uuid; #[derive(Clone, Debug)] @@ -84,7 +86,7 @@ impl OfferStore for DbOfferStore { Some(model) => Ok(Some(OfferRecord { partition: model.partition, id: model.id, - offer: crate::api::offer::OfferRecordSparse { + offer: OfferRecordSparse { max_sendable: model.max_sendable as u64, min_sendable: model.min_sendable as u64, metadata_id: model.metadata_id, @@ -123,7 +125,7 @@ impl OfferStore for DbOfferStore { offers.push(OfferRecord { partition: model.partition, id: model.id, - offer: crate::api::offer::OfferRecordSparse { + offer: OfferRecordSparse { max_sendable: model.max_sendable as u64, min_sendable: model.min_sendable as u64, metadata_id: model.metadata_id, @@ -581,7 +583,6 @@ impl OfferProvider for DbOfferStore { _ => return Ok(None), }; - use crate::api::offer::OfferMetadataSparse; let metadata_sparse: OfferMetadataSparse = serde_json::from_value(metadata_model.metadata) .map_err(|e| { OfferStoreError::serialization_error( @@ -591,7 +592,6 @@ impl OfferProvider for DbOfferStore { ) })?; - use crate::api::lnurl::LnUrlOfferMetadata; let lnurl_metadata = LnUrlOfferMetadata(metadata_sparse); let metadata_json_string = serde_json::to_string(&lnurl_metadata).map_err(|e| { OfferStoreError::serialization_error( diff --git a/service/src/components/offer/db_orm/mod.rs b/components/src/offer/db_orm/mod.rs similarity index 100% rename from service/src/components/offer/db_orm/mod.rs rename to components/src/offer/db_orm/mod.rs diff --git a/service/src/components/offer/db_orm/offer_metadata_table.rs b/components/src/offer/db_orm/offer_metadata_table.rs similarity index 100% rename from service/src/components/offer/db_orm/offer_metadata_table.rs rename to components/src/offer/db_orm/offer_metadata_table.rs diff --git a/service/src/components/offer/db_orm/offer_record_table.rs b/components/src/offer/db_orm/offer_record_table.rs similarity index 100% rename from service/src/components/offer/db_orm/offer_record_table.rs rename to components/src/offer/db_orm/offer_record_table.rs diff --git a/service/src/components/offer/db_orm/prelude.rs b/components/src/offer/db_orm/prelude.rs similarity index 100% rename from service/src/components/offer/db_orm/prelude.rs rename to components/src/offer/db_orm/prelude.rs diff --git a/service/src/components/offer/error.rs b/components/src/offer/error.rs similarity index 98% rename from service/src/components/offer/error.rs rename to components/src/offer/error.rs index e3a02cb..77a935a 100644 --- a/service/src/components/offer/error.rs +++ b/components/src/offer/error.rs @@ -1,6 +1,6 @@ -use crate::api::service::{HasServiceErrorSource, ServiceErrorSource}; use std::borrow::Cow; use std::fmt::{Display, Formatter}; +use switchgear_service_api::service::{HasServiceErrorSource, ServiceErrorSource}; use thiserror::Error; #[derive(Error, Debug)] diff --git a/service/src/components/offer/http.rs b/components/src/offer/http.rs similarity index 98% rename from service/src/components/offer/http.rs rename to components/src/offer/http.rs index a16cd3a..f213234 100644 --- a/service/src/components/offer/http.rs +++ b/components/src/offer/http.rs @@ -1,16 +1,16 @@ -use crate::api::lnurl::LnUrlOfferMetadata; -use crate::api::offer::{ - HttpOfferClient, Offer, OfferMetadata, OfferMetadataStore, OfferProvider, OfferRecord, - OfferStore, -}; -use crate::api::service::ServiceErrorSource; -use crate::components::offer::error::OfferStoreError; +use crate::offer::error::OfferStoreError; use async_trait::async_trait; use axum::http::{HeaderMap, HeaderValue}; use reqwest::{Certificate, Client, ClientBuilder, IntoUrl, StatusCode}; use rustls::pki_types::CertificateDer; use sha2::Digest; use std::time::Duration; +use switchgear_service_api::lnurl::LnUrlOfferMetadata; +use switchgear_service_api::offer::{ + HttpOfferClient, Offer, OfferMetadata, OfferMetadataStore, OfferProvider, OfferRecord, + OfferStore, +}; +use switchgear_service_api::service::ServiceErrorSource; use url::Url; use uuid::Uuid; @@ -505,7 +505,7 @@ impl HttpOfferClient for HttpOfferStore { #[cfg(test)] mod tests { - use crate::components::offer::http::HttpOfferStore; + use crate::offer::http::HttpOfferStore; use url::Url; use uuid::Uuid; diff --git a/service/src/components/offer/memory.rs b/components/src/offer/memory.rs similarity index 97% rename from service/src/components/offer/memory.rs rename to components/src/offer/memory.rs index 3473ec4..c245575 100644 --- a/service/src/components/offer/memory.rs +++ b/components/src/offer/memory.rs @@ -1,13 +1,13 @@ -use crate::api::lnurl::LnUrlOfferMetadata; -use crate::api::offer::{ - Offer, OfferMetadata, OfferMetadataStore, OfferProvider, OfferRecord, OfferStore, -}; -use crate::api::service::ServiceErrorSource; -use crate::components::offer::error::OfferStoreError; +use crate::offer::error::OfferStoreError; use async_trait::async_trait; use sha2::Digest; use std::collections::HashMap; use std::sync::Arc; +use switchgear_service_api::lnurl::LnUrlOfferMetadata; +use switchgear_service_api::offer::{ + Offer, OfferMetadata, OfferMetadataStore, OfferProvider, OfferRecord, OfferStore, +}; +use switchgear_service_api::service::ServiceErrorSource; use tokio::sync::Mutex; use uuid::Uuid; diff --git a/service/src/components/offer/mod.rs b/components/src/offer/mod.rs similarity index 100% rename from service/src/components/offer/mod.rs rename to components/src/offer/mod.rs diff --git a/service/src/components/pool/default_pool.rs b/components/src/pool/client_pool.rs similarity index 60% rename from service/src/components/pool/default_pool.rs rename to components/src/pool/client_pool.rs index 6685472..8ab26ca 100644 --- a/service/src/components/pool/default_pool.rs +++ b/components/src/pool/client_pool.rs @@ -1,24 +1,23 @@ -use crate::api::discovery::{DiscoveryBackend, DiscoveryBackendImplementation}; -use crate::api::offer::Offer; -use crate::api::service::ServiceErrorSource; -use crate::components::pool::cln::grpc::client::TonicClnGrpcClient; -use crate::components::pool::error::LnPoolError; -use crate::components::pool::lnd::grpc::client::TonicLndGrpcClient; -use crate::components::pool::{ - Bolt11InvoiceDescription, LnClientPool, LnMetrics, LnMetricsCache, LnRpcClient, +use crate::pool::cln::grpc::client::TonicClnGrpcClient; +use crate::pool::error::LnPoolError; +use crate::pool::lnd::grpc::client::TonicLndGrpcClient; +use crate::pool::{ + Bolt11InvoiceDescription, DiscoveryBackendImplementation, LnMetrics, LnRpcClient, }; -use async_trait::async_trait; use std::collections::HashMap; use std::fmt::Debug; use std::sync::{Arc, Mutex}; use std::time::Duration; +use switchgear_service_api::discovery::DiscoveryBackend; +use switchgear_service_api::offer::Offer; +use switchgear_service_api::service::ServiceErrorSource; use tonic::transport::CertificateDer; type LnClientMap = HashMap + Send + Sync + 'static>>>; #[derive(Clone)] -pub struct DefaultLnClientPool +pub struct LnClientPool where K: Clone + std::hash::Hash + Eq, { @@ -28,14 +27,11 @@ where trusted_roots: Vec>, } -impl DefaultLnClientPool +impl LnClientPool where K: Clone + std::hash::Hash + Eq + Debug, { - pub fn new( - timeout: Duration, - trusted_roots: Vec>, - ) -> DefaultLnClientPool { + pub fn new(timeout: Duration, trusted_roots: Vec>) -> LnClientPool { Self { timeout, pool: Default::default(), @@ -64,23 +60,14 @@ where })?; Ok(client.clone()) } -} - -#[async_trait] -impl LnClientPool for DefaultLnClientPool -where - K: Clone + std::hash::Hash + Eq + Send + Sync + Debug + 'static, -{ - type Error = LnPoolError; - type Key = K; - async fn get_invoice( + pub async fn get_invoice( &self, offer: &Offer, - key: &Self::Key, + key: &K, amount_msat: Option, expiry_secs: Option, - ) -> Result { + ) -> Result { let client = self.get_client(key).await?; let capabilities = client.get_features(); @@ -94,12 +81,12 @@ where Bolt11InvoiceDescription::DirectIntoHash(offer.metadata_json_string.as_str()) }; - Ok(client + client .get_invoice(amount_msat, description, expiry_secs) - .await?) + .await } - async fn get_metrics(&self, key: &Self::Key) -> Result { + pub async fn get_metrics(&self, key: &K) -> Result { let client = self.get_client(key).await?; let metrics = client.get_metrics().await?; @@ -112,20 +99,18 @@ where Ok(metrics) } - fn connect(&self, key: Self::Key, backend: &DiscoveryBackend) -> Result<(), Self::Error> { - let client: Box + Send + Sync> = - match &backend.backend.implementation { - DiscoveryBackendImplementation::ClnGrpc(c) => Box::new(TonicClnGrpcClient::create( - self.timeout, - c.clone(), - &self.trusted_roots, - )?), - DiscoveryBackendImplementation::LndGrpc(c) => Box::new(TonicLndGrpcClient::create( - self.timeout, - c.clone(), - &self.trusted_roots, - )?), - }; + pub fn connect(&self, key: K, backend: &DiscoveryBackend) -> Result<(), LnPoolError> { + let implementation: DiscoveryBackendImplementation = + serde_json::from_slice(backend.backend.implementation.as_slice()) + .map_err(|e| LnPoolError::from_json_error(e, "parsing backend implementation"))?; + let client: Box + Send + Sync> = match implementation { + DiscoveryBackendImplementation::ClnGrpc(implementation) => Box::new( + TonicClnGrpcClient::create(self.timeout, implementation, &self.trusted_roots)?, + ), + DiscoveryBackendImplementation::LndGrpc(implementation) => Box::new( + TonicLndGrpcClient::create(self.timeout, implementation, &self.trusted_roots)?, + ), + }; let mut pool = self.pool.lock().map_err(|e| { LnPoolError::from_memory_error(e.to_string(), format!("connecting ln client {key:?}")) @@ -134,11 +119,8 @@ where Ok(()) } -} -impl LnMetricsCache for DefaultLnClientPool { - type Key = K; - fn get_cached_metrics(&self, key: &K) -> Option { + pub fn get_cached_metrics(&self, key: &K) -> Option { match self.metrics_cache.lock() { Ok(cache) => cache.get(key).cloned(), Err(_) => None, diff --git a/service/src/components/pool/cln/grpc/client.rs b/components/src/pool/cln/grpc/client.rs similarity index 97% rename from service/src/components/pool/cln/grpc/client.rs rename to components/src/pool/cln/grpc/client.rs index 3c6613e..077493a 100644 --- a/service/src/components/pool/cln/grpc/client.rs +++ b/components/src/pool/cln/grpc/client.rs @@ -1,9 +1,6 @@ -use crate::api::service::ServiceErrorSource; -use crate::components::pool::cln::grpc::config::{ - ClnGrpcClientAuth, ClnGrpcDiscoveryBackendImplementation, -}; -use crate::components::pool::error::LnPoolError; -use crate::components::pool::{Bolt11InvoiceDescription, LnFeatures, LnMetrics, LnRpcClient}; +use crate::pool::cln::grpc::config::{ClnGrpcClientAuth, ClnGrpcDiscoveryBackendImplementation}; +use crate::pool::error::LnPoolError; +use crate::pool::{Bolt11InvoiceDescription, LnFeatures, LnMetrics, LnRpcClient}; use async_trait::async_trait; use hex::ToHex; use rustls::pki_types::CertificateDer; @@ -11,6 +8,7 @@ use sha2::Digest; use std::fs; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use switchgear_service_api::service::ServiceErrorSource; use tokio::sync::Mutex; use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity}; diff --git a/service/src/components/pool/cln/grpc/config.rs b/components/src/pool/cln/grpc/config.rs similarity index 100% rename from service/src/components/pool/cln/grpc/config.rs rename to components/src/pool/cln/grpc/config.rs diff --git a/service/src/components/pool/cln/grpc/mod.rs b/components/src/pool/cln/grpc/mod.rs similarity index 100% rename from service/src/components/pool/cln/grpc/mod.rs rename to components/src/pool/cln/grpc/mod.rs diff --git a/service/src/components/pool/cln/mod.rs b/components/src/pool/cln/mod.rs similarity index 100% rename from service/src/components/pool/cln/mod.rs rename to components/src/pool/cln/mod.rs diff --git a/service/src/components/pool/error.rs b/components/src/pool/error.rs similarity index 88% rename from service/src/components/pool/error.rs rename to components/src/pool/error.rs index fe59870..6414531 100644 --- a/service/src/components/pool/error.rs +++ b/components/src/pool/error.rs @@ -1,6 +1,6 @@ -use crate::api::service::{HasServiceErrorSource, ServiceErrorSource}; use std::borrow::Cow; use std::fmt::{Display, Formatter}; +use switchgear_service_api::service::{HasServiceErrorSource, ServiceErrorSource}; use thiserror::Error; use tonic::{transport, Code, Status}; @@ -16,6 +16,8 @@ pub enum LnPoolErrorSourceKind { InvalidCredentials(String), #[error("memory error: {0}")] MemoryError(String), + #[error("json error: {0}")] + JsonError(serde_json::Error), } #[derive(Error, Debug)] @@ -99,6 +101,17 @@ impl LnPoolError { ) } + pub fn from_json_error>>( + source: serde_json::Error, + context: C, + ) -> Self { + Self::new( + LnPoolErrorSourceKind::JsonError(source), + ServiceErrorSource::Internal, + context, + ) + } + pub fn context(&self) -> &str { self.context.as_ref() } diff --git a/service/src/components/pool/lnd/grpc/client.rs b/components/src/pool/lnd/grpc/client.rs similarity index 96% rename from service/src/components/pool/lnd/grpc/client.rs rename to components/src/pool/lnd/grpc/client.rs index 062c84d..6ca78b1 100644 --- a/service/src/components/pool/lnd/grpc/client.rs +++ b/components/src/pool/lnd/grpc/client.rs @@ -1,15 +1,13 @@ -use crate::api::service::ServiceErrorSource; -use crate::components::pool::error::LnPoolError; -use crate::components::pool::lnd::grpc::config::{ - LndGrpcClientAuth, LndGrpcDiscoveryBackendImplementation, -}; -use crate::components::pool::{Bolt11InvoiceDescription, LnFeatures, LnMetrics, LnRpcClient}; +use crate::pool::error::LnPoolError; +use crate::pool::lnd::grpc::config::{LndGrpcClientAuth, LndGrpcDiscoveryBackendImplementation}; +use crate::pool::{Bolt11InvoiceDescription, LnFeatures, LnMetrics, LnRpcClient}; use async_trait::async_trait; use rustls::pki_types::CertificateDer; use sha2::Digest; use std::fs; use std::sync::Arc; use std::time::Duration; +use switchgear_service_api::service::ServiceErrorSource; use tokio::sync::Mutex; use tonic::service::Interceptor; use tonic::transport::{Certificate, Channel, ClientTlsConfig}; diff --git a/service/src/components/pool/lnd/grpc/config.rs b/components/src/pool/lnd/grpc/config.rs similarity index 100% rename from service/src/components/pool/lnd/grpc/config.rs rename to components/src/pool/lnd/grpc/config.rs diff --git a/service/src/components/pool/lnd/grpc/mod.rs b/components/src/pool/lnd/grpc/mod.rs similarity index 100% rename from service/src/components/pool/lnd/grpc/mod.rs rename to components/src/pool/lnd/grpc/mod.rs diff --git a/service/src/components/pool/lnd/mod.rs b/components/src/pool/lnd/mod.rs similarity index 100% rename from service/src/components/pool/lnd/mod.rs rename to components/src/pool/lnd/mod.rs diff --git a/service/src/components/pool/mod.rs b/components/src/pool/mod.rs similarity index 54% rename from service/src/components/pool/mod.rs rename to components/src/pool/mod.rs index cd7fc0b..d5b3abe 100644 --- a/service/src/components/pool/mod.rs +++ b/components/src/pool/mod.rs @@ -1,13 +1,23 @@ -use crate::api::discovery::DiscoveryBackend; -use crate::api::offer::Offer; -use async_trait::async_trait; -use std::error::Error; - +mod client_pool; pub mod cln; -pub mod default_pool; pub mod error; pub mod lnd; +use crate::pool::cln::grpc::config::ClnGrpcDiscoveryBackendImplementation; +use crate::pool::lnd::grpc::config::LndGrpcDiscoveryBackendImplementation; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +pub use client_pool::LnClientPool; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type")] +pub enum DiscoveryBackendImplementation { + ClnGrpc(ClnGrpcDiscoveryBackendImplementation), + LndGrpc(LndGrpcDiscoveryBackendImplementation), +} + #[async_trait] pub trait LnRpcClient { type Error: std::error::Error + Send + Sync + 'static; @@ -24,30 +34,6 @@ pub trait LnRpcClient { fn get_features(&self) -> Option<&LnFeatures>; } -#[async_trait] -pub trait LnClientPool { - type Error: Error + Send + Sync + 'static; - type Key: std::hash::Hash + Eq + Send + Sync + 'static; - - async fn get_invoice( - &self, - offer: &Offer, - key: &Self::Key, - amount_msat: Option, - expiry_secs: Option, - ) -> Result; - - async fn get_metrics(&self, key: &Self::Key) -> Result; - - fn connect(&self, key: Self::Key, backend: &DiscoveryBackend) -> Result<(), Self::Error>; -} - -pub trait LnMetricsCache { - type Key: std::hash::Hash + Eq; - - fn get_cached_metrics(&self, key: &Self::Key) -> Option; -} - #[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)] pub enum Bolt11InvoiceDescription<'a> { Direct(&'a str), diff --git a/service/tests/common/discovery.rs b/components/tests/common/discovery.rs similarity index 89% rename from service/tests/common/discovery.rs rename to components/tests/common/discovery.rs index 6d1422b..18d552c 100644 --- a/service/tests/common/discovery.rs +++ b/components/tests/common/discovery.rs @@ -1,11 +1,8 @@ use rand::Rng; use secp256k1::{PublicKey, Secp256k1, SecretKey}; -use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatch, - DiscoveryBackendPatchSparse, DiscoveryBackendSparse, DiscoveryBackendStore, -}; -use switchgear_service::components::pool::lnd::grpc::config::{ - LndGrpcClientAuth, LndGrpcClientAuthPath, LndGrpcDiscoveryBackendImplementation, +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, + DiscoveryBackendStore, }; pub fn gen_backends() -> (DiscoveryBackend, DiscoveryBackend, DiscoveryBackend) { @@ -27,17 +24,7 @@ pub fn gen_backends() -> (DiscoveryBackend, DiscoveryBackend, DiscoveryBackend) partitions: ["default".to_string()].into(), weight: 100, enabled: true, - implementation: DiscoveryBackendImplementation::LndGrpc( - LndGrpcDiscoveryBackendImplementation { - url: "https://localhost:9736".parse().unwrap(), - domain: None, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: None, - macaroon_path: "/path/to/macaroon_path".into(), - }), - amp_invoice: false, - }, - ), + implementation: "{}".as_bytes().to_vec(), }, }; @@ -48,17 +35,7 @@ pub fn gen_backends() -> (DiscoveryBackend, DiscoveryBackend, DiscoveryBackend) partitions: ["default".to_string()].into(), weight: 200, enabled: true, - implementation: DiscoveryBackendImplementation::LndGrpc( - LndGrpcDiscoveryBackendImplementation { - url: "https://localhost:9736".parse().unwrap(), - domain: None, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: None, - macaroon_path: "/path/to/macaroon_path".into(), - }), - amp_invoice: false, - }, - ), + implementation: "{}".as_bytes().to_vec(), }, }; @@ -77,17 +54,7 @@ pub fn gen_backends() -> (DiscoveryBackend, DiscoveryBackend, DiscoveryBackend) partitions: ["default".to_string()].into(), weight: 10, enabled: false, - implementation: DiscoveryBackendImplementation::LndGrpc( - LndGrpcDiscoveryBackendImplementation { - url: "https://localhost:9736".parse().unwrap(), - domain: None, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: None, - macaroon_path: "/path/to/macaroon_path".into(), - }), - amp_invoice: false, - }, - ), + implementation: "{}".as_bytes().to_vec(), }, }; diff --git a/components/tests/common/mock_service.rs b/components/tests/common/mock_service.rs new file mode 100644 index 0000000..fffe847 --- /dev/null +++ b/components/tests/common/mock_service.rs @@ -0,0 +1,487 @@ +use anyhow::bail; +use axum::routing::{delete, get, patch, post, put}; +use axum::{ + extract::{Path as AxumPath, State}, + http, + http::{HeaderMap, StatusCode}, + Json, Router, +}; +use hex; +use std::net::{Ipv4Addr, SocketAddr}; +use std::path::Path; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use switchgear_components::discovery::memory::MemoryDiscoveryBackendStore; +use switchgear_components::offer::memory::MemoryOfferStore; +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, + DiscoveryBackendStore, +}; +use switchgear_service_api::offer::{ + OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, OfferRecordSparse, + OfferStore, +}; +use switchgear_testing::ports::PortAllocator; +use tokio::net::TcpListener as TokioTcpListener; +use tokio::sync::Notify; +use tokio::time::{sleep as tokio_sleep, timeout}; +use uuid::Uuid; + +#[derive(Clone)] +struct DiscoveryState { + store: MemoryDiscoveryBackendStore, +} + +#[derive(Clone)] +struct OfferState { + store: MemoryOfferStore, + max_page_size: usize, +} + +async fn discovery_health() -> StatusCode { + StatusCode::OK +} + +async fn get_backend( + State(state): State, + AxumPath(public_key): AxumPath, +) -> Result, StatusCode> { + match state.store.get(&public_key).await { + Ok(Some(backend)) => Ok(Json(backend)), + Ok(None) => Err(StatusCode::NOT_FOUND), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn get_backends( + State(state): State, + headers: HeaderMap, +) -> Result<(StatusCode, HeaderMap, Json>), (StatusCode, HeaderMap)> { + let if_none_match = headers + .get(http::header::IF_NONE_MATCH) + .and_then(|v| v.to_str().ok()) + .and_then(|s| { + // Decode hex etag to u64 + hex::decode(s).ok().and_then(|bytes| { + let arr: [u8; 8] = bytes.try_into().ok()?; + Some(u64::from_be_bytes(arr)) + }) + }); + + match state.store.get_all(if_none_match).await { + Ok(result) => { + let mut response_headers = HeaderMap::new(); + // Encode etag as hex string (8 bytes) + let etag_hex = hex::encode(result.etag.to_be_bytes()); + response_headers.insert(http::header::ETAG, etag_hex.parse().unwrap()); + response_headers.insert( + http::header::CACHE_CONTROL, + "no-store, no-cache, must-revalidate".parse().unwrap(), + ); + response_headers.insert( + http::header::EXPIRES, + "Thu, 01 Jan 1970 00:00:00 GMT".parse().unwrap(), + ); + response_headers.insert(http::header::PRAGMA, "no-cache".parse().unwrap()); + + match result.backends { + Some(backends) => Ok((StatusCode::OK, response_headers, Json(backends))), + None => Err((StatusCode::NOT_MODIFIED, response_headers)), + } + } + Err(_) => { + let headers = HeaderMap::new(); + Err((StatusCode::INTERNAL_SERVER_ERROR, headers)) + } + } +} + +async fn post_backend( + State(state): State, + Json(backend): Json, +) -> Result<(StatusCode, HeaderMap), StatusCode> { + match state.store.post(backend.clone()).await { + Ok(Some(public_key)) => { + let mut headers = HeaderMap::new(); + headers.insert( + http::header::LOCATION, + public_key.to_string().parse().unwrap(), + ); + Ok((StatusCode::CREATED, headers)) + } + Ok(None) => Err(StatusCode::CONFLICT), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn put_backend( + State(state): State, + AxumPath(public_key): AxumPath, + Json(backend_sparse): Json, +) -> Result { + let backend = DiscoveryBackend { + public_key, + backend: backend_sparse, + }; + + match state.store.put(backend).await { + Ok(true) => Ok(StatusCode::CREATED), + Ok(false) => Ok(StatusCode::NO_CONTENT), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn patch_backend( + State(state): State, + AxumPath(public_key): AxumPath, + Json(patch_sparse): Json, +) -> Result { + let patch = DiscoveryBackendPatch { + public_key, + backend: patch_sparse, + }; + + match state.store.patch(patch).await { + Ok(true) => Ok(StatusCode::NO_CONTENT), + Ok(false) => Err(StatusCode::NOT_FOUND), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn delete_backend( + State(state): State, + AxumPath(public_key): AxumPath, +) -> Result { + match state.store.delete(&public_key).await { + Ok(true) => Ok(StatusCode::NO_CONTENT), + Ok(false) => Err(StatusCode::NOT_FOUND), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +// Offer handlers +async fn offer_health() -> StatusCode { + StatusCode::OK +} + +async fn get_offer( + State(state): State, + AxumPath((partition, id)): AxumPath<(String, Uuid)>, +) -> Result, StatusCode> { + match state.store.get_offer(&partition, &id).await { + Ok(Some(offer)) => Ok(Json(offer)), + Ok(None) => Err(StatusCode::NOT_FOUND), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn get_offers( + State(state): State, + AxumPath(partition): AxumPath, + axum::extract::Query(params): axum::extract::Query>, +) -> Result>, StatusCode> { + let start: usize = params + .get("start") + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let count: usize = params + .get("count") + .and_then(|s| s.parse().ok()) + .unwrap_or(100); + + if count > state.max_page_size { + return Err(StatusCode::BAD_REQUEST); + } + + match state.store.get_offers(&partition, start, count).await { + Ok(offers) => Ok(Json(offers)), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn post_offer( + State(state): State, + Json(offer): Json, +) -> Result<(StatusCode, HeaderMap), StatusCode> { + match state.store.post_offer(offer.clone()).await { + Ok(Some(id)) => { + let mut headers = HeaderMap::new(); + headers.insert( + http::header::LOCATION, + format!("{}/{}", offer.partition, id).parse().unwrap(), + ); + Ok((StatusCode::CREATED, headers)) + } + Ok(None) => { + let mut headers = HeaderMap::new(); + headers.insert( + http::header::LOCATION, + format!("{}/{}", offer.partition, offer.id).parse().unwrap(), + ); + Err(StatusCode::CONFLICT) + } + Err(_) => Err(StatusCode::BAD_REQUEST), + } +} + +async fn put_offer( + State(state): State, + AxumPath((partition, id)): AxumPath<(String, Uuid)>, + Json(offer_sparse): Json, +) -> Result { + let offer = OfferRecord { + partition, + id, + offer: offer_sparse, + }; + + match state.store.put_offer(offer).await { + Ok(true) => Ok(StatusCode::CREATED), + Ok(false) => Ok(StatusCode::NO_CONTENT), + Err(_) => Err(StatusCode::BAD_REQUEST), + } +} + +async fn delete_offer( + State(state): State, + AxumPath((partition, id)): AxumPath<(String, Uuid)>, +) -> Result { + match state.store.delete_offer(&partition, &id).await { + Ok(true) => Ok(StatusCode::NO_CONTENT), + Ok(false) => Err(StatusCode::NOT_FOUND), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn get_metadata( + State(state): State, + AxumPath((partition, id)): AxumPath<(String, Uuid)>, +) -> Result, StatusCode> { + match state.store.get_metadata(&partition, &id).await { + Ok(Some(metadata)) => Ok(Json(metadata)), + Ok(None) => Err(StatusCode::NOT_FOUND), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn get_all_metadata( + State(state): State, + AxumPath(partition): AxumPath, + axum::extract::Query(params): axum::extract::Query>, +) -> Result>, StatusCode> { + let start: usize = params + .get("start") + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let count: usize = params + .get("count") + .and_then(|s| s.parse().ok()) + .unwrap_or(100); + + if count > state.max_page_size { + return Err(StatusCode::BAD_REQUEST); + } + + match state.store.get_all_metadata(&partition, start, count).await { + Ok(metadata) => Ok(Json(metadata)), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn post_metadata( + State(state): State, + Json(metadata): Json, +) -> Result<(StatusCode, HeaderMap), StatusCode> { + match state.store.post_metadata(metadata.clone()).await { + Ok(Some(id)) => { + let mut headers = HeaderMap::new(); + headers.insert( + http::header::LOCATION, + format!("{}/{}", metadata.partition, id).parse().unwrap(), + ); + Ok((StatusCode::CREATED, headers)) + } + Ok(None) => { + let mut headers = HeaderMap::new(); + headers.insert( + http::header::LOCATION, + format!("{}/{}", metadata.partition, metadata.id) + .parse() + .unwrap(), + ); + Err(StatusCode::CONFLICT) + } + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn put_metadata( + State(state): State, + AxumPath((partition, id)): AxumPath<(String, Uuid)>, + Json(metadata_sparse): Json, +) -> Result { + let metadata = OfferMetadata { + partition, + id, + metadata: metadata_sparse, + }; + + match state.store.put_metadata(metadata).await { + Ok(true) => Ok(StatusCode::CREATED), + Ok(false) => Ok(StatusCode::NO_CONTENT), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +async fn delete_metadata( + State(state): State, + AxumPath((partition, id)): AxumPath<(String, Uuid)>, +) -> Result { + match state.store.delete_metadata(&partition, &id).await { + Ok(true) => Ok(StatusCode::NO_CONTENT), + Ok(false) => Err(StatusCode::NOT_FOUND), + Err(_) => Err(StatusCode::BAD_REQUEST), + } +} + +pub struct TestService { + pub discovery_port: u16, + pub offer_port: u16, + _discovery_handle: tokio::task::JoinHandle>, + _offer_handle: tokio::task::JoinHandle>, + shutdown_notify: Arc, + pub discovery_authorization: String, + pub offer_authorization: String, +} + +impl TestService { + pub async fn start(ports_path: &Path) -> anyhow::Result { + let discovery_port = PortAllocator::find_available_port(ports_path)?; + let offer_port = PortAllocator::find_available_port(ports_path)?; + + let discovery_state = DiscoveryState { + store: MemoryDiscoveryBackendStore::default(), + }; + + let offer_state = OfferState { + store: MemoryOfferStore::default(), + max_page_size: 100, + }; + + let discovery_router = Router::new() + .route("/discovery/{public_key}", get(get_backend)) + .route("/discovery/{public_key}", put(put_backend)) + .route("/discovery/{public_key}", patch(patch_backend)) + .route("/discovery/{public_key}", delete(delete_backend)) + .route("/discovery", get(get_backends)) + .route("/discovery", post(post_backend)) + .route("/health", get(discovery_health)) + .with_state(discovery_state); + + let offer_router = Router::new() + .route("/offers/{partition}/{id}", get(get_offer)) + .route("/offers/{partition}/{id}", put(put_offer)) + .route("/offers/{partition}/{id}", delete(delete_offer)) + .route("/offers/{partition}", get(get_offers)) + .route("/offers", post(post_offer)) + .route("/metadata/{partition}/{id}", get(get_metadata)) + .route("/metadata/{partition}/{id}", put(put_metadata)) + .route("/metadata/{partition}/{id}", delete(delete_metadata)) + .route("/metadata/{partition}", get(get_all_metadata)) + .route("/metadata", post(post_metadata)) + .route("/health", get(offer_health)) + .with_state(offer_state); + + let discovery_listener = + TokioTcpListener::bind(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), discovery_port)) + .await?; + let offer_listener = + TokioTcpListener::bind(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), offer_port)).await?; + + let shutdown_notify = Arc::new(Notify::new()); + let discovery_shutdown = shutdown_notify.clone(); + let offer_shutdown = shutdown_notify.clone(); + + let discovery_handle = tokio::spawn(async move { + axum::serve(discovery_listener, discovery_router) + .with_graceful_shutdown(async move { + discovery_shutdown.notified().await; + }) + .await + }); + + let offer_handle = tokio::spawn(async move { + axum::serve(offer_listener, offer_router) + .with_graceful_shutdown(async move { + offer_shutdown.notified().await; + }) + .await + }); + + let service = TestService { + discovery_port, + offer_port, + _discovery_handle: discovery_handle, + _offer_handle: offer_handle, + shutdown_notify, + discovery_authorization: "mock-bearer-token".to_string(), + offer_authorization: "mock-bearer-token".to_string(), + }; + + service.wait_for_startup().await?; + + Ok(service) + } + + async fn wait_for_startup(&self) -> anyhow::Result<()> { + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(2)) + .build()?; + let timeout_duration = Duration::from_secs(15); + let start = Instant::now(); + + while start.elapsed() < timeout_duration { + let discovery_health = timeout( + Duration::from_secs(1), + client + .get(format!("http://127.0.0.1:{}/health", self.discovery_port)) + .send(), + ) + .await; + + let offer_health = timeout( + Duration::from_secs(1), + client + .get(format!("http://127.0.0.1:{}/health", self.offer_port)) + .send(), + ) + .await; + + if discovery_health.is_ok() + && discovery_health?.is_ok() + && offer_health.is_ok() + && offer_health?.is_ok() + { + return Ok(()); + } + + tokio_sleep(Duration::from_millis(200)).await; + } + + bail!("Services failed to start within timeout") + } + + pub fn discovery_base_url(&self) -> String { + format!("http://127.0.0.1:{}", self.discovery_port) + } + + pub fn offer_base_url(&self) -> String { + format!("http://127.0.0.1:{}", self.offer_port) + } + + pub async fn shutdown(self) { + self.shutdown_notify.notify_waiters(); + let _ = self._discovery_handle.await; + let _ = self._offer_handle.await; + } +} diff --git a/service/tests/common/mod.rs b/components/tests/common/mod.rs similarity index 60% rename from service/tests/common/mod.rs rename to components/tests/common/mod.rs index e1b8307..290b60f 100644 --- a/service/tests/common/mod.rs +++ b/components/tests/common/mod.rs @@ -1,3 +1,3 @@ pub mod discovery; +pub mod mock_service; pub mod offer; -pub mod service; diff --git a/service/tests/common/offer.rs b/components/tests/common/offer.rs similarity index 99% rename from service/tests/common/offer.rs rename to components/tests/common/offer.rs index 350e602..eec43f1 100644 --- a/service/tests/common/offer.rs +++ b/components/tests/common/offer.rs @@ -1,12 +1,12 @@ use chrono::{Timelike, Utc}; use sha2::Digest; -use switchgear_service::api::lnurl::LnUrlOfferMetadata; -use switchgear_service::api::offer::{ +use switchgear_components::offer::error::{OfferStoreError, OfferStoreErrorSourceKind}; +use switchgear_service_api::lnurl::LnUrlOfferMetadata; +use switchgear_service_api::offer::{ OfferMetadata, OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse, OfferMetadataStore, OfferProvider, OfferRecord, OfferRecordSparse, OfferStore, }; -use switchgear_service::api::service::ServiceErrorSource; -use switchgear_service::components::offer::error::{OfferStoreError, OfferStoreErrorSourceKind}; +use switchgear_service_api::service::ServiceErrorSource; use uuid::Uuid; // Test data generators diff --git a/service/tests/discovery/db_mysql.rs b/components/tests/discovery/db_mysql.rs similarity index 55% rename from service/tests/discovery/db_mysql.rs rename to components/tests/discovery/db_mysql.rs index b41a62d..2d9af7e 100644 --- a/service/tests/discovery/db_mysql.rs +++ b/components/tests/discovery/db_mysql.rs @@ -1,11 +1,11 @@ use crate::common::discovery; use anyhow::anyhow; -use switchgear_service::components::discovery::db::DbDiscoveryBackendStore; +use switchgear_components::discovery::db::DbDiscoveryBackendStore; use switchgear_testing::db::TestMysqlDatabase; use switchgear_testing::services::IntegrationTestServices; use uuid::Uuid; -async fn create_mysql_store() -> Option<(DbDiscoveryBackendStore, TestMysqlDatabase)> { +async fn create_mysql_store() -> (DbDiscoveryBackendStore, TestMysqlDatabase) { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); @@ -14,13 +14,9 @@ async fn create_mysql_store() -> Option<(DbDiscoveryBackendStore, TestMysqlDatab "test_discovery_{}", Uuid::new_v4().to_string().replace("-", "") ); - let services = IntegrationTestServices::create().unwrap(); + let services = IntegrationTestServices::new(); - let mysql = match services.mysql() { - None => return None, - Some(v) => v, - }; - let db = TestMysqlDatabase::new("root", &db_name, mysql, false, None); + let db = TestMysqlDatabase::new("root", &db_name, services.mysql(), false, None); let store = DbDiscoveryBackendStore::connect(db.connection_url(), 5) .await @@ -28,86 +24,59 @@ async fn create_mysql_store() -> Option<(DbDiscoveryBackendStore, TestMysqlDatab store.migrate_up().await.unwrap(); - Some((store, db)) + (store, db) } #[tokio::test] async fn test_mysql_post_new_backend_returns_address() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_post_new_backend_returns_address(store).await; } #[tokio::test] async fn test_mysql_get_returns_correct_backends() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_get_returns_correct_backends(store).await; } #[tokio::test] async fn test_mysql_delete_removes_and_returns_backends() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_delete_removes_target(store).await; } #[tokio::test] async fn test_mysql_put_new_backend_returns_true() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_put_new_backend_returns_true(store).await; } #[tokio::test] async fn test_mysql_put_existing_backend_updates_and_returns_false() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_put_existing_backend_updates_and_returns_false(store).await; } #[tokio::test] async fn test_mysql_test_patch_backend() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_patch_backend(store).await; } #[tokio::test] async fn test_mysql_test_patch_missing_backend() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_patch_missing_backend(store).await; } #[tokio::test] async fn test_mysql_etag_changes_on_mutations_get_all() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_etag_changes_on_mutations_get_all(store).await; } #[tokio::test] async fn test_mysql_etag_conditional_get_all() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; discovery::test_etag_conditional_get_all(store).await; } diff --git a/service/tests/discovery/db_postgres.rs b/components/tests/discovery/db_postgres.rs similarity index 55% rename from service/tests/discovery/db_postgres.rs rename to components/tests/discovery/db_postgres.rs index 57570d6..850ab91 100644 --- a/service/tests/discovery/db_postgres.rs +++ b/components/tests/discovery/db_postgres.rs @@ -1,11 +1,11 @@ use crate::common::discovery; use anyhow::anyhow; -use switchgear_service::components::discovery::db::DbDiscoveryBackendStore; +use switchgear_components::discovery::db::DbDiscoveryBackendStore; use switchgear_testing::db::TestPostgresDatabase; use switchgear_testing::services::IntegrationTestServices; use uuid::Uuid; -async fn create_postgres_store() -> Option<(DbDiscoveryBackendStore, TestPostgresDatabase)> { +async fn create_postgres_store() -> (DbDiscoveryBackendStore, TestPostgresDatabase) { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); @@ -14,13 +14,9 @@ async fn create_postgres_store() -> Option<(DbDiscoveryBackendStore, TestPostgre "test_discovery_{}", Uuid::new_v4().to_string().replace("-", "") ); - let services = IntegrationTestServices::create().unwrap(); + let services = IntegrationTestServices::new(); - let postgres = match services.postgres() { - None => return None, - Some(v) => v, - }; - let db = TestPostgresDatabase::new("postgres", &db_name, postgres, false, None); + let db = TestPostgresDatabase::new("postgres", &db_name, services.postgres(), false, None); let store = DbDiscoveryBackendStore::connect(db.connection_url(), 5) .await @@ -28,86 +24,59 @@ async fn create_postgres_store() -> Option<(DbDiscoveryBackendStore, TestPostgre store.migrate_up().await.unwrap(); - Some((store, db)) + (store, db) } #[tokio::test] async fn test_postgres_post_new_backend_returns_address() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_post_new_backend_returns_address(store).await; } #[tokio::test] async fn test_postgres_get_returns_correct_backends() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_get_returns_correct_backends(store).await; } #[tokio::test] async fn test_postgres_delete_removes_and_returns_backends() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_delete_removes_target(store).await; } #[tokio::test] async fn test_postgres_put_new_backend_returns_true() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_put_new_backend_returns_true(store).await; } #[tokio::test] async fn test_postgres_put_existing_backend_updates_and_returns_false() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_put_existing_backend_updates_and_returns_false(store).await; } #[tokio::test] async fn test_postgres_test_patch_backend() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_patch_backend(store).await; } #[tokio::test] async fn test_postgres_test_patch_missing_backend() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_patch_missing_backend(store).await; } #[tokio::test] async fn test_postgres_etag_changes_on_mutations_get_all() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_etag_changes_on_mutations_get_all(store).await; } #[tokio::test] async fn test_postgres_etag_conditional_get_all() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; discovery::test_etag_conditional_get_all(store).await; } diff --git a/service/tests/discovery/db_sqlite.rs b/components/tests/discovery/db_sqlite.rs similarity index 96% rename from service/tests/discovery/db_sqlite.rs rename to components/tests/discovery/db_sqlite.rs index adb0156..e7143cb 100644 --- a/service/tests/discovery/db_sqlite.rs +++ b/components/tests/discovery/db_sqlite.rs @@ -1,5 +1,5 @@ use std::path::Path; -use switchgear_service::components::discovery::db::DbDiscoveryBackendStore; +use switchgear_components::discovery::db::DbDiscoveryBackendStore; use tempfile::TempDir; use crate::common::discovery; diff --git a/service/tests/discovery/http.rs b/components/tests/discovery/http.rs similarity index 88% rename from service/tests/discovery/http.rs rename to components/tests/discovery/http.rs index 4f29286..a268907 100644 --- a/service/tests/discovery/http.rs +++ b/components/tests/discovery/http.rs @@ -1,17 +1,17 @@ -use crate::common::{discovery, service}; +use crate::common::{discovery, mock_service}; use anyhow::anyhow; use std::path::PathBuf; use std::time::Duration; -use switchgear_service::api::discovery::HttpDiscoveryBackendClient; -use switchgear_service::components::discovery::http::HttpDiscoveryBackendStore; +use switchgear_components::discovery::http::HttpDiscoveryBackendStore; +use switchgear_service_api::discovery::HttpDiscoveryBackendClient; -async fn create_http_store() -> (HttpDiscoveryBackendStore, service::TestService) { +async fn create_http_store() -> (HttpDiscoveryBackendStore, mock_service::TestService) { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); let ports_path = PathBuf::from(env!("CARGO_TARGET_TMPDIR")); - let test_service = service::TestService::start(&ports_path).await.unwrap(); + let test_service = mock_service::TestService::start(&ports_path).await.unwrap(); let base_url = test_service.discovery_base_url(); let store = HttpDiscoveryBackendStore::create( diff --git a/service/tests/discovery/main.rs b/components/tests/discovery/main.rs similarity index 100% rename from service/tests/discovery/main.rs rename to components/tests/discovery/main.rs diff --git a/service/tests/discovery/memory.rs b/components/tests/discovery/memory.rs similarity index 96% rename from service/tests/discovery/memory.rs rename to components/tests/discovery/memory.rs index 8d9a05b..c2cf69c 100644 --- a/service/tests/discovery/memory.rs +++ b/components/tests/discovery/memory.rs @@ -1,4 +1,4 @@ -use switchgear_service::components::discovery::memory::MemoryDiscoveryBackendStore; +use switchgear_components::discovery::memory::MemoryDiscoveryBackendStore; use crate::common::discovery; diff --git a/service/tests/ln/cln.rs b/components/tests/ln/cln.rs similarity index 78% rename from service/tests/ln/cln.rs rename to components/tests/ln/cln.rs index 8cf0d9f..c6aa07e 100644 --- a/service/tests/ln/cln.rs +++ b/components/tests/ln/cln.rs @@ -1,53 +1,34 @@ -use crate::try_create_cln_backend; -use anyhow::{anyhow, bail}; +use crate::try_create_cln_backend_implementation; +use anyhow::anyhow; use bitcoin_hashes::Hash; use lightning_invoice::Bolt11Invoice; use rand::{distributions::Alphanumeric, Rng}; use sha2::{Digest, Sha256}; use std::str::FromStr; use std::time::Duration; -use switchgear_service::api::discovery::DiscoveryBackendImplementation; -use switchgear_service::components::pool::cln::grpc::client::TonicClnGrpcClient; -use switchgear_service::components::pool::{Bolt11InvoiceDescription, LnRpcClient}; +use switchgear_components::pool::cln::grpc::client::TonicClnGrpcClient; +use switchgear_components::pool::error::LnPoolError; +use switchgear_components::pool::{Bolt11InvoiceDescription, LnRpcClient}; use switchgear_testing::credentials::lightning::LnCredentials; -async fn try_create_cln_tonic_client( +async fn create_cln_tonic_client( credentials: &LnCredentials, -) -> anyhow::Result< - Option< - Box< - dyn LnRpcClient - + Send - + Sync - + 'static, - >, - >, -> { +) -> anyhow::Result + Send + Sync + 'static>> { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); - let backend = match try_create_cln_backend(credentials)? { - None => return Ok(None), - Some(backend) => match backend.backend.implementation { - DiscoveryBackendImplementation::ClnGrpc(b) => b, - _ => bail!("wrong implementation"), - }, - }; + let backend = try_create_cln_backend_implementation(credentials)?; let client = TonicClnGrpcClient::create(Duration::from_secs(1), backend, &[])?; - Ok(Some(Box::new(client))) + Ok(Box::new(client)) } #[tokio::test] async fn test_cln_tonic_invoice_with_direct_description() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_cln_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_cln_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -96,11 +77,7 @@ async fn test_cln_tonic_invoice_with_direct_description() { #[tokio::test] async fn test_cln_tonic_invoice_with_direct_into_hash_description() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_cln_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_cln_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -147,11 +124,7 @@ async fn test_cln_tonic_invoice_with_direct_into_hash_description() { #[tokio::test] async fn test_cln_tonic_invoice_with_hash_description_produces_error() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_cln_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_cln_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -187,11 +160,7 @@ async fn test_cln_tonic_invoice_with_hash_description_produces_error() { #[tokio::test] async fn test_cln_tonic_invoice_with_none_amount() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_cln_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_cln_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -233,11 +202,7 @@ async fn test_cln_tonic_invoice_with_none_amount() { #[tokio::test] async fn test_cln_tonic_invoice_with_none_expiry() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_cln_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_cln_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -281,11 +246,7 @@ async fn test_cln_tonic_invoice_with_none_expiry() { #[tokio::test] async fn test_cln_tonic_metrics() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_cln_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_cln_tonic_client(&credentials).await.unwrap(); let metrics_result = client .get_metrics() diff --git a/service/tests/ln/lnd.rs b/components/tests/ln/lnd.rs similarity index 78% rename from service/tests/ln/lnd.rs rename to components/tests/ln/lnd.rs index 9382a09..38f2aca 100644 --- a/service/tests/ln/lnd.rs +++ b/components/tests/ln/lnd.rs @@ -1,51 +1,38 @@ -use crate::try_create_lnd_backend; -use anyhow::{anyhow, bail}; +use crate::try_create_lnd_backend_implementation; +use anyhow::anyhow; use bitcoin_hashes::Hash; use lightning_invoice::Bolt11Invoice; use rand::{distributions::Alphanumeric, Rng}; use sha2::Digest; use std::str::FromStr; use std::time::Duration; -use switchgear_service::api::discovery::DiscoveryBackendImplementation; -use switchgear_service::components::pool::lnd::grpc::client::TonicLndGrpcClient; -use switchgear_service::components::pool::{Bolt11InvoiceDescription, LnRpcClient}; +use switchgear_components::pool::lnd::grpc::client::TonicLndGrpcClient; +use switchgear_components::pool::{Bolt11InvoiceDescription, LnRpcClient}; use switchgear_testing::credentials::lightning::LnCredentials; type LnClientBox = Box< - dyn LnRpcClient + dyn LnRpcClient + Send + Sync + 'static, >; -async fn try_create_lnd_tonic_client( - credentials: &LnCredentials, -) -> anyhow::Result> { +async fn create_lnd_tonic_client(credentials: &LnCredentials) -> anyhow::Result { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); - let backend = match try_create_lnd_backend(credentials)? { - None => return Ok(None), - Some(backend) => match backend.backend.implementation { - DiscoveryBackendImplementation::LndGrpc(b) => b, - _ => bail!("wrong implementation"), - }, - }; + let backend = try_create_lnd_backend_implementation(credentials)?; let client = TonicLndGrpcClient::create(Duration::from_secs(1), backend, &[])?; - Ok(Some(Box::new(client))) + Ok(Box::new(client)) } #[tokio::test] async fn test_lnd_tonic_invoice_with_direct_description() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_lnd_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, - Err(e) => panic!("{}", e), - }; + let client = create_lnd_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -87,20 +74,13 @@ async fn test_lnd_tonic_invoice_with_direct_description() { } } - // Validate expiry assert_eq!(invoice.expiry_time().as_secs(), expected_expiry_secs); - - eprintln!("lnd success! credentials: {:?}", credentials.get_backends()); } #[tokio::test] async fn test_lnd_tonic_invoice_with_hash_description() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_lnd_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_lnd_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -141,11 +121,7 @@ async fn test_lnd_tonic_invoice_with_hash_description() { #[tokio::test] async fn test_lnd_tonic_invoice_with_none_amount() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_lnd_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_lnd_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -187,11 +163,7 @@ async fn test_lnd_tonic_invoice_with_none_amount() { #[tokio::test] async fn test_lnd_tonic_invoice_with_direct_into_hash_description() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_lnd_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, // Test skipped gracefully - Err(e) => panic!("{}", e), - }; + let client = create_lnd_tonic_client(&credentials).await.unwrap(); let random_string: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -242,11 +214,7 @@ async fn test_lnd_tonic_invoice_with_direct_into_hash_description() { #[tokio::test] async fn test_lnd_tonic_metrics() { let credentials = LnCredentials::create().unwrap(); - let client = match try_create_lnd_tonic_client(&credentials).await { - Ok(Some(client)) => client, - Ok(None) => return, - Err(e) => panic!("{}", e), - }; + let client = create_lnd_tonic_client(&credentials).await.unwrap(); let metrics_result = client .get_metrics() diff --git a/components/tests/ln/main.rs b/components/tests/ln/main.rs new file mode 100644 index 0000000..109e1cc --- /dev/null +++ b/components/tests/ln/main.rs @@ -0,0 +1,58 @@ +use switchgear_components::pool::cln::grpc::config::{ + ClnGrpcClientAuth, ClnGrpcClientAuthPath, ClnGrpcDiscoveryBackendImplementation, +}; +use switchgear_components::pool::lnd::grpc::config::{ + LndGrpcClientAuth, LndGrpcClientAuthPath, LndGrpcDiscoveryBackendImplementation, +}; +use switchgear_testing::credentials::lightning::LnCredentials; +use url::Url; + +#[path = "../common/mod.rs"] +pub mod common; + +mod cln; +mod lnd; + +pub fn try_create_cln_backend_implementation( + credentials: &LnCredentials, +) -> anyhow::Result { + let backends = credentials.get_backends()?; + + let cln_node = backends.cln.clone(); + + let url = Url::parse(&format!("https://{}", cln_node.address))?; + + let implementation = ClnGrpcDiscoveryBackendImplementation { + url, + auth: ClnGrpcClientAuth::Path(ClnGrpcClientAuthPath { + ca_cert_path: cln_node.ca_cert_path.into(), + client_cert_path: cln_node.client_cert_path, + client_key_path: cln_node.client_key_path, + }), + domain: None, + }; + + Ok(implementation) +} + +pub fn try_create_lnd_backend_implementation( + credentials: &LnCredentials, +) -> anyhow::Result { + let backends = credentials.get_backends()?; + + let lnd_node = backends.lnd.clone(); + + let url = Url::parse(&format!("https://{}", lnd_node.address))?; + + let implementation = LndGrpcDiscoveryBackendImplementation { + url, + auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { + tls_cert_path: lnd_node.tls_cert_path.into(), + macaroon_path: lnd_node.macaroon_path, + }), + amp_invoice: false, + domain: None, + }; + + Ok(implementation) +} diff --git a/service/tests/offer/db_mysql.rs b/components/tests/offer/db_mysql.rs similarity index 54% rename from service/tests/offer/db_mysql.rs rename to components/tests/offer/db_mysql.rs index daad1ca..c3e491d 100644 --- a/service/tests/offer/db_mysql.rs +++ b/components/tests/offer/db_mysql.rs @@ -1,11 +1,11 @@ use crate::common::offer; use anyhow::anyhow; -use switchgear_service::components::offer::db::DbOfferStore; +use switchgear_components::offer::db::DbOfferStore; use switchgear_testing::db::TestMysqlDatabase; use switchgear_testing::services::IntegrationTestServices; use uuid::Uuid; -async fn create_mysql_store() -> Option<(DbOfferStore, TestMysqlDatabase)> { +async fn create_mysql_store() -> (DbOfferStore, TestMysqlDatabase) { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); @@ -14,240 +14,161 @@ async fn create_mysql_store() -> Option<(DbOfferStore, TestMysqlDatabase)> { "test_discovery_{}", Uuid::new_v4().to_string().replace("-", "") ); - let services = IntegrationTestServices::create().unwrap(); + let services = IntegrationTestServices::new(); - let mysql = match services.mysql() { - None => return None, - Some(v) => v, - }; - let db = TestMysqlDatabase::new("root", &db_name, mysql, false, None); + let db = TestMysqlDatabase::new("root", &db_name, services.mysql(), false, None); let store = DbOfferStore::connect(db.connection_url(), 5).await.unwrap(); store.migrate_up().await.unwrap(); - Some((store, db)) + (store, db) } #[tokio::test] async fn test_mysql_get_nonexistent_offer() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_get_nonexistent_offer(store).await; } #[tokio::test] async fn test_mysql_post_new_offer() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_post_new_offer(store).await; } #[tokio::test] async fn test_mysql_post_existing_offer() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_post_existing_offer(store).await; } #[tokio::test] async fn test_mysql_put_new_offer() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_put_new_offer(store).await; } #[tokio::test] async fn test_mysql_put_existing_offer() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_put_existing_offer(store).await; } #[tokio::test] async fn test_mysql_delete_existing_offer() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_delete_existing_offer(store).await; } #[tokio::test] async fn test_mysql_delete_nonexistent_offer() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_delete_nonexistent_offer(store).await; } #[tokio::test] async fn test_mysql_get_offers() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_get_offers(store).await; } #[tokio::test] async fn test_mysql_get_nonexistent_offer_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_get_nonexistent_offer_metadata(store).await; } #[tokio::test] async fn test_mysql_post_new_offer_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_post_new_offer_metadata(store).await; } #[tokio::test] async fn test_mysql_post_existing_offer_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_post_existing_offer_metadata(store).await; } #[tokio::test] async fn test_mysql_put_new_offer_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_put_new_offer_metadata(store).await; } #[tokio::test] async fn test_mysql_put_existing_offer_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_put_existing_offer_metadata(store).await; } #[tokio::test] async fn test_mysql_delete_existing_offer_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_delete_existing_offer_metadata(store).await; } #[tokio::test] async fn test_mysql_delete_nonexistent_offer_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_delete_nonexistent_offer_metadata(store).await; } #[tokio::test] async fn test_mysql_get_all_offer_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_get_all_offer_metadata(store).await; } #[tokio::test] async fn test_mysql_offer_provider_successful_retrieval() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_offer_provider_successful_retrieval(store).await; } #[tokio::test] async fn test_mysql_offer_provider_offer_not_found() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_offer_provider_offer_not_found(store).await; } #[tokio::test] async fn test_mysql_offer_provider_metadata_not_found() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_offer_provider_metadata_not_found_or_foreign_key_constraint(store).await; } #[tokio::test] async fn test_mysql_offer_provider_hash_consistency() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_offer_provider_hash_consistency(store).await; } #[tokio::test] async fn test_mysql_offer_provider_different_metadata_different_hashes() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_offer_provider_different_metadata_different_hashes(store).await; } #[tokio::test] async fn test_mysql_offer_provider_valid_current_offer_returns_some() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_offer_provider_valid_current_offer_returns_some(store).await; } #[tokio::test] async fn test_mysql_post_offer_with_missing_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_post_offer_with_missing_metadata(store).await; } #[tokio::test] async fn test_mysql_put_offer_with_missing_metadata() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_put_offer_with_missing_metadata(store).await; } #[tokio::test] async fn test_mysql_delete_metadata_with_referencing_offers() { - let (store, _guard) = match create_mysql_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_mysql_store().await; offer::test_delete_metadata_with_referencing_offers(store).await; } diff --git a/service/tests/offer/db_postgres.rs b/components/tests/offer/db_postgres.rs similarity index 53% rename from service/tests/offer/db_postgres.rs rename to components/tests/offer/db_postgres.rs index 4e3d4b6..bb6dce9 100644 --- a/service/tests/offer/db_postgres.rs +++ b/components/tests/offer/db_postgres.rs @@ -1,250 +1,171 @@ use crate::common::offer; use anyhow::anyhow; -use switchgear_service::components::offer::db::DbOfferStore; +use switchgear_components::offer::db::DbOfferStore; use switchgear_testing::db::TestPostgresDatabase; use switchgear_testing::services::IntegrationTestServices; use uuid::Uuid; -async fn create_postgres_store() -> Option<(DbOfferStore, TestPostgresDatabase)> { +async fn create_postgres_store() -> (DbOfferStore, TestPostgresDatabase) { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); let db_name = format!("test_offer_{}", Uuid::new_v4().to_string().replace("-", "")); - let services = IntegrationTestServices::create().unwrap(); + let services = IntegrationTestServices::new(); - let postgres = match services.postgres() { - None => return None, - Some(v) => v, - }; - let db = TestPostgresDatabase::new("postgres", &db_name, postgres, false, None); + let db = TestPostgresDatabase::new("postgres", &db_name, services.postgres(), false, None); let store = DbOfferStore::connect(db.connection_url(), 5).await.unwrap(); store.migrate_up().await.unwrap(); - Some((store, db)) + (store, db) } #[tokio::test] async fn test_postgres_get_nonexistent_offer() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_get_nonexistent_offer(store).await; } #[tokio::test] async fn test_postgres_post_new_offer() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_post_new_offer(store).await; } #[tokio::test] async fn test_postgres_post_existing_offer() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_post_existing_offer(store).await; } #[tokio::test] async fn test_postgres_put_new_offer() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_put_new_offer(store).await; } #[tokio::test] async fn test_postgres_put_existing_offer() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_put_existing_offer(store).await; } #[tokio::test] async fn test_postgres_delete_existing_offer() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_delete_existing_offer(store).await; } #[tokio::test] async fn test_postgres_delete_nonexistent_offer() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_delete_nonexistent_offer(store).await; } #[tokio::test] async fn test_postgres_get_offers() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_get_offers(store).await; } #[tokio::test] async fn test_postgres_get_nonexistent_offer_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_get_nonexistent_offer_metadata(store).await; } #[tokio::test] async fn test_postgres_post_new_offer_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_post_new_offer_metadata(store).await; } #[tokio::test] async fn test_postgres_post_existing_offer_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_post_existing_offer_metadata(store).await; } #[tokio::test] async fn test_postgres_put_new_offer_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_put_new_offer_metadata(store).await; } #[tokio::test] async fn test_postgres_put_existing_offer_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_put_existing_offer_metadata(store).await; } #[tokio::test] async fn test_postgres_delete_existing_offer_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_delete_existing_offer_metadata(store).await; } #[tokio::test] async fn test_postgres_delete_nonexistent_offer_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_delete_nonexistent_offer_metadata(store).await; } #[tokio::test] async fn test_postgres_get_all_offer_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_get_all_offer_metadata(store).await; } #[tokio::test] async fn test_postgres_offer_provider_successful_retrieval() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_offer_provider_successful_retrieval(store).await; } #[tokio::test] async fn test_postgres_offer_provider_offer_not_found() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_offer_provider_offer_not_found(store).await; } #[tokio::test] async fn test_postgres_offer_provider_metadata_not_found() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_offer_provider_metadata_not_found_or_foreign_key_constraint(store).await; } #[tokio::test] async fn test_postgres_offer_provider_hash_consistency() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_offer_provider_hash_consistency(store).await; } #[tokio::test] async fn test_postgres_offer_provider_different_metadata_different_hashes() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_offer_provider_different_metadata_different_hashes(store).await; } #[tokio::test] async fn test_postgres_offer_provider_valid_current_offer_returns_some() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_offer_provider_valid_current_offer_returns_some(store).await; } #[tokio::test] async fn test_postgres_post_offer_with_missing_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_post_offer_with_missing_metadata(store).await; } #[tokio::test] async fn test_postgres_put_offer_with_missing_metadata() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_put_offer_with_missing_metadata(store).await; } #[tokio::test] async fn test_postgres_delete_metadata_with_referencing_offers() { - let (store, _guard) = match create_postgres_store().await { - None => return, - Some(v) => v, - }; + let (store, _guard) = create_postgres_store().await; offer::test_delete_metadata_with_referencing_offers(store).await; } diff --git a/service/tests/offer/db_sqlite.rs b/components/tests/offer/db_sqlite.rs similarity index 98% rename from service/tests/offer/db_sqlite.rs rename to components/tests/offer/db_sqlite.rs index 585111f..156c0ba 100644 --- a/service/tests/offer/db_sqlite.rs +++ b/components/tests/offer/db_sqlite.rs @@ -1,5 +1,5 @@ use std::path::Path; -use switchgear_service::components::offer::db::DbOfferStore; +use switchgear_components::offer::db::DbOfferStore; use tempfile::TempDir; use crate::common::offer; diff --git a/service/tests/offer/http.rs b/components/tests/offer/http.rs similarity index 95% rename from service/tests/offer/http.rs rename to components/tests/offer/http.rs index 92d3686..80b5715 100644 --- a/service/tests/offer/http.rs +++ b/components/tests/offer/http.rs @@ -1,17 +1,17 @@ -use crate::common::{offer, service}; +use crate::common::{mock_service, offer}; use anyhow::anyhow; use std::path::PathBuf; use std::time::Duration; -use switchgear_service::api::offer::HttpOfferClient; -use switchgear_service::components::offer::http::HttpOfferStore; +use switchgear_components::offer::http::HttpOfferStore; +use switchgear_service_api::offer::HttpOfferClient; -async fn create_http_store() -> (HttpOfferStore, service::TestService) { +async fn create_http_store() -> (HttpOfferStore, mock_service::TestService) { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); let ports_path = PathBuf::from(env!("CARGO_TARGET_TMPDIR")); - let test_service = service::TestService::start(&ports_path).await.unwrap(); + let test_service = mock_service::TestService::start(&ports_path).await.unwrap(); let base_url = test_service.offer_base_url(); let store = HttpOfferStore::create( diff --git a/service/tests/offer/main.rs b/components/tests/offer/main.rs similarity index 100% rename from service/tests/offer/main.rs rename to components/tests/offer/main.rs diff --git a/service/tests/offer/memory.rs b/components/tests/offer/memory.rs similarity index 98% rename from service/tests/offer/memory.rs rename to components/tests/offer/memory.rs index f6c5a49..16bd323 100644 --- a/service/tests/offer/memory.rs +++ b/components/tests/offer/memory.rs @@ -1,4 +1,4 @@ -use switchgear_service::components::offer::memory::MemoryOfferStore; +use switchgear_components::offer::memory::MemoryOfferStore; use crate::common::offer; diff --git a/doc/pingora_traits_component_diagram-PingoraLnBalancer_Trait_Dependencies.png b/doc/pingora_traits_component_diagram-PingoraLnBalancer_Trait_Dependencies.png deleted file mode 100644 index c725670..0000000 Binary files a/doc/pingora_traits_component_diagram-PingoraLnBalancer_Trait_Dependencies.png and /dev/null differ diff --git a/doc/pingora_traits_component_diagram.puml b/doc/pingora_traits_component_diagram.puml deleted file mode 100644 index 20fff39..0000000 --- a/doc/pingora_traits_component_diagram.puml +++ /dev/null @@ -1,52 +0,0 @@ -@startuml -title PingoraLnBalancer Trait Dependencies - -class PingoraLnBalancer - -package "lnurl-balancer-service" <> { - interface LnBalancer { - +get_invoice() - +health() - } - - interface LnBalancerBackgroundServices { - +run_continuous_health_check() - } - - interface LnClientPool { - +get_invoice() - +get_metrics() - +health() - +connect() - } - - interface LnMetricsCache { - +get() - +update() - } - - interface BackoffProvider { - +get_backoff() - } -} - -package "pingora-load-balancing" <> { - interface BackendSelection { - +iter() - } - - interface BackendIter -} - -' Implementations -PingoraLnBalancer ..|> LnBalancer : implements -PingoraLnBalancer ..|> LnBalancerBackgroundServices : implements - -' Generic constraints -PingoraLnBalancer --> BackendSelection : "S" -PingoraLnBalancer --> LnClientPool : "P" -PingoraLnBalancer --> LnMetricsCache : "M" -PingoraLnBalancer --> BackoffProvider : "B" -BackendSelection::Iter --> BackendIter : implements - -@enduml \ No newline at end of file diff --git a/doc/service_components_traits_diagram-Service_Components_Trait_Dependencies.png b/doc/service_components_traits_diagram-Service_Components_Trait_Dependencies.png deleted file mode 100644 index 6de7fa8..0000000 Binary files a/doc/service_components_traits_diagram-Service_Components_Trait_Dependencies.png and /dev/null differ diff --git a/doc/service_components_traits_diagram.puml b/doc/service_components_traits_diagram.puml deleted file mode 100644 index d05590b..0000000 --- a/doc/service_components_traits_diagram.puml +++ /dev/null @@ -1,35 +0,0 @@ -@startuml -title Service Components Trait Dependencies - -class DefaultLnClientPool - -package "lnurl-balancer-service::components" <> { - interface LnRpcClient { - +get_invoice() - +get_metrics() - +get_features() - } - - interface LnClientPool { - +get_invoice() - +get_metrics() - +connect() - } - - interface LnMetricsCache { - +get_cached_metrics() - } - - interface BackoffProvider { - +get_backoff() - } -} - -' Implementations -DefaultLnClientPool ..|> LnClientPool : implements -DefaultLnClientPool ..|> LnMetricsCache : implements - -' Generic constraints and associations -DefaultLnClientPool --> LnRpcClient : uses - -@enduml \ No newline at end of file diff --git a/doc/service_discovery_traits_diagram-Discovery_Components_Trait_Dependencies.png b/doc/service_discovery_traits_diagram-Discovery_Components_Trait_Dependencies.png deleted file mode 100644 index 53f60f7..0000000 Binary files a/doc/service_discovery_traits_diagram-Discovery_Components_Trait_Dependencies.png and /dev/null differ diff --git a/doc/service_discovery_traits_diagram.puml b/doc/service_discovery_traits_diagram.puml deleted file mode 100644 index 0289f86..0000000 --- a/doc/service_discovery_traits_diagram.puml +++ /dev/null @@ -1,25 +0,0 @@ -@startuml -title Discovery Components Trait Dependencies - -class MemoryDiscoveryBackendStore - -class DbDiscoveryBackendStore - -class HttpDiscoveryBackendStore - -package "lnurl-balancer-service::api::discovery" <> { - interface DiscoveryBackendStore { - +get() - +get_all() - +post() - +put() - +delete() - } -} - -' Implementations -MemoryDiscoveryBackendStore ..|> DiscoveryBackendStore : implements -DbDiscoveryBackendStore ..|> DiscoveryBackendStore : implements -HttpDiscoveryBackendStore ..|> DiscoveryBackendStore : implements - -@enduml \ No newline at end of file diff --git a/doc/service_offer_traits_diagram-Offer_Components_Trait_Dependencies.png b/doc/service_offer_traits_diagram-Offer_Components_Trait_Dependencies.png deleted file mode 100644 index 24e5149..0000000 Binary files a/doc/service_offer_traits_diagram-Offer_Components_Trait_Dependencies.png and /dev/null differ diff --git a/doc/service_offer_traits_diagram.puml b/doc/service_offer_traits_diagram.puml deleted file mode 100644 index a26901f..0000000 --- a/doc/service_offer_traits_diagram.puml +++ /dev/null @@ -1,54 +0,0 @@ -@startuml -title Offer Components Trait Dependencies - -class HttpOfferStore - -class MemoryOfferStore - -class DbOfferStore - -package "lnurl-balancer-service::api::offer" <> { - interface OfferStore { - +get_offer() - +get_offers() - +post_offer() - +put_offer() - +delete_offer() - } - - interface OfferMetadataStore { - +get_metadata() - +get_all_metadata() - +post_metadata() - +put_metadata() - +delete_metadata() - } - - interface OfferProvider { - +offer() - } - - interface HttpOfferClient { - +health() - } -} - -' Implementations -HttpOfferStore ..|> OfferStore : implements -HttpOfferStore ..|> OfferMetadataStore : implements -HttpOfferStore ..|> OfferProvider : implements -HttpOfferStore ..|> HttpOfferClient : implements - -MemoryOfferStore ..|> OfferStore : implements -MemoryOfferStore ..|> OfferMetadataStore : implements -MemoryOfferStore ..|> OfferProvider : implements - -DbOfferStore ..|> OfferStore : implements -DbOfferStore ..|> OfferMetadataStore : implements -DbOfferStore ..|> OfferProvider : implements - -' Trait relationships -HttpOfferClient --|> OfferStore : extends -HttpOfferClient --|> OfferMetadataStore : extends - -@enduml \ No newline at end of file diff --git a/doc/service_traits_component_diagram-Service_Layer_Trait_Relationships.png b/doc/service_traits_component_diagram-Service_Layer_Trait_Relationships.png deleted file mode 100644 index 40922a5..0000000 Binary files a/doc/service_traits_component_diagram-Service_Layer_Trait_Relationships.png and /dev/null differ diff --git a/doc/service_traits_component_diagram.puml b/doc/service_traits_component_diagram.puml deleted file mode 100644 index fbf04a9..0000000 --- a/doc/service_traits_component_diagram.puml +++ /dev/null @@ -1,30 +0,0 @@ -@startuml -title Service Layer Trait Relationships - -package "Services" { - class DiscoveryService - class LnUrlBalancerService - class OfferService -} - -package "Core Traits" { - interface DiscoveryBackendStore - interface OfferStore - interface OfferMetadataStore - interface OfferProvider - interface LnBalancer - interface BearerTokenValidator -} - -' Direct trait usage by services -DiscoveryService --> DiscoveryBackendStore : requires -DiscoveryService --> BearerTokenValidator : uses - -LnUrlBalancerService --> OfferProvider : requires -LnUrlBalancerService --> LnBalancer : requires - -OfferService --> OfferStore : requires -OfferService --> OfferMetadataStore : requires -OfferService --> BearerTokenValidator : uses - -@enduml \ No newline at end of file diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index 6f413c7..00894ae 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -21,11 +21,7 @@ impl MigrationTrait for DiscoveryBackendMigration { .col(string_null(DiscoveryBackend::Name)) .col(integer(DiscoveryBackend::Weight).not_null()) .col(boolean(DiscoveryBackend::Enabled).not_null()) - .col( - ColumnDef::new(DiscoveryBackend::Implementation) - .json_binary() - .not_null(), - ) + .col(blob(DiscoveryBackend::Implementation).not_null()) .col(timestamp_with_time_zone(DiscoveryBackend::CreatedAt).not_null()) .col(timestamp_with_time_zone(DiscoveryBackend::UpdatedAt).not_null()) .primary_key(Index::create().col(DiscoveryBackend::Id)) diff --git a/pingora/Cargo.toml b/pingora/Cargo.toml index 00be5be..469898f 100644 --- a/pingora/Cargo.toml +++ b/pingora/Cargo.toml @@ -18,15 +18,14 @@ async-trait = "0.1" axum = { version = "0.8", features = ["macros"] } backoff = { version = "0.4", features = ["tokio"] } chrono = { version = "0.4", features = ["serde"] } -switchgear-service.workspace = true log = "0.4" pingora-core = { version = "0.6", default-features = false } pingora-error = { version = "0.6", default-features = false } pingora-load-balancing = { version = "0.6", default-features = false } +switchgear-service-api.workspace = true thiserror = "2" tokio = { version = "1", features = ["full"] } uuid = { version = "1", features = ["v4", "serde"] } -url = "2" [dev-dependencies] chrono = "0.4" diff --git a/service/src/components/backoff.rs b/pingora/src/backoff.rs similarity index 100% rename from service/src/components/backoff.rs rename to pingora/src/backoff.rs diff --git a/pingora/src/balance.rs b/pingora/src/balance.rs index 20ef30e..9d2c18d 100644 --- a/pingora/src/balance.rs +++ b/pingora/src/balance.rs @@ -1,5 +1,6 @@ +use crate::backoff::BackoffProvider; use crate::error::PingoraLnError; -use crate::PingoraLnBackendExtension; +use crate::{PingoraLnBackendExtension, PingoraLnClientPool, PingoraLnMetricsCache}; use async_trait::async_trait; use backoff::backoff::Backoff; use log::{error, warn}; @@ -7,12 +8,9 @@ use pingora_core::services::background::BackgroundService; use pingora_load_balancing::selection::{BackendIter, BackendSelection}; use pingora_load_balancing::{Backend, LoadBalancer}; use std::sync::Arc; -use switchgear_service::api::balance::{LnBalancer, LnBalancerBackgroundServices}; -use switchgear_service::api::offer::Offer; -use switchgear_service::api::service::ServiceErrorSource; -use switchgear_service::components::backoff::BackoffProvider; -use switchgear_service::components::pool::error::LnPoolError; -use switchgear_service::components::pool::{LnClientPool, LnMetricsCache}; +use switchgear_service_api::balance::{LnBalancer, LnBalancerBackgroundServices}; +use switchgear_service_api::offer::Offer; +use switchgear_service_api::service::ServiceErrorSource; use tokio::sync::watch::Receiver; use tokio::time::sleep; @@ -100,8 +98,8 @@ impl PingoraLnBalancer where S: BackendSelection + 'static, S::Iter: BackendIter, - P: LnClientPool + Clone, - M: LnMetricsCache + Clone, + P: PingoraLnClientPool + Clone, + M: PingoraLnMetricsCache + Clone, B: BackoffProvider, X: MaxIterations, { @@ -172,16 +170,7 @@ where let invoice = self .pool .get_invoice(offer, backend, amount_msat.into(), expiry_secs.into()) - .await - .map_err(|e| { - PingoraLnError::from_pool_err( - e.esource(), - format!( - "generating invoice for offer {offer:?} from selected backend {backend:?}" - ), - e, - ) - })?; + .await?; Ok(invoice) } @@ -192,8 +181,8 @@ impl LnBalancer for PingoraLnBalancer where S: BackendSelection + Send + Sync + 'static, S::Iter: BackendIter, - P: LnClientPool + Send + Sync + Clone + 'static, - M: LnMetricsCache + Send + Sync + Clone + 'static, + P: PingoraLnClientPool + Send + Sync + Clone + 'static, + M: PingoraLnMetricsCache + Send + Sync + Clone + 'static, B: BackoffProvider + Send + Sync + 'static, X: MaxIterations, { @@ -284,8 +273,8 @@ impl LnBalancerBackgroundServices for PingoraLnBalancer + Send + Sync + Clone + 'static, - M: LnMetricsCache + Send + Sync + Clone + 'static, + P: PingoraLnClientPool + Send + Sync + Clone + 'static, + M: PingoraLnMetricsCache + Send + Sync + Clone + 'static, B: BackoffProvider + Send + Sync + 'static, X: MaxIterations, { @@ -297,7 +286,8 @@ where #[cfg(test)] mod tests { use super::*; - use crate::PingoraLnBackendExtension; + use crate::backoff::StopBackoffProvider; + use crate::{PingoraLnBackendExtension, PingoraLnMetrics}; use async_trait::async_trait; use pingora_error::Result as PingoraResult; use pingora_load_balancing::discovery::ServiceDiscovery; @@ -306,13 +296,11 @@ mod tests { use pingora_load_balancing::{Backends, LoadBalancer}; use std::collections::{BTreeSet, HashMap}; use std::hash::{DefaultHasher, Hash, Hasher}; + use std::io; use std::sync::{Arc, Mutex}; - use switchgear_service::api::balance::LnBalancer; - use switchgear_service::api::discovery::DiscoveryBackend; - use switchgear_service::api::service::ServiceErrorSource; - use switchgear_service::components::backoff::StopBackoffProvider; - use switchgear_service::components::pool::error::LnPoolError; - use switchgear_service::components::pool::{LnClientPool, LnMetrics, LnMetricsCache}; + use switchgear_service_api::balance::LnBalancer; + use switchgear_service_api::discovery::DiscoveryBackend; + use switchgear_service_api::service::ServiceErrorSource; use uuid::Uuid; #[derive(Clone)] @@ -322,8 +310,8 @@ mod tests { } #[async_trait] - impl LnClientPool for MockLnClientPool { - type Error = LnPoolError; + impl PingoraLnClientPool for MockLnClientPool { + type Error = PingoraLnError; type Key = Backend; async fn get_invoice( @@ -340,15 +328,15 @@ mod tests { Ok("mock_invoice".to_string()) } } else { - Err(LnPoolError::from_invalid_configuration( - "Mock pool forced failure", + Err(PingoraLnError::from_io_err( ServiceErrorSource::Upstream, "mock get_invoice", + io::Error::from(io::ErrorKind::Other), )) } } - async fn get_metrics(&self, _key: &Self::Key) -> Result { + async fn get_metrics(&self, _key: &Self::Key) -> Result { unimplemented!("get_metrics not needed for these tests") } @@ -359,11 +347,11 @@ mod tests { #[derive(Clone, Default)] struct MockLnMetricsCache { - metrics: Arc>>, + metrics: Arc>>, } impl MockLnMetricsCache { - fn set_metrics_for_backend(&self, backend: &Backend, metrics: LnMetrics) { + fn set_metrics_for_backend(&self, backend: &Backend, metrics: PingoraLnMetrics) { self.metrics .lock() .unwrap() @@ -371,9 +359,9 @@ mod tests { } } - impl LnMetricsCache for MockLnMetricsCache { + impl PingoraLnMetricsCache for MockLnMetricsCache { type Key = Backend; - fn get_cached_metrics(&self, backend: &Backend) -> Option { + fn get_cached_metrics(&self, backend: &Backend) -> Option { self.metrics.lock().unwrap().get(backend).cloned() } } @@ -537,7 +525,7 @@ mod tests { let backend = create_mock_backend("127.0.0.1:8080", &offer.partition); balancer.metrics.set_metrics_for_backend( &backend, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 100000, }, @@ -557,7 +545,7 @@ mod tests { let backend = create_mock_backend("127.0.0.1:8080", &offer.partition); balancer.metrics.set_metrics_for_backend( &backend, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 100000, }, @@ -617,7 +605,7 @@ mod tests { // Set metrics to allow the invoice balancer.metrics.set_metrics_for_backend( &backend, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 100000, }, @@ -648,7 +636,7 @@ mod tests { for backend in [&backend1, &backend2] { balancer.metrics.set_metrics_for_backend( backend, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 100000, }, @@ -699,7 +687,7 @@ mod tests { // Set metrics: low weight has sufficient capacity, high weight backends do not balancer.metrics.set_metrics_for_backend( &backend_low_weight, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 100000, // 100k * 0.8 = 80k effective (sufficient for 75k) }, @@ -713,7 +701,7 @@ mod tests { ] { balancer.metrics.set_metrics_for_backend( backend, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 80000, // 80k * 0.8 = 64k effective (insufficient for 75k) }, @@ -797,7 +785,7 @@ mod tests { // Set metrics: backend has insufficient capacity for the requested amount balancer.metrics.set_metrics_for_backend( &backend, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 80000, // 80k * 0.8 = 64k effective capacity }, @@ -853,7 +841,7 @@ mod tests { // but with None bias, this should be ignored balancer.metrics.set_metrics_for_backend( &backend_low_weight, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 100000, // Plenty of capacity }, @@ -867,7 +855,7 @@ mod tests { ] { balancer.metrics.set_metrics_for_backend( backend, - LnMetrics { + PingoraLnMetrics { healthy: true, node_effective_inbound_msat: 10000, // Very low capacity (10k) }, diff --git a/pingora/src/discovery.rs b/pingora/src/discovery.rs index 4bf56ba..c34b851 100644 --- a/pingora/src/discovery.rs +++ b/pingora/src/discovery.rs @@ -1,5 +1,4 @@ -use crate::error::PingoraLnError; -use crate::{PingoraBackendProvider, PingoraLnBackendExtension}; +use crate::{PingoraBackendProvider, PingoraLnBackendExtension, PingoraLnClientPool}; use arc_swap::ArcSwap; use async_trait::async_trait; use axum::http::Extensions; @@ -12,15 +11,8 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use std::net::{Ipv6Addr, SocketAddrV6}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use switchgear_service::api::discovery::DiscoveryBackendStore; -use switchgear_service::api::discovery::DiscoveryBackends; -use switchgear_service::api::service::ServiceErrorSource; -use switchgear_service::components::discovery::db::DbDiscoveryBackendStore; -use switchgear_service::components::discovery::http::HttpDiscoveryBackendStore; -use switchgear_service::components::discovery::memory::MemoryDiscoveryBackendStore; -use switchgear_service::components::pool::LnClientPool; - -pub struct DefaultPingoraLnDiscovery { + +pub struct LnServiceDiscovery { backend_provider: B, pool: P, partitions: BTreeSet, @@ -28,9 +20,9 @@ pub struct DefaultPingoraLnDiscovery { last_etag: AtomicU64, } -impl DefaultPingoraLnDiscovery { +impl LnServiceDiscovery { pub fn new(backend_provider: B, pool: P, partitions: HashSet) -> Self { - DefaultPingoraLnDiscovery { + LnServiceDiscovery { backend_provider, pool, partitions: partitions.into_iter().collect(), @@ -41,10 +33,10 @@ impl DefaultPingoraLnDiscovery { } #[async_trait] -impl ServiceDiscovery for DefaultPingoraLnDiscovery +impl ServiceDiscovery for LnServiceDiscovery where B: PingoraBackendProvider + Send + Sync, - P: LnClientPool + Send + Sync, + P: PingoraLnClientPool + Send + Sync, { async fn discover(&self) -> pingora_error::Result<(BTreeSet, HashMap)> { let etag = self.last_etag.load(Ordering::Relaxed); @@ -120,59 +112,11 @@ where } } -#[async_trait] -impl PingoraBackendProvider for MemoryDiscoveryBackendStore { - type Error = PingoraLnError; - - async fn backends(&self, etag: Option) -> Result { - let backends = self.get_all(etag).await.map_err(|e| { - PingoraLnError::from_discovery_backend_store_err( - ServiceErrorSource::Internal, - "getting all discovery backends", - e, - ) - })?; - Ok(backends) - } -} - -#[async_trait] -impl PingoraBackendProvider for DbDiscoveryBackendStore { - type Error = PingoraLnError; - - async fn backends(&self, etag: Option) -> Result { - let backends = self.get_all(etag).await.map_err(|e| { - PingoraLnError::from_discovery_backend_store_err( - ServiceErrorSource::Internal, - "getting all discovery backends", - e, - ) - })?; - Ok(backends) - } -} - -#[async_trait] -impl PingoraBackendProvider for HttpDiscoveryBackendStore { - type Error = PingoraLnError; - - async fn backends(&self, etag: Option) -> Result { - let backends = self.get_all(etag).await.map_err(|e| { - PingoraLnError::from_discovery_backend_store_err( - ServiceErrorSource::Internal, - "getting all discovery backends", - e, - ) - })?; - Ok(backends) - } -} - #[cfg(test)] mod tests { - use crate::discovery::DefaultPingoraLnDiscovery; + use crate::discovery::LnServiceDiscovery; use crate::error::PingoraLnError; - use crate::PingoraBackendProvider; + use crate::{PingoraBackendProvider, PingoraLnClientPool, PingoraLnMetrics}; use async_trait::async_trait; use pingora_load_balancing::discovery::ServiceDiscovery; use pingora_load_balancing::Backend; @@ -180,18 +124,13 @@ mod tests { use secp256k1::{PublicKey, Secp256k1, SecretKey}; use std::collections::{BTreeSet, HashSet}; use std::hash::{DefaultHasher, Hash, Hasher}; + use std::io; use std::sync::Arc; - use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendSparse, DiscoveryBackends, - }; - use switchgear_service::api::offer::Offer; - use switchgear_service::api::service::ServiceErrorSource; - use switchgear_service::components::pool::error::LnPoolError; - use switchgear_service::components::pool::lnd::grpc::config::{ - LndGrpcClientAuth, LndGrpcClientAuthPath, LndGrpcDiscoveryBackendImplementation, + use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendSparse, DiscoveryBackends, }; - use switchgear_service::components::pool::LnClientPool; - use switchgear_service::components::pool::LnMetrics; + use switchgear_service_api::offer::Offer; + use switchgear_service_api::service::ServiceErrorSource; use tokio::sync::Mutex; struct MockBackendProvider { @@ -211,16 +150,10 @@ mod tests { .cloned() .map(|s| s.into_iter().collect::>()) .ok_or_else(|| { - PingoraLnError::new( - crate::error::PingoraLnErrorSourceKind::PoolError( - LnPoolError::from_invalid_configuration( - "Mock BackendProvider forced error", - ServiceErrorSource::Internal, - "get_offer_invoice", - ), - ), + PingoraLnError::from_io_err( ServiceErrorSource::Internal, "Mock BackendProvider forced error", + io::Error::from(io::ErrorKind::Other), ) })?; Ok(DiscoveryBackends { @@ -235,7 +168,7 @@ mod tests { } #[async_trait] - impl LnClientPool for MockLnClientPool { + impl PingoraLnClientPool for MockLnClientPool { type Error = PingoraLnError; type Key = Backend; @@ -249,22 +182,16 @@ mod tests { unimplemented!("get_invoice not implemented for MockLnClientPool") } - async fn get_metrics(&self, _key: &Self::Key) -> Result { + async fn get_metrics(&self, _key: &Self::Key) -> Result { unimplemented!("get_metrics not implemented for MockLnClientPool") } fn connect(&self, _key: Self::Key, _backend: &DiscoveryBackend) -> Result<(), Self::Error> { if self.should_fail_connect { - Err(PingoraLnError::new( - crate::error::PingoraLnErrorSourceKind::PoolError( - LnPoolError::from_invalid_configuration( - "Mock LnClientPool forced connect error", - ServiceErrorSource::Internal, - "get_offer_invoice", - ), - ), + Err(PingoraLnError::from_io_err( ServiceErrorSource::Upstream, "Mock LnClientPool forced connect error", + io::Error::from(io::ErrorKind::Other), )) } else { Ok(()) @@ -286,17 +213,7 @@ mod tests { partitions: [partition.to_string()].into(), weight, enabled, - implementation: DiscoveryBackendImplementation::LndGrpc( - LndGrpcDiscoveryBackendImplementation { - url: "https://localhost:9736".parse().unwrap(), - domain: None, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: None, - macaroon_path: "/path/to/macaroon_path".into(), - }), - amp_invoice: false, - }, - ), + implementation: "{}".as_bytes().to_vec(), }, } } @@ -309,7 +226,7 @@ mod tests { let mock_ln_client_pool = MockLnClientPool { should_fail_connect: false, }; - let discovery = DefaultPingoraLnDiscovery::new( + let discovery = LnServiceDiscovery::new( mock_backend_provider, mock_ln_client_pool, HashSet::from(["default".to_string()]), @@ -335,7 +252,7 @@ mod tests { let mock_ln_client_pool = MockLnClientPool { should_fail_connect: false, }; - let discovery = DefaultPingoraLnDiscovery::new( + let discovery = LnServiceDiscovery::new( mock_backend_provider, mock_ln_client_pool, HashSet::from(["default".to_string()]), @@ -375,7 +292,7 @@ mod tests { let mock_ln_client_pool = MockLnClientPool { should_fail_connect: false, }; - let discovery = DefaultPingoraLnDiscovery::new( + let discovery = LnServiceDiscovery::new( mock_backend_provider, mock_ln_client_pool, HashSet::from(["default".to_string()]), @@ -402,7 +319,7 @@ mod tests { let mock_ln_client_pool = MockLnClientPool { should_fail_connect: true, }; - let discovery = DefaultPingoraLnDiscovery::new( + let discovery = LnServiceDiscovery::new( mock_backend_provider, mock_ln_client_pool, HashSet::from(["default".to_string()]), @@ -435,7 +352,7 @@ mod tests { } #[async_trait] - impl LnClientPool for SelectiveMockLnClientPool { + impl PingoraLnClientPool for SelectiveMockLnClientPool { type Error = PingoraLnError; type Key = Backend; @@ -449,7 +366,7 @@ mod tests { unimplemented!("get_invoice not implemented for SelectiveMockLnClientPool") } - async fn get_metrics(&self, _key: &Self::Key) -> Result { + async fn get_metrics(&self, _key: &Self::Key) -> Result { unimplemented!("get_metrics not implemented for SelectiveMockLnClientPool") } @@ -464,16 +381,10 @@ mod tests { .iter() .any(|fail_addr| addr_str.as_str() == fail_addr.as_str()) { - Err(PingoraLnError::new( - crate::error::PingoraLnErrorSourceKind::PoolError( - LnPoolError::from_invalid_configuration( - format!("Forced failure for address: {addr_str}"), - ServiceErrorSource::Upstream, - "get_metrics".to_string(), - ), - ), + Err(PingoraLnError::from_io_err( ServiceErrorSource::Upstream, "Selective mock pool forced connect error", + io::Error::from(io::ErrorKind::Other), )) } else { Ok(()) @@ -485,7 +396,7 @@ mod tests { fail_addresses: vec![backend2.public_key.to_string()], }; - let discovery = DefaultPingoraLnDiscovery::new( + let discovery = LnServiceDiscovery::new( mock_backend_provider, mock_ln_client_pool, HashSet::from(["default".to_string()]), diff --git a/pingora/src/error.rs b/pingora/src/error.rs index 9165d46..26e9906 100644 --- a/pingora/src/error.rs +++ b/pingora/src/error.rs @@ -1,16 +1,12 @@ use std::borrow::Cow; use std::fmt::{Display, Formatter}; -use switchgear_service::api::service::{HasServiceErrorSource, ServiceErrorSource}; -use switchgear_service::components::discovery::error::DiscoveryBackendStoreError; -use switchgear_service::components::pool::error::LnPoolError; +use switchgear_service_api::service::{HasServiceErrorSource, ServiceErrorSource}; use thiserror::Error; #[derive(Error, Debug)] pub enum PingoraLnErrorSourceKind { #[error("{0}")] - PoolError(LnPoolError), - #[error("{0}")] - DiscoveryBackendStoreError(DiscoveryBackendStoreError), + Error(String), #[error("no available lightning nodes")] NoAvailableNodes, #[error("{0}")] @@ -49,49 +45,37 @@ impl PingoraLnError { } } - pub fn from_pool_err>>( - esource: ServiceErrorSource, - context: C, - error: LnPoolError, - ) -> Self { - Self { - context: context.into(), - source: PingoraLnErrorSourceKind::PoolError(error), - esource, - } - } - - pub fn from_discovery_backend_store_err>>( + pub fn no_available_nodes>>( esource: ServiceErrorSource, context: C, - error: DiscoveryBackendStoreError, ) -> Self { Self { context: context.into(), - source: PingoraLnErrorSourceKind::DiscoveryBackendStoreError(error), + source: PingoraLnErrorSourceKind::NoAvailableNodes, esource, } } - pub fn no_available_nodes>>( + pub fn from_io_err>>( esource: ServiceErrorSource, context: C, + error: std::io::Error, ) -> Self { Self { context: context.into(), - source: PingoraLnErrorSourceKind::NoAvailableNodes, + source: PingoraLnErrorSourceKind::IoError(error), esource, } } - pub fn from_io_err>>( + pub fn general_error>>( esource: ServiceErrorSource, context: C, - error: std::io::Error, + error: String, ) -> Self { Self { context: context.into(), - source: PingoraLnErrorSourceKind::IoError(error), + source: PingoraLnErrorSourceKind::Error(error), esource, } } diff --git a/pingora/src/health.rs b/pingora/src/health.rs index 262a97c..4b18517 100644 --- a/pingora/src/health.rs +++ b/pingora/src/health.rs @@ -1,8 +1,8 @@ +use crate::PingoraLnClientPool; use async_trait::async_trait; use pingora_error::ErrorType; use pingora_load_balancing::health_check::HealthCheck; use pingora_load_balancing::Backend; -use switchgear_service::components::pool::LnClientPool; pub struct PingoraLnHealthCheck

{ pool: P, @@ -23,8 +23,8 @@ impl

PingoraLnHealthCheck

{ #[async_trait] impl

HealthCheck for PingoraLnHealthCheck

where - P: LnClientPool + Send + Sync, - P::Error: switchgear_service::api::service::HasServiceErrorSource, + P: PingoraLnClientPool + Send + Sync, + P::Error: switchgear_service_api::service::HasServiceErrorSource, { async fn check(&self, target: &Backend) -> pingora_error::Result<()> { let metrics = self.pool.get_metrics(target).await.map_err(|e| { @@ -55,13 +55,13 @@ where mod tests { use super::*; use crate::error::PingoraLnError; + use crate::PingoraLnMetrics; use pingora_core::protocols::l4::socket::SocketAddr; + use std::io; use std::net::SocketAddr as StdSocketAddr; - use switchgear_service::api::discovery::DiscoveryBackend; - use switchgear_service::api::offer::Offer; - use switchgear_service::api::service::ServiceErrorSource; - use switchgear_service::components::pool::error::LnPoolError; - use switchgear_service::components::pool::LnMetrics; + use switchgear_service_api::discovery::DiscoveryBackend; + use switchgear_service_api::offer::Offer; + use switchgear_service_api::service::ServiceErrorSource; struct MockPingoraLnClientPool { should_be_healthy: bool, @@ -69,7 +69,7 @@ mod tests { } #[async_trait] - impl LnClientPool for MockPingoraLnClientPool { + impl PingoraLnClientPool for MockPingoraLnClientPool { type Error = PingoraLnError; type Key = Backend; @@ -83,19 +83,15 @@ mod tests { unimplemented!("get_invoice is not used in health check tests") } - async fn get_metrics(&self, _key: &Self::Key) -> Result { + async fn get_metrics(&self, _key: &Self::Key) -> Result { if self.return_error { - Err(PingoraLnError::from_pool_err( + Err(PingoraLnError::from_io_err( ServiceErrorSource::Upstream, - "metrics failure", - LnPoolError::from_invalid_configuration( - "unknown error", - ServiceErrorSource::Upstream, - "get_metrics", - ), + "get_metrics", + io::Error::from(io::ErrorKind::Other), )) } else { - Ok(LnMetrics { + Ok(PingoraLnMetrics { healthy: self.should_be_healthy, node_effective_inbound_msat: 0, }) diff --git a/pingora/src/lib.rs b/pingora/src/lib.rs index 0c85a11..99222e8 100644 --- a/pingora/src/lib.rs +++ b/pingora/src/lib.rs @@ -1,8 +1,10 @@ use async_trait::async_trait; use std::collections::BTreeSet; use std::error::Error; -use switchgear_service::api::discovery::DiscoveryBackends; +use switchgear_service_api::discovery::{DiscoveryBackend, DiscoveryBackends}; +use switchgear_service_api::offer::Offer; +pub mod backoff; pub mod balance; pub mod discovery; pub mod error; @@ -19,3 +21,33 @@ pub trait PingoraBackendProvider { async fn backends(&self, etag: Option) -> Result; } + +#[async_trait] +pub trait PingoraLnClientPool { + type Error: Error + Send + Sync + 'static; + type Key: std::hash::Hash + Eq + Send + Sync + 'static; + + async fn get_invoice( + &self, + offer: &Offer, + key: &Self::Key, + amount_msat: Option, + expiry_secs: Option, + ) -> Result; + + async fn get_metrics(&self, key: &Self::Key) -> Result; + + fn connect(&self, key: Self::Key, backend: &DiscoveryBackend) -> Result<(), Self::Error>; +} + +pub trait PingoraLnMetricsCache { + type Key: std::hash::Hash + Eq; + + fn get_cached_metrics(&self, key: &Self::Key) -> Option; +} + +#[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)] +pub struct PingoraLnMetrics { + pub healthy: bool, + pub node_effective_inbound_msat: u64, +} diff --git a/server/Cargo.toml b/server/Cargo.toml index 7d6ccd7..c2159aa 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -40,7 +40,9 @@ signal-hook = "0.3" signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } simplelog = {version = "0.12", features = ["paris"] } strfmt = "0.2" +switchgear-components.workspace = true switchgear-pingora.workspace = true +switchgear-service-api.workspace = true switchgear-service.workspace = true tokio = { version = "1", features = ["full"] } url = { version = "2", features = ["serde"] } @@ -49,7 +51,7 @@ uuid = { version = "1", features = ["v4", "serde"] } [dev-dependencies] chrono = { version = "0.4", features = ["serde"] } jemallocator = "0.5" -lightning-invoice = { version = "0.33", features = ["serde", "std"] } +lightning-invoice = { version = "0.34", features = ["serde", "std"] } rand = "0.8" rcgen = { version = "0.14", default-features = false, features = ["crypto", "aws_lc_rs", "pem"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots-no-provider"] } diff --git a/server/src/commands/discovery/backend.rs b/server/src/commands/discovery/backend.rs index d2f24e2..298f415 100644 --- a/server/src/commands/discovery/backend.rs +++ b/server/src/commands/discovery/backend.rs @@ -8,17 +8,18 @@ use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; use std::time::Duration; use std::{env, fs}; -use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatch, - DiscoveryBackendPatchSparse, DiscoveryBackendSparse, DiscoveryBackendStore, -}; -use switchgear_service::components::discovery::http::HttpDiscoveryBackendStore; -use switchgear_service::components::pool::cln::grpc::config::{ +use switchgear_components::discovery::http::HttpDiscoveryBackendStore; +use switchgear_components::pool::cln::grpc::config::{ ClnGrpcClientAuth, ClnGrpcClientAuthPath, ClnGrpcDiscoveryBackendImplementation, }; -use switchgear_service::components::pool::lnd::grpc::config::{ +use switchgear_components::pool::lnd::grpc::config::{ LndGrpcClientAuth, LndGrpcClientAuthPath, LndGrpcDiscoveryBackendImplementation, }; +use switchgear_components::pool::DiscoveryBackendImplementation; +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, + DiscoveryBackendStore, +}; use url::Url; #[derive(Parser, Debug)] @@ -194,7 +195,7 @@ pub fn new_backend( partitions: [partition.to_string()].into(), weight: 1, enabled: false, - implementation, + implementation: serde_json::to_vec(&implementation)?, }, }; let backend = serde_json::to_string_pretty(&backend).with_context(|| "serializing backend")?; diff --git a/server/src/commands/offer/metadata.rs b/server/src/commands/offer/metadata.rs index 1b4e177..20f5960 100644 --- a/server/src/commands/offer/metadata.rs +++ b/server/src/commands/offer/metadata.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Context}; use clap::Parser; use log::info; use std::path::{Path, PathBuf}; -use switchgear_service::api::offer::{OfferMetadata, OfferMetadataSparse, OfferMetadataStore}; +use switchgear_service_api::offer::{OfferMetadata, OfferMetadataSparse, OfferMetadataStore}; use uuid::Uuid; #[derive(Parser, Debug)] diff --git a/server/src/commands/offer/mod.rs b/server/src/commands/offer/mod.rs index 539e42a..b466143 100644 --- a/server/src/commands/offer/mod.rs +++ b/server/src/commands/offer/mod.rs @@ -8,7 +8,7 @@ use rustls::pki_types::CertificateDer; use std::path::PathBuf; use std::time::Duration; use std::{env, fs}; -use switchgear_service::components::offer::http::HttpOfferStore; +use switchgear_components::offer::http::HttpOfferStore; use url::Url; pub mod metadata; diff --git a/server/src/commands/offer/record.rs b/server/src/commands/offer/record.rs index ec13719..8307005 100644 --- a/server/src/commands/offer/record.rs +++ b/server/src/commands/offer/record.rs @@ -5,7 +5,7 @@ use chrono::{DateTime, Utc}; use clap::Parser; use log::info; use std::path::{Path, PathBuf}; -use switchgear_service::api::offer::{OfferRecord, OfferRecordSparse, OfferStore}; +use switchgear_service_api::offer::{OfferRecord, OfferRecordSparse, OfferStore}; use uuid::Uuid; #[derive(Parser, Debug)] diff --git a/server/src/di/delegates.rs b/server/src/di/delegates.rs index b812759..29e7109 100644 --- a/server/src/di/delegates.rs +++ b/server/src/di/delegates.rs @@ -5,41 +5,119 @@ use crate::di::macros::{ use anyhow::Result; use async_trait::async_trait; use pingora_load_balancing::selection::{Consistent, Random, RoundRobin}; +use pingora_load_balancing::Backend; use secp256k1::PublicKey; +use switchgear_components::discovery::db::DbDiscoveryBackendStore; +use switchgear_components::discovery::error::DiscoveryBackendStoreError; +use switchgear_components::discovery::http::HttpDiscoveryBackendStore; +use switchgear_components::discovery::memory::MemoryDiscoveryBackendStore; +use switchgear_components::offer::db::DbOfferStore; +use switchgear_components::offer::error::OfferStoreError; +use switchgear_components::offer::http::HttpOfferStore; +use switchgear_components::offer::memory::MemoryOfferStore; +use switchgear_components::pool::LnClientPool; +use switchgear_pingora::backoff::{ + BackoffInstance, BackoffProvider, ExponentialBackoffProvider, StopBackoffProvider, +}; use switchgear_pingora::balance::{ ConsistentMaxIterations, RandomMaxIterations, RoundRobinMaxIterations, }; use switchgear_pingora::error::PingoraLnError; -use switchgear_pingora::PingoraBackendProvider; -use switchgear_service::api::balance::{LnBalancer, LnBalancerBackgroundServices}; -use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, +use switchgear_pingora::{ + PingoraBackendProvider, PingoraLnClientPool, PingoraLnMetrics, PingoraLnMetricsCache, }; -use switchgear_service::api::offer::Offer; -use switchgear_service::api::offer::{OfferMetadataStore, OfferProvider, OfferStore}; -use switchgear_service::components::backoff::{ - BackoffInstance, BackoffProvider, ExponentialBackoffProvider, StopBackoffProvider, +use switchgear_service_api::balance::{LnBalancer, LnBalancerBackgroundServices}; +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, }; -use switchgear_service::components::discovery::db::DbDiscoveryBackendStore; -use switchgear_service::components::discovery::error::DiscoveryBackendStoreError; -use switchgear_service::components::discovery::http::HttpDiscoveryBackendStore; -use switchgear_service::components::discovery::memory::MemoryDiscoveryBackendStore; -use switchgear_service::components::offer::db::DbOfferStore; -use switchgear_service::components::offer::error::OfferStoreError; -use switchgear_service::components::offer::http::HttpOfferStore; -use switchgear_service::components::offer::memory::MemoryOfferStore; +use switchgear_service_api::offer::Offer; +use switchgear_service_api::offer::{OfferMetadataStore, OfferProvider, OfferStore}; +use switchgear_service_api::service::ServiceErrorSource; use tokio::sync::watch; use uuid::Uuid; // ===== TYPE ALIASES ===== type Balancer = switchgear_pingora::balance::PingoraLnBalancer< T, - super::Pool, - super::Pool, + LnClientPoolDelegate, + LnClientPoolDelegate, BackoffProviderDelegate, X, >; +#[derive(Clone)] +pub enum LnClientPoolDelegate { + Default(LnClientPool), +} + +#[async_trait] +impl PingoraLnClientPool for LnClientPoolDelegate { + type Error = PingoraLnError; + type Key = Backend; + + async fn get_invoice( + &self, + offer: &Offer, + key: &Self::Key, + amount_msat: Option, + expiry_secs: Option, + ) -> std::result::Result { + let LnClientPoolDelegate::Default(delegate) = self; + delegate + .get_invoice(offer, key, amount_msat, expiry_secs) + .await + .map_err(|e| { + PingoraLnError::general_error( + ServiceErrorSource::Upstream, + "get invoice", + e.to_string(), + ) + }) + } + + async fn get_metrics( + &self, + key: &Self::Key, + ) -> std::result::Result { + let LnClientPoolDelegate::Default(delegate) = self; + let metrics = delegate.get_metrics(key).await.map_err(|e| { + PingoraLnError::general_error( + ServiceErrorSource::Upstream, + "get metrics", + e.to_string(), + ) + })?; + Ok(PingoraLnMetrics { + healthy: metrics.healthy, + node_effective_inbound_msat: metrics.node_effective_inbound_msat, + }) + } + + fn connect( + &self, + key: Self::Key, + backend: &DiscoveryBackend, + ) -> std::result::Result<(), Self::Error> { + let LnClientPoolDelegate::Default(delegate) = self; + delegate.connect(key, backend).map_err(|e| { + PingoraLnError::general_error(ServiceErrorSource::Upstream, "connect", e.to_string()) + }) + } +} + +impl PingoraLnMetricsCache for LnClientPoolDelegate { + type Key = Backend; + + fn get_cached_metrics(&self, key: &Self::Key) -> Option { + let LnClientPoolDelegate::Default(delegate) = self; + let metrics = delegate.get_cached_metrics(key); + metrics.map(|m| PingoraLnMetrics { + healthy: m.healthy, + node_effective_inbound_msat: m.node_effective_inbound_msat, + }) + } +} + // ===== LN BALANCER DELEGATE ===== pub enum LnBalancerDelegate { @@ -106,7 +184,7 @@ impl OfferStore for OfferStoreDelegate { &self, partition: &str, id: &Uuid, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { delegate_to_offer_store_variants!(self, get_offer, partition, id).await } @@ -115,20 +193,20 @@ impl OfferStore for OfferStoreDelegate { partition: &str, start: usize, count: usize, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { delegate_to_offer_store_variants!(self, get_offers, partition, start, count).await } async fn post_offer( &self, - offer: switchgear_service::api::offer::OfferRecord, + offer: switchgear_service_api::offer::OfferRecord, ) -> Result, Self::Error> { delegate_to_offer_store_variants!(self, post_offer, offer).await } async fn put_offer( &self, - offer: switchgear_service::api::offer::OfferRecord, + offer: switchgear_service_api::offer::OfferRecord, ) -> Result { delegate_to_offer_store_variants!(self, put_offer, offer).await } @@ -146,7 +224,7 @@ impl OfferMetadataStore for OfferStoreDelegate { &self, partition: &str, id: &Uuid, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { delegate_to_offer_store_variants!(self, get_metadata, partition, id).await } @@ -155,20 +233,20 @@ impl OfferMetadataStore for OfferStoreDelegate { partition: &str, start: usize, count: usize, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { delegate_to_offer_store_variants!(self, get_all_metadata, partition, start, count).await } async fn post_metadata( &self, - metadata: switchgear_service::api::offer::OfferMetadata, + metadata: switchgear_service_api::offer::OfferMetadata, ) -> Result, Self::Error> { delegate_to_offer_store_variants!(self, post_metadata, metadata).await } async fn put_metadata( &self, - metadata: switchgear_service::api::offer::OfferMetadata, + metadata: switchgear_service_api::offer::OfferMetadata, ) -> Result { delegate_to_offer_store_variants!(self, put_metadata, metadata).await } @@ -238,7 +316,25 @@ impl PingoraBackendProvider for DiscoveryBackendStoreDelegate { type Error = PingoraLnError; async fn backends(&self, etag: Option) -> Result { - delegate_to_discovery_store_variants!(self, backends, etag).await + match self { + DiscoveryBackendStoreDelegate::Database(d) => Self::_backends(d.get_all(etag).await), + DiscoveryBackendStoreDelegate::Memory(d) => Self::_backends(d.get_all(etag).await), + DiscoveryBackendStoreDelegate::Http(d) => Self::_backends(d.get_all(etag).await), + } + } +} + +impl DiscoveryBackendStoreDelegate { + fn _backends( + backends_result: Result, + ) -> Result { + backends_result.map_err(|e| { + PingoraLnError::general_error( + ServiceErrorSource::Upstream, + "getting all discovery backends", + e.to_string(), + ) + }) } } diff --git a/server/src/di/inject/injectors/balance.rs b/server/src/di/inject/injectors/balance.rs index a847817..0d552ff 100644 --- a/server/src/di/inject/injectors/balance.rs +++ b/server/src/di/inject/injectors/balance.rs @@ -1,5 +1,5 @@ use crate::config::{BackendSelectionConfig, BackoffConfig, LnUrlBalancerServiceConfig}; -use crate::di::delegates::{BackoffProviderDelegate, LnBalancerDelegate}; +use crate::di::delegates::{BackoffProviderDelegate, LnBalancerDelegate, LnClientPoolDelegate}; use crate::di::inject::injectors::config::{ServerConfigInjector, ServiceEnablementInjector}; use crate::di::inject::injectors::store::discovery::DiscoveryStoreInjector; use anyhow::{anyhow, Context}; @@ -12,13 +12,13 @@ use std::cell::RefCell; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; +use switchgear_components::pool::LnClientPool; +use switchgear_pingora::backoff::{ExponentialBackoffProvider, StopBackoffProvider}; use switchgear_pingora::balance::{ ConsistentMaxIterations, PingoraLnBalancer, RandomMaxIterations, RoundRobinMaxIterations, }; -use switchgear_pingora::discovery::DefaultPingoraLnDiscovery; +use switchgear_pingora::discovery::LnServiceDiscovery; use switchgear_pingora::health::PingoraLnHealthCheck; -use switchgear_service::components::backoff::{ExponentialBackoffProvider, StopBackoffProvider}; -use switchgear_service::components::pool::default_pool::DefaultLnClientPool; #[derive(Clone)] pub struct BalancerInjector { @@ -105,16 +105,15 @@ impl BalancerInjector { vec![] }; - let pool = DefaultLnClientPool::new( + let pool = LnClientPool::new( Duration::from_secs_f64(lnurl_config.ln_client_timeout_secs), trusted_roots, ); - let discovery = DefaultPingoraLnDiscovery::new( - discovery, - pool.clone(), - lnurl_config.partitions.clone(), - ); + let pool = LnClientPoolDelegate::Default(pool); + + let discovery = + LnServiceDiscovery::new(discovery, pool.clone(), lnurl_config.partitions.clone()); let health = PingoraLnHealthCheck::new( pool.clone(), diff --git a/server/src/di/inject/injectors/service/balance.rs b/server/src/di/inject/injectors/service/balance.rs index ed20858..48c1d7d 100644 --- a/server/src/di/inject/injectors/service/balance.rs +++ b/server/src/di/inject/injectors/service/balance.rs @@ -7,7 +7,7 @@ use log::{info, warn}; use std::future::Future; use std::net::{SocketAddr, TcpListener}; use std::pin::Pin; -use switchgear_service::components::axum::middleware::logger::ClfLogger; +use switchgear_components::axum::middleware::logger::ClfLogger; use switchgear_service::scheme::Scheme; use switchgear_service::{LnUrlBalancerService, LnUrlPayState}; diff --git a/server/src/di/inject/injectors/service/balance_background.rs b/server/src/di/inject/injectors/service/balance_background.rs index cba39bc..cc73c0b 100644 --- a/server/src/di/inject/injectors/service/balance_background.rs +++ b/server/src/di/inject/injectors/service/balance_background.rs @@ -4,7 +4,7 @@ use anyhow::anyhow; use std::future::Future; use std::pin::Pin; -use switchgear_service::api::balance::LnBalancerBackgroundServices; +use switchgear_service_api::balance::LnBalancerBackgroundServices; use tokio::sync::watch; pub struct BackgroundBalancerServiceInjector { diff --git a/server/src/di/inject/injectors/service/discovery.rs b/server/src/di/inject/injectors/service/discovery.rs index 85c26c9..5887af1 100644 --- a/server/src/di/inject/injectors/service/discovery.rs +++ b/server/src/di/inject/injectors/service/discovery.rs @@ -7,7 +7,7 @@ use log::{info, warn}; use std::future::Future; use std::net::{SocketAddr, TcpListener}; use std::pin::Pin; -use switchgear_service::components::axum::middleware::logger::ClfLogger; +use switchgear_components::axum::middleware::logger::ClfLogger; use switchgear_service::{DiscoveryService, DiscoveryState}; pub struct DiscoveryServiceInjector { diff --git a/server/src/di/inject/injectors/service/offer.rs b/server/src/di/inject/injectors/service/offer.rs index ee268fc..18b2943 100644 --- a/server/src/di/inject/injectors/service/offer.rs +++ b/server/src/di/inject/injectors/service/offer.rs @@ -8,7 +8,7 @@ use log::{info, warn}; use std::future::Future; use std::net::{SocketAddr, TcpListener}; use std::pin::Pin; -use switchgear_service::components::axum::middleware::logger::ClfLogger; +use switchgear_components::axum::middleware::logger::ClfLogger; use switchgear_service::{OfferService, OfferState}; pub struct OfferServiceInjector { diff --git a/server/src/di/inject/injectors/store/discovery.rs b/server/src/di/inject/injectors/store/discovery.rs index fb0e6a9..5c6198b 100644 --- a/server/src/di/inject/injectors/store/discovery.rs +++ b/server/src/di/inject/injectors/store/discovery.rs @@ -7,9 +7,9 @@ use std::cell::RefCell; use std::rc::Rc; use std::str::from_utf8; use std::time::Duration; -use switchgear_service::components::discovery::db::DbDiscoveryBackendStore; -use switchgear_service::components::discovery::http::HttpDiscoveryBackendStore; -use switchgear_service::components::discovery::memory::MemoryDiscoveryBackendStore; +use switchgear_components::discovery::db::DbDiscoveryBackendStore; +use switchgear_components::discovery::http::HttpDiscoveryBackendStore; +use switchgear_components::discovery::memory::MemoryDiscoveryBackendStore; #[derive(Clone)] pub struct DiscoveryStoreInjector { diff --git a/server/src/di/inject/injectors/store/offer.rs b/server/src/di/inject/injectors/store/offer.rs index 23b9078..56235a2 100644 --- a/server/src/di/inject/injectors/store/offer.rs +++ b/server/src/di/inject/injectors/store/offer.rs @@ -7,9 +7,9 @@ use std::cell::RefCell; use std::rc::Rc; use std::str::from_utf8; use std::time::Duration; -use switchgear_service::components::offer::db::DbOfferStore; -use switchgear_service::components::offer::http::HttpOfferStore; -use switchgear_service::components::offer::memory::MemoryOfferStore; +use switchgear_components::offer::db::DbOfferStore; +use switchgear_components::offer::http::HttpOfferStore; +use switchgear_components::offer::memory::MemoryOfferStore; #[derive(Clone)] pub struct OfferStoreInjector { diff --git a/server/src/di/mod.rs b/server/src/di/mod.rs index deea062..ad5eb33 100644 --- a/server/src/di/mod.rs +++ b/server/src/di/mod.rs @@ -1,9 +1,3 @@ -use switchgear_service::components::pool::default_pool::DefaultLnClientPool; - pub mod delegates; pub mod inject; pub mod macros; - -// ===== TYPE ALIASES ===== - -type Pool = DefaultLnClientPool; diff --git a/server/tests/features/backend_create_delete.rs b/server/tests/features/backend_create_delete.rs index 82beb28..ad3e31c 100644 --- a/server/tests/features/backend_create_delete.rs +++ b/server/tests/features/backend_create_delete.rs @@ -9,10 +9,7 @@ use std::time::Duration; async fn test_complete_backend_lifecycle_management() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); diff --git a/server/tests/features/backend_enable_disable.rs b/server/tests/features/backend_enable_disable.rs index de8a4ba..cd094e9 100644 --- a/server/tests/features/backend_enable_disable.rs +++ b/server/tests/features/backend_enable_disable.rs @@ -9,10 +9,7 @@ use std::time::Duration; async fn test_complete_backend_lifecycle_management() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); diff --git a/server/tests/features/cli_discovery_manage.rs b/server/tests/features/cli_discovery_manage.rs index 8ecce13..efa1c91 100644 --- a/server/tests/features/cli_discovery_manage.rs +++ b/server/tests/features/cli_discovery_manage.rs @@ -83,10 +83,7 @@ async fn test_discovery_new_with_output() { async fn test_discovery_post() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -141,10 +138,7 @@ async fn test_discovery_post() { async fn test_discovery_post_conflict() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -210,10 +204,7 @@ async fn test_discovery_post_conflict() { async fn test_discovery_ls() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -287,11 +278,7 @@ async fn test_discovery_get() { for root_location in root_locations { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; - + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -363,10 +350,7 @@ async fn test_discovery_get() { async fn test_discovery_get_all() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -433,10 +417,7 @@ async fn test_discovery_get_all() { async fn test_discovery_put() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -517,10 +498,7 @@ async fn test_discovery_put() { async fn test_discovery_delete() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -598,10 +576,7 @@ async fn test_discovery_delete() { async fn test_discovery_patch() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -682,10 +657,7 @@ async fn test_discovery_patch() { async fn test_discovery_enable() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -769,10 +741,7 @@ async fn test_discovery_enable() { async fn test_discovery_disable() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -850,10 +819,7 @@ async fn test_discovery_disable() { async fn test_discovery_get_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -908,10 +874,7 @@ async fn test_discovery_get_error() { async fn test_discovery_patch_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -971,10 +934,7 @@ async fn test_discovery_patch_error() { async fn test_discovery_enable_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1029,10 +989,7 @@ async fn test_discovery_enable_error() { async fn test_discovery_disable_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1087,10 +1044,7 @@ async fn test_discovery_disable_error() { async fn test_discovery_delete_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); diff --git a/server/tests/features/cli_offer_manage.rs b/server/tests/features/cli_offer_manage.rs index ec491cb..da4725c 100644 --- a/server/tests/features/cli_offer_manage.rs +++ b/server/tests/features/cli_offer_manage.rs @@ -59,10 +59,7 @@ async fn test_offer_new_with_output() { async fn test_offer_post() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -125,10 +122,7 @@ async fn test_offer_get() { for certificate_location in certificate_locations { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -201,10 +195,7 @@ async fn test_offer_get() { async fn test_offer_get_all() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -296,10 +287,7 @@ async fn test_offer_get_all() { async fn test_offer_get_all_bounds_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -367,10 +355,7 @@ async fn test_offer_get_all_bounds_error() { async fn test_offer_put() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -446,10 +431,7 @@ async fn test_offer_put() { async fn test_offer_delete() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -568,10 +550,7 @@ async fn test_offer_metadata_new_with_output() { async fn test_offer_metadata_post() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -634,10 +613,7 @@ async fn test_offer_metadata_get() { for certificate_location in certificate_locations { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -714,10 +690,7 @@ async fn test_offer_metadata_get() { async fn test_offer_metadata_get_all() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -810,10 +783,7 @@ async fn test_offer_metadata_get_all() { async fn test_offer_metadata_get_all_bounds_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -881,10 +851,7 @@ async fn test_offer_metadata_get_all_bounds_error() { async fn test_offer_metadata_put() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -965,10 +932,7 @@ async fn test_offer_metadata_put() { async fn test_offer_metadata_delete() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1046,10 +1010,7 @@ async fn test_offer_metadata_delete() { async fn test_offer_post_invalid_metadata() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1107,10 +1068,7 @@ async fn test_offer_post_invalid_metadata() { async fn test_offer_metadata_delete_referenced() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1177,10 +1135,7 @@ async fn test_offer_metadata_delete_referenced() { async fn test_offer_get_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1235,10 +1190,7 @@ async fn test_offer_get_error() { async fn test_offer_delete_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1293,10 +1245,7 @@ async fn test_offer_delete_error() { async fn test_offer_post_conflict() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1362,10 +1311,7 @@ async fn test_offer_post_conflict() { async fn test_offer_metadata_get_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -1420,11 +1366,7 @@ async fn test_offer_metadata_get_error() { async fn test_offer_metadata_delete_error() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; - + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -1478,11 +1420,7 @@ async fn test_offer_metadata_delete_error() { async fn test_offer_metadata_post_conflict() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; - + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( diff --git a/server/tests/features/common/client.rs b/server/tests/features/common/client.rs index 9da430d..7e16b85 100644 --- a/server/tests/features/common/client.rs +++ b/server/tests/features/common/client.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Context}; use reqwest::{Certificate, Client, ClientBuilder, Url}; use std::net::SocketAddr; use std::time::Duration; -use switchgear_service::api::lnurl::{LnUrlInvoice, LnUrlOffer}; +use switchgear_service_api::lnurl::{LnUrlInvoice, LnUrlOffer}; use tokio::net::TcpStream; use tokio::time::timeout; use uuid::Uuid; diff --git a/server/tests/features/common/context/global.rs b/server/tests/features/common/context/global.rs index aa374ee..7b2136d 100644 --- a/server/tests/features/common/context/global.rs +++ b/server/tests/features/common/context/global.rs @@ -9,11 +9,9 @@ use rcgen::{Issuer, KeyPair}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; -use switchgear_service::components::discovery::http::HttpDiscoveryBackendStore; -use switchgear_service::components::offer::http::HttpOfferStore; -use switchgear_testing::credentials::lightning::{ - ClnRegTestLnNode, LnCredentials, LndRegTestLnNode, RegTestLnNode, -}; +use switchgear_components::discovery::http::HttpDiscoveryBackendStore; +use switchgear_components::offer::http::HttpOfferStore; +use switchgear_testing::credentials::lightning::{LnCredentials, RegTestLnNodes}; use tempfile::TempDir; pub struct GlobalContext { @@ -25,21 +23,18 @@ pub struct GlobalContext { pki_root_certificate_path: PathBuf, pki_root_cn: String, pki_root_issuer: Issuer<'static, KeyPair>, - ln_nodes: Vec, + ln_nodes: RegTestLnNodes, _credentials: LnCredentials, } impl GlobalContext { - pub fn create(feature_test_config_path: &Path) -> anyhow::Result> { + pub fn create(feature_test_config_path: &Path) -> anyhow::Result { let _ = rustls::crypto::aws_lc_rs::default_provider() .install_default() .map_err(|_| anyhow!("failed to stand up rustls encryption platform")); let credentials = LnCredentials::create()?; let ln_nodes = credentials.get_backends()?; - if ln_nodes.is_empty() { - return Ok(None); - } let feature_test_config_path = Self::load_test_config(feature_test_config_path)?; let temp_dir = TempDir::new()?; @@ -48,7 +43,7 @@ impl GlobalContext { let (pki_root_cn, pki_root_issuer, pki_root_certificate_path) = gen_root_cert(pki_dir.as_path())?; - Ok(Some(Self { + Ok(Self { temp_dir, servers: HashMap::new(), active_server: "".to_string(), @@ -59,29 +54,11 @@ impl GlobalContext { pki_root_issuer, ln_nodes, _credentials: credentials, - })) - } - - pub fn get_first_cln_node(&self) -> anyhow::Result<&ClnRegTestLnNode> { - self.ln_nodes - .iter() - .filter_map(|n| match n { - RegTestLnNode::Cln(cln) => Some(cln), - RegTestLnNode::Lnd(_) => None, - }) - .next() - .ok_or_else(|| anyhow!("no cln node")) + }) } - pub fn get_first_lnd_node(&self) -> anyhow::Result<&LndRegTestLnNode> { - self.ln_nodes - .iter() - .filter_map(|n| match n { - RegTestLnNode::Cln(_) => None, - RegTestLnNode::Lnd(lnd) => Some(lnd), - }) - .next() - .ok_or_else(|| anyhow!("no lnd node")) + pub fn get_ln_nodes(&self) -> &RegTestLnNodes { + &self.ln_nodes } fn load_test_config(config_path: &Path) -> anyhow::Result { @@ -94,9 +71,11 @@ impl GlobalContext { self.active_server = server.to_string(); } - pub fn add_payee(&mut self, payee_id: &str, node: RegTestLnNode) { - self.payees - .insert(payee_id.to_string(), PayeeContext::new(node)); + pub fn add_payee(&mut self, payee_id: &str, nodes: RegTestLnNodes, target_ln_node: &str) { + self.payees.insert( + payee_id.to_string(), + PayeeContext::new(nodes, target_ln_node.to_string()), + ); } pub fn get_payee(&self, payee_id: &str) -> Option<&PayeeContext> { diff --git a/server/tests/features/common/context/pay.rs b/server/tests/features/common/context/pay.rs index 74668f4..794fe0a 100644 --- a/server/tests/features/common/context/pay.rs +++ b/server/tests/features/common/context/pay.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use switchgear_service::api::lnurl::LnUrlOffer; -use switchgear_testing::credentials::lightning::RegTestLnNode; +use switchgear_service_api::lnurl::LnUrlOffer; +use switchgear_testing::credentials::lightning::RegTestLnNodes; use uuid::Uuid; #[derive(Clone)] @@ -30,14 +30,16 @@ impl OfferRequest { #[derive(Clone)] pub struct PayeeContext { - pub node: RegTestLnNode, + pub ln_nodes: RegTestLnNodes, + pub target_ln_node: String, pub offer_requests: HashMap, } impl PayeeContext { - pub fn new(node: RegTestLnNode) -> Self { + pub fn new(node: RegTestLnNodes, target_ln_node: String) -> Self { Self { - node, + ln_nodes: node, + target_ln_node, offer_requests: HashMap::new(), } } diff --git a/server/tests/features/common/context/server.rs b/server/tests/features/common/context/server.rs index dd70472..a41be99 100644 --- a/server/tests/features/common/context/server.rs +++ b/server/tests/features/common/context/server.rs @@ -14,9 +14,9 @@ use std::path::{Path, PathBuf}; use std::process::{Child, Command}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use switchgear_components::discovery::http::HttpDiscoveryBackendStore; +use switchgear_components::offer::http::HttpOfferStore; use switchgear_server::config::TlsConfig; -use switchgear_service::components::discovery::http::HttpDiscoveryBackendStore; -use switchgear_service::components::offer::http::HttpOfferStore; use uuid::Uuid; #[derive(Debug, Clone)] diff --git a/server/tests/features/common/helpers.rs b/server/tests/features/common/helpers.rs index c8313c6..4efd2ed 100644 --- a/server/tests/features/common/helpers.rs +++ b/server/tests/features/common/helpers.rs @@ -6,9 +6,9 @@ use anyhow::Result; use lightning_invoice::Bolt11Invoice; use std::fmt::Display; use std::str::FromStr; -use switchgear_service::api::discovery::HttpDiscoveryBackendClient; -use switchgear_service::api::lnurl::LnUrlOffer; -use switchgear_service::api::offer::HttpOfferClient; +use switchgear_service_api::discovery::HttpDiscoveryBackendClient; +use switchgear_service_api::lnurl::LnUrlOffer; +use switchgear_service_api::offer::HttpOfferClient; use uuid::Uuid; fn get_required_with_error(option: Option, error_msg: impl FnOnce() -> String) -> Result { diff --git a/server/tests/features/common/step_functions.rs b/server/tests/features/common/step_functions.rs index 076afb0..c19e156 100644 --- a/server/tests/features/common/step_functions.rs +++ b/server/tests/features/common/step_functions.rs @@ -11,31 +11,30 @@ use crate::common::helpers::{ verify_exit_code, verify_single_service_status, }; use crate::{anyhow_log, bail_log}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use chrono::{Duration as ChronoDuration, Utc}; use rand::{distributions::Alphanumeric, Rng}; use reqwest::{StatusCode, Url}; use secp256k1::PublicKey; use std::time::{Duration, SystemTime}; use std::vec; -use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatch, - DiscoveryBackendPatchSparse, DiscoveryBackendSparse, DiscoveryBackendStore, -}; -use switchgear_service::api::offer::{ - OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, OfferRecordSparse, - OfferStore, -}; -use switchgear_service::components::pool::cln::grpc::config::{ +use switchgear_components::pool::cln::grpc::config::{ ClnGrpcClientAuth, ClnGrpcClientAuthPath, ClnGrpcDiscoveryBackendImplementation, }; -use switchgear_service::components::pool::lnd::grpc::config::{ +use switchgear_components::pool::lnd::grpc::config::{ LndGrpcClientAuth, LndGrpcClientAuthPath, LndGrpcDiscoveryBackendImplementation, }; -use switchgear_testing::credentials::lightning::{RegTestLnNode, RegTestLnNodeType}; +use switchgear_components::pool::DiscoveryBackendImplementation; +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, + DiscoveryBackendStore, +}; +use switchgear_service_api::offer::{ + OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, OfferRecordSparse, + OfferStore, +}; use tokio::time::sleep as tokio_sleep; use uuid::Uuid; - // ============================================================================= // STEP FUNCTIONS - Mapped to Gherkin steps in feature files // ============================================================================= @@ -326,13 +325,9 @@ pub async fn step_then_no_error_logs_should_be_present(ctx: &mut GlobalContext) /// Sets up the specific lightning node backend type for the test pub async fn step_given_the_payee_has_a_lightning_node_available( ctx: &mut GlobalContext, - backend_type: RegTestLnNodeType, + node_target: &str, ) -> Result<()> { - let node = match backend_type { - RegTestLnNodeType::Cln => RegTestLnNode::Cln(ctx.get_first_cln_node()?.clone()), - RegTestLnNodeType::Lnd => RegTestLnNode::Lnd(ctx.get_first_lnd_node()?.clone()), - }; - ctx.add_payee("single", node.clone()); + ctx.add_payee("single", ctx.get_ln_nodes().clone(), node_target); Ok(()) } @@ -344,7 +339,6 @@ pub async fn step_when_the_payee_creates_an_offer_for_their_lightning_node( ) -> Result<()> { // Use the selected node from context let payee = get_payee_from_context(ctx, payee_id)?; - let node = payee.node.clone(); let client = ctx.get_active_offer_client()?; @@ -378,9 +372,10 @@ pub async fn step_when_the_payee_creates_an_offer_for_their_lightning_node( // Create offer record with different limits based on node type let offer_id = Uuid::new_v4(); - let (max_sendable, min_sendable) = match node { - RegTestLnNode::Cln(_) => (1_000_000_000, 1_000), - RegTestLnNode::Lnd(_) => (2_000_000_000, 1_000), + let (max_sendable, min_sendable) = match payee.target_ln_node.as_str() { + "cln" => (1_000_000_000, 1_000), + "lnd" => (1_000_000_000, 1_000), + _ => bail!("Invalid target ln_node"), }; let now = Utc::now(); @@ -424,55 +419,54 @@ pub async fn step_when_the_payee_registers_their_lightning_node_as_a_backend( payee_id: &str, include_ca: bool, ) -> Result<()> { - // Use the selected node from context let payee = get_payee_from_context(ctx, payee_id)?; - let node = payee.node.clone(); let client = ctx.get_active_discovery_client()?; - let url = Url::parse(&format!("https://{}", node.address()))?; - - let implementation = match &node { - RegTestLnNode::Cln(cln) => { + let (public_key, implementation) = match payee.target_ln_node.as_str() { + "cln" => ( + payee.ln_nodes.cln.public_key, DiscoveryBackendImplementation::ClnGrpc(ClnGrpcDiscoveryBackendImplementation { - url, + url: format!("https://{}", payee.ln_nodes.cln.address).parse()?, auth: ClnGrpcClientAuth::Path(ClnGrpcClientAuthPath { ca_cert_path: if include_ca { - cln.ca_cert_path.clone().into() + payee.ln_nodes.cln.ca_cert_path.clone().into() } else { None }, - client_cert_path: cln.client_cert_path.clone(), - client_key_path: cln.client_key_path.clone(), + client_cert_path: payee.ln_nodes.cln.client_cert_path.clone(), + client_key_path: payee.ln_nodes.cln.client_key_path.clone(), }), domain: None, - }) - } - RegTestLnNode::Lnd(lnd) => { + }), + ), + "lnd" => ( + payee.ln_nodes.lnd.public_key, DiscoveryBackendImplementation::LndGrpc(LndGrpcDiscoveryBackendImplementation { - url, + url: format!("https://{}", payee.ln_nodes.lnd.address).parse()?, auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { tls_cert_path: if include_ca { - lnd.tls_cert_path.clone().into() + payee.ln_nodes.lnd.tls_cert_path.clone().into() } else { None }, - macaroon_path: lnd.macaroon_path.clone(), + macaroon_path: payee.ln_nodes.lnd.macaroon_path.clone(), }), amp_invoice: false, domain: None, - }) - } + }), + ), + _ => bail!("Invalid target ln_node"), }; let backend = DiscoveryBackend { - public_key: *node.public_key(), + public_key, backend: DiscoveryBackendSparse { name: None, partitions: ["default".to_string()].into(), weight: 100, enabled: true, - implementation, + implementation: serde_json::to_vec(&implementation)?, }, }; @@ -674,12 +668,8 @@ pub async fn step_then_the_invoice_description_hash_should_match_the_metadata_ha pub async fn step_given_two_payees_each_have_their_own_lightning_node( ctx: &mut GlobalContext, ) -> Result<()> { - let cln_node = ctx.get_first_cln_node()?; - ctx.add_payee("first", RegTestLnNode::Cln(cln_node.clone())); - - let lnd_node = ctx.get_first_lnd_node()?; - ctx.add_payee("second", RegTestLnNode::Lnd(lnd_node.clone())); - + ctx.add_payee("first", ctx.get_ln_nodes().clone(), "cln"); + ctx.add_payee("second", ctx.get_ln_nodes().clone(), "lnd"); Ok(()) } @@ -702,44 +692,44 @@ pub async fn step_and_both_nodes_are_registered_as_separate_backends( /// Generic function to register a payee's node as a backend pub async fn register_payee_node_as_backend(ctx: &mut GlobalContext, payee_id: &str) -> Result<()> { let payee = get_payee_from_context(ctx, payee_id)?; - let node = payee.node.clone(); let client = ctx.get_active_discovery_client()?; - let url = Url::parse(&format!("https://{}", node.address()))?; - - let implementation = match &node { - RegTestLnNode::Cln(cln) => { + let (public_key, implementation) = match payee.target_ln_node.as_str() { + "cln" => ( + payee.ln_nodes.cln.public_key, DiscoveryBackendImplementation::ClnGrpc(ClnGrpcDiscoveryBackendImplementation { - url, + url: format!("https://{}", payee.ln_nodes.cln.address).parse()?, auth: ClnGrpcClientAuth::Path(ClnGrpcClientAuthPath { - ca_cert_path: cln.ca_cert_path.clone().into(), - client_cert_path: cln.client_cert_path.clone(), - client_key_path: cln.client_key_path.clone(), + ca_cert_path: payee.ln_nodes.cln.ca_cert_path.clone().into(), + client_cert_path: payee.ln_nodes.cln.client_cert_path.clone(), + client_key_path: payee.ln_nodes.cln.client_key_path.clone(), }), domain: None, - }) - } - RegTestLnNode::Lnd(lnd) => { + }), + ), + "lnd" => ( + payee.ln_nodes.lnd.public_key, DiscoveryBackendImplementation::LndGrpc(LndGrpcDiscoveryBackendImplementation { - url, + url: format!("https://{}", payee.ln_nodes.lnd.address).parse()?, auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: lnd.tls_cert_path.clone().into(), - macaroon_path: lnd.macaroon_path.clone(), + tls_cert_path: payee.ln_nodes.lnd.tls_cert_path.clone().into(), + macaroon_path: payee.ln_nodes.lnd.macaroon_path.clone(), }), amp_invoice: false, domain: None, - }) - } + }), + ), + _ => bail!("Invalid target ln_node"), }; let backend = DiscoveryBackend { - public_key: *node.public_key(), + public_key, backend: DiscoveryBackendSparse { name: None, partitions: ["default".to_string()].into(), weight: 100, - implementation, + implementation: serde_json::to_vec(&implementation)?, enabled: true, }, }; @@ -752,21 +742,11 @@ pub async fn register_payee_node_as_backend(ctx: &mut GlobalContext, payee_id: & /// Generic function to set up two payees with specific node types pub async fn setup_two_payees_with_node_types( ctx: &mut GlobalContext, - first_node_type: RegTestLnNodeType, - second_node_type: RegTestLnNodeType, + first_node_type: &str, + second_node_type: &str, ) -> Result<()> { - let first_node = match first_node_type { - RegTestLnNodeType::Cln => RegTestLnNode::Cln(ctx.get_first_cln_node()?.clone()), - RegTestLnNodeType::Lnd => RegTestLnNode::Lnd(ctx.get_first_lnd_node()?.clone()), - }; - ctx.add_payee("first", first_node); - - let second_node = match second_node_type { - RegTestLnNodeType::Cln => RegTestLnNode::Cln(ctx.get_first_cln_node()?.clone()), - RegTestLnNodeType::Lnd => RegTestLnNode::Lnd(ctx.get_first_lnd_node()?.clone()), - }; - ctx.add_payee("second", second_node); - + ctx.add_payee("first", ctx.get_ln_nodes().clone(), first_node_type); + ctx.add_payee("second", ctx.get_ln_nodes().clone(), second_node_type); Ok(()) } @@ -810,8 +790,8 @@ pub async fn step_but_when_the_payer_requests_an_invoice_for_100_sats_using_the_ pub async fn step_given_the_payee_has_access_to_both_cln_and_lnd_lightning_nodes( ctx: &mut GlobalContext, ) -> Result<()> { - ctx.add_payee("cln", RegTestLnNode::Cln(ctx.get_first_cln_node()?.clone())); - ctx.add_payee("lnd", RegTestLnNode::Lnd(ctx.get_first_lnd_node()?.clone())); + ctx.add_payee("cln", ctx.get_ln_nodes().clone(), "cln"); + ctx.add_payee("lnd", ctx.get_ln_nodes().clone(), "lnd"); Ok(()) } @@ -915,7 +895,7 @@ pub async fn step_when_the_admin_disables_the_first_backend(ctx: &mut GlobalCont .ok_or_else(|| anyhow_log!("LND payee not found in context"))? .clone(); - enable_disable_backend(ctx, lnd_payee.node.public_key(), false).await?; + enable_disable_backend(ctx, &lnd_payee.ln_nodes.lnd.public_key, false).await?; Ok(()) } @@ -942,7 +922,7 @@ pub async fn step_when_the_admin_disables_the_second_backend( .ok_or_else(|| anyhow_log!("CLN payee not found in context"))? .clone(); - enable_disable_backend(ctx, cln_payee.node.public_key(), false).await?; + enable_disable_backend(ctx, &cln_payee.ln_nodes.cln.public_key, false).await?; Ok(()) } @@ -1000,7 +980,7 @@ pub async fn step_when_the_admin_enables_any_backend(ctx: &mut GlobalContext) -> .ok_or_else(|| anyhow_log!("LND payee not found in context"))? .clone(); - enable_disable_backend(ctx, lnd_payee.node.public_key(), true).await?; + enable_disable_backend(ctx, &lnd_payee.ln_nodes.lnd.public_key, true).await?; Ok(()) } @@ -1051,7 +1031,7 @@ pub async fn step_when_the_admin_deletes_the_first_backend(ctx: &mut GlobalConte .ok_or_else(|| anyhow_log!("LND payee not found in context"))? .clone(); - delete_backend(ctx, lnd_payee.node.public_key()).await?; + delete_backend(ctx, &lnd_payee.ln_nodes.lnd.public_key).await?; Ok(()) } @@ -1065,7 +1045,7 @@ pub async fn step_when_the_admin_deletes_the_second_backend(ctx: &mut GlobalCont .ok_or_else(|| anyhow_log!("CLN payee not found in context"))? .clone(); - delete_backend(ctx, cln_payee.node.public_key()).await?; + delete_backend(ctx, &cln_payee.ln_nodes.cln.public_key).await?; Ok(()) } diff --git a/server/tests/features/http_remote_stores.rs b/server/tests/features/http_remote_stores.rs index 7f31ef9..027145a 100644 --- a/server/tests/features/http_remote_stores.rs +++ b/server/tests/features/http_remote_stores.rs @@ -3,7 +3,6 @@ use crate::common::context::Protocol; use crate::common::step_functions::*; use crate::FEATURE_TEST_CONFIG_PATH; use std::path::PathBuf; -use switchgear_testing::credentials::lightning::RegTestLnNodeType; use crate::common::context::server::CertificateLocation; @@ -14,10 +13,7 @@ async fn test_complete_http_remote_stores_workflow_with_distributed_services() { for certificate_location in certificate_locations { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -56,7 +52,7 @@ async fn test_complete_http_remote_stores_workflow_with_distributed_services() { ctx.activate_server(server1); // Background - step_given_the_payee_has_a_lightning_node_available(&mut ctx, RegTestLnNodeType::Cln) + step_given_the_payee_has_a_lightning_node_available(&mut ctx, "cln") .await .expect("assert"); step_given_the_server_is_not_already_running(&mut ctx) diff --git a/server/tests/features/invalid_configuration_rejection.rs b/server/tests/features/invalid_configuration_rejection.rs index eb4fc25..383d0ff 100644 --- a/server/tests/features/invalid_configuration_rejection.rs +++ b/server/tests/features/invalid_configuration_rejection.rs @@ -25,10 +25,7 @@ use std::path::PathBuf; async fn test_configuration_validation_invalid_scenario() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("tests/features/common/config/invalid-config.yaml"); ctx.add_server( diff --git a/server/tests/features/lnurl_pay_invoice_generation.rs b/server/tests/features/lnurl_pay_invoice_generation.rs index 1428389..b4f8e92 100644 --- a/server/tests/features/lnurl_pay_invoice_generation.rs +++ b/server/tests/features/lnurl_pay_invoice_generation.rs @@ -4,8 +4,8 @@ use crate::common::context::Protocol; use crate::common::helpers::get_payee_from_context; use crate::common::step_functions::*; use crate::FEATURE_TEST_CONFIG_PATH; +use anyhow::bail; use std::path::PathBuf; -use switchgear_testing::credentials::lightning::{RegTestLnNode, RegTestLnNodeType}; enum LnTrustRootsLocation { Credentials, @@ -17,7 +17,7 @@ enum LnTrustRootsLocation { async fn test_payer_requests_invoice_from_payee_cln_lightning_offer_using_http_creds() { test_payer_requests_invoice_from_payee_inner( Protocol::Http, - RegTestLnNodeType::Cln, + "cln", LnTrustRootsLocation::Credentials, ) .await @@ -28,7 +28,7 @@ async fn test_payer_requests_invoice_from_payee_cln_lightning_offer_using_http_c async fn test_payer_requests_invoice_from_payee_cln_lightning_offer_using_https_creds() { test_payer_requests_invoice_from_payee_inner( Protocol::Https, - RegTestLnNodeType::Cln, + "cln", LnTrustRootsLocation::Credentials, ) .await @@ -39,7 +39,7 @@ async fn test_payer_requests_invoice_from_payee_cln_lightning_offer_using_https_ async fn test_payer_requests_invoice_from_payee_cln_lightning_offer_using_https_config() { test_payer_requests_invoice_from_payee_inner( Protocol::Https, - RegTestLnNodeType::Cln, + "cln", LnTrustRootsLocation::Configuration, ) .await @@ -50,7 +50,7 @@ async fn test_payer_requests_invoice_from_payee_cln_lightning_offer_using_https_ async fn test_payer_requests_invoice_from_payee_cln_lightning_offer_using_https_native() { test_payer_requests_invoice_from_payee_inner( Protocol::Https, - RegTestLnNodeType::Cln, + "cln", LnTrustRootsLocation::Native, ) .await @@ -61,7 +61,7 @@ async fn test_payer_requests_invoice_from_payee_cln_lightning_offer_using_https_ async fn test_payer_requests_invoice_from_payee_lnd_lightning_offer_using_http_creds() { test_payer_requests_invoice_from_payee_inner( Protocol::Http, - RegTestLnNodeType::Lnd, + "cln", LnTrustRootsLocation::Credentials, ) .await @@ -72,7 +72,7 @@ async fn test_payer_requests_invoice_from_payee_lnd_lightning_offer_using_http_c async fn test_payer_requests_invoice_from_payee_lnd_lightning_offer_using_https_creds() { test_payer_requests_invoice_from_payee_inner( Protocol::Https, - RegTestLnNodeType::Lnd, + "lnd", LnTrustRootsLocation::Credentials, ) .await @@ -83,7 +83,7 @@ async fn test_payer_requests_invoice_from_payee_lnd_lightning_offer_using_https_ async fn test_payer_requests_invoice_from_payee_lnd_lightning_offer_using_https_config() { test_payer_requests_invoice_from_payee_inner( Protocol::Https, - RegTestLnNodeType::Lnd, + "lnd", LnTrustRootsLocation::Configuration, ) .await @@ -94,7 +94,7 @@ async fn test_payer_requests_invoice_from_payee_lnd_lightning_offer_using_https_ async fn test_payer_requests_invoice_from_payee_lnd_lightning_offer_using_https_native() { test_payer_requests_invoice_from_payee_inner( Protocol::Https, - RegTestLnNodeType::Lnd, + "lnd", LnTrustRootsLocation::Native, ) .await @@ -103,15 +103,12 @@ async fn test_payer_requests_invoice_from_payee_lnd_lightning_offer_using_https_ async fn test_payer_requests_invoice_from_payee_inner( protocol: Protocol, - node_type: RegTestLnNodeType, + node_type: &str, ln_trusted_roots_location: LnTrustRootsLocation, -) -> Result<(), Box> { +) -> Result<(), anyhow::Error> { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path)? { - Some(ctx) => ctx, - None => return Ok(()), - }; + let mut ctx = GlobalContext::create(&feature_test_config_path)?; let server1 = "server1"; let config_path = match protocol { @@ -126,9 +123,10 @@ async fn test_payer_requests_invoice_from_payee_inner( step_given_the_payee_has_a_lightning_node_available(&mut ctx, node_type).await?; let payee = get_payee_from_context(&ctx, "single")?; - let node_cert_path = match &payee.node { - RegTestLnNode::Cln(n) => n.ca_cert_path.as_path(), - RegTestLnNode::Lnd(n) => n.tls_cert_path.as_path(), + let node_cert_path = match payee.target_ln_node.as_str() { + "cln" => payee.ln_nodes.cln.ca_cert_path.as_path(), + "lnd" => payee.ln_nodes.lnd.tls_cert_path.as_path(), + _ => bail!("invalid node type"), }; let include_ca = match ln_trusted_roots_location { diff --git a/server/tests/features/lnurl_pay_multi_backend_invoice_generation.rs b/server/tests/features/lnurl_pay_multi_backend_invoice_generation.rs index f5ff91a..a8b01d5 100644 --- a/server/tests/features/lnurl_pay_multi_backend_invoice_generation.rs +++ b/server/tests/features/lnurl_pay_multi_backend_invoice_generation.rs @@ -3,16 +3,12 @@ use crate::common::context::Protocol; use crate::common::step_functions::*; use crate::FEATURE_TEST_CONFIG_PATH; use std::path::PathBuf; -use switchgear_testing::credentials::lightning::RegTestLnNodeType; #[tokio::test] async fn test_no_backends_no_invoices_for_either_offer() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -86,10 +82,7 @@ async fn test_no_backends_no_invoices_for_either_offer() { async fn test_two_backends_both_offers_generate_invoices() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -106,7 +99,7 @@ async fn test_two_backends_both_offers_generate_invoices() { step_given_the_lnurl_server_is_ready_to_start(&mut ctx) .await .expect("assert"); - setup_two_payees_with_node_types(&mut ctx, RegTestLnNodeType::Lnd, RegTestLnNodeType::Cln) + setup_two_payees_with_node_types(&mut ctx, "lnd", "cln") .await .expect("assert"); diff --git a/server/tests/features/secrets.rs b/server/tests/features/secrets.rs index 61546de..9e0e5b0 100644 --- a/server/tests/features/secrets.rs +++ b/server/tests/features/secrets.rs @@ -4,7 +4,6 @@ use crate::common::step_functions::*; use crate::FEATURE_TEST_CONFIG_PATH; use std::fs; use std::path::PathBuf; -use switchgear_testing::credentials::lightning::RegTestLnNodeType; use tempfile::TempDir; /// Feature: Server handles secrets files @@ -13,10 +12,7 @@ use tempfile::TempDir; async fn test_server_startup_succeeds_with_valid_secrets_file() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); @@ -36,7 +32,7 @@ async fn test_server_startup_succeeds_with_valid_secrets_file() { .expect("assert"); // Background steps - step_given_the_payee_has_a_lightning_node_available(&mut ctx, RegTestLnNodeType::Cln) + step_given_the_payee_has_a_lightning_node_available(&mut ctx, "cln") .await .expect("assert"); step_given_the_server_is_not_already_running(&mut ctx) @@ -70,10 +66,7 @@ async fn test_server_startup_succeeds_with_valid_secrets_file() { async fn test_server_startup_fails_with_missing_secrets_file() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/persistence-with-secrets.yaml"); @@ -118,10 +111,7 @@ async fn test_server_startup_fails_with_missing_secrets_file() { async fn test_server_startup_fails_with_invalid_secrets_file() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/persistence-with-secrets.yaml"); @@ -170,10 +160,7 @@ async fn test_server_startup_fails_with_invalid_secrets_file() { async fn test_server_startup_fails_with_missing_secret_in_file() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/persistence-with-secrets.yaml"); diff --git a/server/tests/features/server_lifecycle_with_graceful_shutdown.rs b/server/tests/features/server_lifecycle_with_graceful_shutdown.rs index b7cb482..c4df1a7 100644 --- a/server/tests/features/server_lifecycle_with_graceful_shutdown.rs +++ b/server/tests/features/server_lifecycle_with_graceful_shutdown.rs @@ -42,10 +42,7 @@ async fn signal_server(signal: sysinfo::Signal) -> Result<()> { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return Ok(()), - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( server1, diff --git a/server/tests/features/server_persistence.rs b/server/tests/features/server_persistence.rs index 2639388..930ff2e 100644 --- a/server/tests/features/server_persistence.rs +++ b/server/tests/features/server_persistence.rs @@ -6,7 +6,6 @@ use crate::FEATURE_TEST_CONFIG_PATH; use std::cmp::PartialEq; use std::path::PathBuf; use switchgear_testing::credentials::db::{DbCredentials, TestDatabase}; -use switchgear_testing::credentials::lightning::RegTestLnNodeType; use switchgear_testing::db::{TestMysqlDatabase, TestPostgresDatabase}; use uuid::Uuid; @@ -94,10 +93,7 @@ async fn test_complete_persistence_lifecycle_with_secrets() { async fn test_backend_data_loss_with_offer_persistence_sqlite_sqlite() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/persistence.yaml"); ctx.add_server( @@ -124,7 +120,7 @@ async fn test_backend_data_loss_with_offer_persistence_sqlite_sqlite() { .await .expect("assert"); - step_given_the_payee_has_a_lightning_node_available(&mut ctx, RegTestLnNodeType::Cln) + step_given_the_payee_has_a_lightning_node_available(&mut ctx, "cln") .await .expect("assert"); step_when_the_payee_creates_an_offer_for_their_lightning_node(&mut ctx, "single") @@ -218,10 +214,7 @@ async fn test_complete_persistence_lifecycle_impl(db_type: DbType, db_uri_type: let db_credentials = DbCredentials::create().expect("assert"); let db = db_credentials.get_databases().expect("assert"); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = match db_uri_type { DbUriType::Full => manifest_dir.join("config/persistence.yaml"), @@ -241,76 +234,59 @@ async fn test_complete_persistence_lifecycle_impl(db_type: DbType, db_uri_type: let _db_guard = match &db_type { DbType::Sqlite => (None, None), - DbType::Mysql { ssl } => match &db.mysql { - None => { - eprintln!("MySQL database not available, skipping test"); - return; - } - Some(db) => ( - Some( - install_mysql_databases( - &mut ctx, - DataStoreActivations::All, - server1, - db, - DbUriType::Full, - *ssl, - ) - .expect("assert"), - ), - None, + DbType::Mysql { ssl } => ( + Some( + install_mysql_databases( + &mut ctx, + DataStoreActivations::All, + server1, + &db.mysql, + DbUriType::Full, + *ssl, + ) + .expect("assert"), ), - }, - DbType::Postgresql { ssl } => match &db.postgres { - None => { - eprintln!("PostgreSQL database not available, skipping test"); - return; - } - Some(db) => ( - None, - Some( - install_postgres_databases( - &mut ctx, - DataStoreActivations::All, - server1, - db, - DbUriType::Full, - *ssl, - ) - .expect("assert"), - ), + None, + ), + DbType::Postgresql { ssl } => ( + None, + Some( + install_postgres_databases( + &mut ctx, + DataStoreActivations::All, + server1, + &db.postgres, + DbUriType::Full, + *ssl, + ) + .expect("assert"), ), - }, - DbType::PostgresqlAndMysql => match (&db.postgres, &db.mysql) { - (Some(postgres), Some(mysql)) => ( - Some( - install_mysql_databases( - &mut ctx, - DataStoreActivations::Offer, - server1, - mysql, - DbUriType::AddressNameWithSecrets, - DbSslType::None, - ) - .expect("assert"), - ), - Some( - install_postgres_databases( - &mut ctx, - DataStoreActivations::Discovery, - server1, - postgres, - DbUriType::AddressNameWithSecrets, - DbSslType::None, - ) - .expect("assert"), - ), + ), + + DbType::PostgresqlAndMysql => ( + Some( + install_mysql_databases( + &mut ctx, + DataStoreActivations::Offer, + server1, + &db.mysql, + DbUriType::AddressNameWithSecrets, + DbSslType::None, + ) + .expect("assert"), ), - _ => { - eprintln!("Both PostgreSQL and Mysql databases not available, skipping test"); - return; - } - }, + Some( + install_postgres_databases( + &mut ctx, + DataStoreActivations::Discovery, + server1, + &db.postgres, + DbUriType::AddressNameWithSecrets, + DbSslType::None, + ) + .expect("assert"), + ), + ), }; ctx.activate_server(server1); @@ -336,7 +312,7 @@ async fn test_complete_persistence_lifecycle_impl(db_type: DbType, db_uri_type: .expect("assert"); // Setup specific backend and create data to persist - step_given_the_payee_has_a_lightning_node_available(&mut ctx, RegTestLnNodeType::Cln) + step_given_the_payee_has_a_lightning_node_available(&mut ctx, "cln") .await .expect("assert"); step_when_the_payee_creates_an_offer_for_their_lightning_node(&mut ctx, "single") diff --git a/server/tests/features/service_enablement.rs b/server/tests/features/service_enablement.rs index 79629b7..23c4a24 100644 --- a/server/tests/features/service_enablement.rs +++ b/server/tests/features/service_enablement.rs @@ -9,10 +9,7 @@ use std::path::PathBuf; async fn test_start_all_service() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -68,10 +65,7 @@ async fn test_start_all_service() { async fn test_start_only_lnurl_service() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -127,10 +121,7 @@ async fn test_start_only_lnurl_service() { async fn test_start_only_backend_discovery_service() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -186,10 +177,7 @@ async fn test_start_only_backend_discovery_service() { async fn test_start_only_offers_service() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( diff --git a/server/tests/features/service_logs.rs b/server/tests/features/service_logs.rs index fb52172..7f25b7c 100644 --- a/server/tests/features/service_logs.rs +++ b/server/tests/features/service_logs.rs @@ -5,16 +5,12 @@ use crate::FEATURE_TEST_CONFIG_PATH; use rand::Rng; use secp256k1::{PublicKey, Secp256k1, SecretKey}; use std::path::PathBuf; -use switchgear_testing::credentials::lightning::RegTestLnNodeType; #[tokio::test] async fn test_service_health_check_logging() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -36,7 +32,7 @@ async fn test_service_health_check_logging() { step_given_the_lnurl_server_is_ready_to_start(&mut ctx) .await .expect("assert"); - step_given_the_payee_has_a_lightning_node_available(&mut ctx, RegTestLnNodeType::Cln) + step_given_the_payee_has_a_lightning_node_available(&mut ctx, "cln") .await .expect("assert"); step_when_i_start_the_lnurl_server_with_the_configuration(&mut ctx) @@ -75,10 +71,7 @@ async fn test_service_health_check_logging() { async fn test_service_operation_request_logging() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( @@ -100,7 +93,7 @@ async fn test_service_operation_request_logging() { step_given_the_lnurl_server_is_ready_to_start(&mut ctx) .await .expect("assert"); - step_given_the_payee_has_a_lightning_node_available(&mut ctx, RegTestLnNodeType::Cln) + step_given_the_payee_has_a_lightning_node_available(&mut ctx, "cln") .await .expect("assert"); step_when_i_start_the_lnurl_server_with_the_configuration(&mut ctx) @@ -161,10 +154,7 @@ async fn test_service_operation_request_logging() { async fn test_error_conditions_are_properly_logged() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let feature_test_config_path = manifest_dir.join(FEATURE_TEST_CONFIG_PATH); - let mut ctx = match GlobalContext::create(&feature_test_config_path).expect("assert") { - Some(ctx) => ctx, - None => return, - }; + let mut ctx = GlobalContext::create(&feature_test_config_path).expect("assert"); let server1 = "server1"; let config_path = manifest_dir.join("config/memory-basic.yaml"); ctx.add_server( diff --git a/service-api/Cargo.toml b/service-api/Cargo.toml new file mode 100644 index 0000000..2d47093 --- /dev/null +++ b/service-api/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "switchgear-service-api" +version.workspace = true +edition = "2021" +authors = ["Bitshock "] +description = "Service layer API for Switchgear LNURL load balancer" +documentation = "https://github.com/bitshock-src/switchgear" +homepage = "https://bitshock.com" +repository = "https://github.com/bitshock-src/switchgear" +license = "Apache-2.0" +keywords = ["bitcoin", "lightning", "lnurl", "api", "service"] +categories = ["network-programming", "web-programming", "cryptography::cryptocurrencies"] +publish = true + +[dependencies] +async-trait = "0.1" +axum = { version = "0.8", features = ["macros"] } +base64 = "0.22" +chrono = { version = "0.4", features = ["serde"] } +email_address = "0.2" +hex = "0.4" +secp256k1 = { version = "0.31", features = ["recovery", "serde"] } +serde = "1" +serde_json = "1" +tokio = { version = "1", features = ["full"] } +url = { version = "2", features = ["serde"] } +uuid = { version = "1", features = ["v4", "serde"] } + +[dev-dependencies] +bitcoin_hashes = "0.14" +lightning-invoice = { version = "0.34", features = ["serde", "std"] } +secp256k1_0_29 = { package = "secp256k1", version = "0.29", features = ["recovery", "serde"] } diff --git a/service/src/api/balance.rs b/service-api/src/balance.rs similarity index 87% rename from service/src/api/balance.rs rename to service-api/src/balance.rs index 6ebd99b..8060f00 100644 --- a/service/src/api/balance.rs +++ b/service-api/src/balance.rs @@ -1,5 +1,5 @@ -use crate::api::offer::Offer; -use crate::api::service::HasServiceErrorSource; +use crate::offer::Offer; +use crate::service::HasServiceErrorSource; use async_trait::async_trait; use std::error::Error; use tokio::sync::watch; diff --git a/service-api/src/discovery.rs b/service-api/src/discovery.rs new file mode 100644 index 0000000..53e41a5 --- /dev/null +++ b/service-api/src/discovery.rs @@ -0,0 +1,115 @@ +use crate::service::HasServiceErrorSource; +use async_trait::async_trait; +use secp256k1::PublicKey; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeSet; +use std::error::Error; +use std::io; + +#[async_trait] +pub trait DiscoveryBackendStore { + type Error: Error + Send + Sync + 'static + HasServiceErrorSource; + + async fn get(&self, public_key: &PublicKey) -> Result, Self::Error>; + + async fn get_all(&self, etag: Option) -> Result; + + async fn post(&self, backend: DiscoveryBackend) -> Result, Self::Error>; + + async fn put(&self, backend: DiscoveryBackend) -> Result; + + async fn patch(&self, backend: DiscoveryBackendPatch) -> Result; + + async fn delete(&self, public_key: &PublicKey) -> Result; +} + +#[async_trait] +pub trait HttpDiscoveryBackendClient: DiscoveryBackendStore { + async fn health(&self) -> Result<(), Self::Error>; +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveryBackends { + pub etag: u64, + pub backends: Option>, +} + +impl DiscoveryBackends { + pub fn etag_from_str(etag: &str) -> io::Result { + let etag = hex::decode(etag).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let etag: [u8; 8] = etag.try_into().map_err(|etag: Vec| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("invalid etag size: {} bytes", etag.len()), + ) + })?; + Ok(u64::from_be_bytes(etag)) + } + + pub fn etag_string(&self) -> String { + hex::encode(self.etag.to_be_bytes()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveryBackend { + pub public_key: PublicKey, + #[serde(flatten)] + pub backend: DiscoveryBackendSparse, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveryBackendSparse { + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + pub partitions: BTreeSet, + pub weight: usize, + pub enabled: bool, + #[serde(with = "json_bytes")] + pub implementation: Vec, +} + +mod json_bytes { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde_json::Value; + + pub fn serialize(bytes: &[u8], serializer: S) -> Result + where + S: Serializer, + { + let value: Value = serde_json::from_slice(bytes).map_err(serde::ser::Error::custom)?; + value.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let value = Value::deserialize(deserializer)?; + serde_json::to_vec(&value).map_err(serde::de::Error::custom) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveryBackendPatch { + pub public_key: PublicKey, + #[serde(flatten)] + pub backend: DiscoveryBackendPatchSparse, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveryBackendPatchSparse { + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub partitions: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub weight: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enabled: Option, +} diff --git a/service/src/api/mod.rs b/service-api/src/lib.rs similarity index 100% rename from service/src/api/mod.rs rename to service-api/src/lib.rs diff --git a/service/src/api/lnurl.rs b/service-api/src/lnurl.rs similarity index 98% rename from service/src/api/lnurl.rs rename to service-api/src/lnurl.rs index 22ff6d6..9169cd4 100644 --- a/service/src/api/lnurl.rs +++ b/service-api/src/lnurl.rs @@ -1,4 +1,4 @@ -use crate::api::offer::{OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse}; +use crate::offer::{OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse}; use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; use base64::Engine; use serde::de::{Error, SeqAccess, Visitor}; @@ -199,10 +199,10 @@ pub enum LnUrlErrorStatus { #[cfg(test)] mod test { - use crate::api::lnurl::{ + use crate::lnurl::{ LnUrlError, LnUrlErrorStatus, LnUrlInvoice, LnUrlOffer, LnUrlOfferMetadata, LnUrlOfferTag, }; - use crate::api::offer::{OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse}; + use crate::offer::{OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse}; use bitcoin_hashes::{sha256, Hash}; use lightning_invoice::{Currency, InvoiceBuilder, PaymentSecret}; use secp256k1_0_29::{Secp256k1, SecretKey}; @@ -334,7 +334,7 @@ mod test { ) .unwrap(); - let payment_hash = sha256::Hash::from_slice(&[0; 32][..]).unwrap(); + let payment_hash = sha256::Hash::from_byte_array([0; 32]); let payment_secret = PaymentSecret([42u8; 32]); let invoice = LnUrlInvoice { diff --git a/service/src/api/offer.rs b/service-api/src/offer.rs similarity index 98% rename from service/src/api/offer.rs rename to service-api/src/offer.rs index 79566a7..f33bbf3 100644 --- a/service/src/api/offer.rs +++ b/service-api/src/offer.rs @@ -1,4 +1,4 @@ -use crate::api::service::HasServiceErrorSource; +use crate::service::HasServiceErrorSource; use async_trait::async_trait; use email_address::EmailAddress; use serde::{Deserialize, Serialize}; @@ -182,7 +182,7 @@ mod base64_bytes { #[cfg(test)] mod test { - use crate::api::offer::{ + use crate::offer::{ OfferMetadata, OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse, }; diff --git a/service/src/api/service.rs b/service-api/src/service.rs similarity index 100% rename from service/src/api/service.rs rename to service-api/src/service.rs diff --git a/service/Cargo.toml b/service/Cargo.toml index 72f9c3c..0750fe0 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -3,7 +3,7 @@ name = "switchgear-service" version.workspace = true edition = "2021" authors = ["Bitshock "] -description = "Service layer and API implementations for Switchgear LNURL load balancer" +description = "Service layer implementations for Switchgear LNURL load balancer" documentation = "https://github.com/bitshock-src/switchgear" homepage = "https://bitshock.com" repository = "https://github.com/bitshock-src/switchgear" @@ -16,55 +16,29 @@ publish = true async-trait = "0.1" axum = { version = "0.8", features = ["macros"] } axum-extra = { version = "0.12", features = ["typed-header"] } -backoff = { version = "0.4", features = ["tokio"] } -base64 = "0.22" bech32 = "0.11" chrono = { version = "0.4", features = ["serde"] } -client-ip = { version = "0.1", features = ["forwarded-header"] } -email_address = "0.2" -hex = "0.4" http = "1" -hyper-rustls = { version = "0.27", features = ["http2", "tls12", "native-tokio"], default-features = false } -hyper-timeout = "0.5" -hyper-util = "0.1" image = { version = "0.25", default-features = false, features = ["png"] } jsonwebtoken = { version = "10", features = ["aws_lc_rs"] } log = "0.4" -prost = { version = "0.14" } qrcode = { version = "0.14", default-features = false, features = ["image"] } -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots-no-provider"] } -rustls = { version = "0.23", default-features = false } -rustls-pemfile = "2" -sea-orm = { version = "1", default-features = false, features = ["with-chrono", "with-uuid", "with-json"] } secp256k1 = { version = "0.31", features = ["recovery", "serde"] } serde = "1" serde_json = "1" -sha2 = "0.10" sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "tls-rustls-aws-lc-rs", "sqlite", "postgres", "mysql"] } -switchgear-migration.workspace = true -tempfile = "3" +switchgear-components.workspace = true +switchgear-service-api.workspace = true thiserror = "2" tokio = { version = "1", features = ["full"] } -tonic = { version = "0.14", default-features = false, features = ["codegen", "transport", "tls-native-roots"] } -tonic-prost = "0.14" tower = { version = "0.5", features = ["balance"] } url = { version = "2", features = ["serde"] } uuid = { version = "1", features = ["v4", "serde"] } -[build-dependencies] -tonic-prost-build = { version = "0.14" } - [dev-dependencies] -anyhow = "1" axum-test = "18" -bitcoin_hashes = "0.14" -hyper-util = "0.1" -lightning-invoice = { version = "0.33", features = ["serde", "std"] } p256 = { version = "0.13", features = ["ecdsa"] } pkcs8 = { version = "0.10", features = ["pem"] } png = "0.18" rand = "0.8" rqrr = "0.10" -rustls = { version = "0.23", features = ["aws-lc-rs"] } -secp256k1_0_29 = { package = "secp256k1", version = "0.29", features = ["recovery", "serde"] } -switchgear-testing.workspace = true diff --git a/service/src/api/discovery.rs b/service/src/api/discovery.rs deleted file mode 100644 index 2654e66..0000000 --- a/service/src/api/discovery.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::api::service::HasServiceErrorSource; -use crate::components::pool::cln::grpc::config::ClnGrpcDiscoveryBackendImplementation; -use crate::components::pool::lnd::grpc::config::LndGrpcDiscoveryBackendImplementation; -use async_trait::async_trait; -use secp256k1::PublicKey; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeSet; -use std::error::Error; -use std::io; - -#[async_trait] -pub trait DiscoveryBackendStore { - type Error: Error + Send + Sync + 'static + HasServiceErrorSource; - - async fn get(&self, public_key: &PublicKey) -> Result, Self::Error>; - - async fn get_all(&self, etag: Option) -> Result; - - async fn post(&self, backend: DiscoveryBackend) -> Result, Self::Error>; - - async fn put(&self, backend: DiscoveryBackend) -> Result; - - async fn patch(&self, backend: DiscoveryBackendPatch) -> Result; - - async fn delete(&self, public_key: &PublicKey) -> Result; -} - -#[async_trait] -pub trait HttpDiscoveryBackendClient: DiscoveryBackendStore { - async fn health(&self) -> Result<(), Self::Error>; -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DiscoveryBackends { - pub etag: u64, - pub backends: Option>, -} - -impl DiscoveryBackends { - pub fn etag_from_str(etag: &str) -> io::Result { - let etag = hex::decode(etag).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let etag: [u8; 8] = etag.try_into().map_err(|etag: Vec| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("invalid etag size: {} bytes", etag.len()), - ) - })?; - Ok(u64::from_be_bytes(etag)) - } - - pub fn etag_string(&self) -> String { - hex::encode(self.etag.to_be_bytes()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DiscoveryBackend { - pub public_key: PublicKey, - #[serde(flatten)] - pub backend: DiscoveryBackendSparse, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DiscoveryBackendSparse { - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - pub partitions: BTreeSet, - pub weight: usize, - pub enabled: bool, - pub implementation: DiscoveryBackendImplementation, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DiscoveryBackendPatch { - pub public_key: PublicKey, - #[serde(flatten)] - pub backend: DiscoveryBackendPatchSparse, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DiscoveryBackendPatchSparse { - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub partitions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub weight: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[serde(tag = "type")] -pub enum DiscoveryBackendImplementation { - ClnGrpc(ClnGrpcDiscoveryBackendImplementation), - LndGrpc(LndGrpcDiscoveryBackendImplementation), -} - -#[cfg(test)] -mod test { - use super::*; - use crate::components::pool::lnd::grpc::config::{LndGrpcClientAuth, LndGrpcClientAuthPath}; - use secp256k1::{Secp256k1, SecretKey}; - - #[test] - fn serialize_when_discovery_backend_then_returns_json_with_flattened_fields() { - let private_key = SecretKey::from_byte_array([ - 0xe1, 0x26, 0xf6, 0x8f, 0x7e, 0xaf, 0xcc, 0x8b, 0x74, 0xf5, 0x4d, 0x26, 0x9f, 0xe2, - 0x06, 0xbe, 0x71, 0x50, 0x00, 0xf9, 0x4d, 0xac, 0x06, 0x7d, 0x1c, 0x04, 0xa8, 0xca, - 0x3b, 0x2d, 0xb7, 0x34, - ]) - .unwrap(); - let public_key = private_key.public_key(&Secp256k1::new()); - - let backend = DiscoveryBackend { - public_key, - backend: DiscoveryBackendSparse { - name: None, - partitions: ["default".to_string()].into(), - weight: 0, - enabled: true, - implementation: DiscoveryBackendImplementation::LndGrpc( - LndGrpcDiscoveryBackendImplementation { - url: "https://localhost:9736".parse().unwrap(), - domain: None, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: None, - macaroon_path: "/path/to/macaroon_path".into(), - }), - amp_invoice: false, - }, - ), - }, - }; - - let backends = serde_json::to_string(&backend).unwrap(); - let backends_expected = r#"{"publicKey":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","partitions":["default"],"weight":0,"enabled":true,"implementation":{"type":"lndGrpc","url":"https://localhost:9736/","domain":null,"auth":{"type":"path","tlsCertPath":null,"macaroonPath":"/path/to/macaroon_path"},"ampInvoice":false}}"#; - assert_eq!(backends, backends_expected); - } - - #[test] - fn deserialize_when_valid_json_then_creates_discovery_backend_with_flattened_fields() { - let private_key = SecretKey::from_byte_array([ - 0xe1, 0x26, 0xf6, 0x8f, 0x7e, 0xaf, 0xcc, 0x8b, 0x74, 0xf5, 0x4d, 0x26, 0x9f, 0xe2, - 0x06, 0xbe, 0x71, 0x50, 0x00, 0xf9, 0x4d, 0xac, 0x06, 0x7d, 0x1c, 0x04, 0xa8, 0xca, - 0x3b, 0x2d, 0xb7, 0x34, - ]) - .unwrap(); - let public_key = private_key.public_key(&Secp256k1::new()); - - let backend_expected = DiscoveryBackend { - public_key, - backend: DiscoveryBackendSparse { - name: None, - partitions: ["default".to_string()].into(), - weight: 0, - enabled: true, - implementation: DiscoveryBackendImplementation::LndGrpc( - LndGrpcDiscoveryBackendImplementation { - url: "https://localhost:9736".parse().unwrap(), - domain: None, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: None, - macaroon_path: "/path/to/macaroon_path".into(), - }), - amp_invoice: false, - }, - ), - }, - }; - - let backend = r#"{"publicKey":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","partitions":["default"],"weight":0,"enabled":true,"implementation":{"type":"lndGrpc","url":"https://localhost:9736/","domain":null,"auth":{"type":"path","tlsCertPath":null,"macaroonPath":"/path/to/macaroon_path"},"ampInvoice":false}}"#; - let backend: DiscoveryBackend = serde_json::from_str(backend).unwrap(); - - assert_eq!(backend_expected, backend); - } -} diff --git a/service/src/axum/crud/error.rs b/service/src/axum/crud/error.rs index fe29cb6..09913a4 100644 --- a/service/src/axum/crud/error.rs +++ b/service/src/axum/crud/error.rs @@ -1,4 +1,3 @@ -use crate::api::service::HasServiceErrorSource; use axum::http::header::InvalidHeaderValue; use axum::http::{HeaderMap, HeaderValue}; use axum::{ @@ -7,6 +6,7 @@ use axum::{ }; use log::error; use std::fmt::Display; +use switchgear_service_api::service::HasServiceErrorSource; use thiserror::Error; #[macro_export] diff --git a/service/src/axum/crud/response.rs b/service/src/axum/crud/response.rs index 1db0169..4c9b0e9 100644 --- a/service/src/axum/crud/response.rs +++ b/service/src/axum/crud/response.rs @@ -1,6 +1,6 @@ -use crate::api::service::StatusCode; use axum::http::{HeaderMap, HeaderValue}; use axum::response::{IntoResponse, Response}; +use switchgear_service_api::service::StatusCode; pub struct JsonCrudResponse { body: Option, diff --git a/service/src/discovery/handler.rs b/service/src/discovery/handler.rs index 2b9ad11..93529df 100644 --- a/service/src/discovery/handler.rs +++ b/service/src/discovery/handler.rs @@ -1,7 +1,3 @@ -use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, - DiscoveryBackendStore, DiscoveryBackends, -}; use crate::axum::crud::error::CrudError; use crate::axum::crud::response::JsonCrudResponse; use crate::axum::header::no_cache_headers; @@ -9,6 +5,10 @@ use crate::discovery::state::DiscoveryState; use axum::extract::Path; use axum::http::{HeaderMap, HeaderValue}; use axum::{extract::State, Json}; +use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, + DiscoveryBackendStore, DiscoveryBackends, +}; pub struct DiscoveryHandlers; diff --git a/service/src/discovery/service.rs b/service/src/discovery/service.rs index ff59428..514cf97 100644 --- a/service/src/discovery/service.rs +++ b/service/src/discovery/service.rs @@ -1,11 +1,11 @@ -use crate::api::discovery::DiscoveryBackendStore; -use crate::api::service::StatusCode; use crate::axum::auth::BearerTokenAuthLayer; use crate::discovery::auth::DiscoveryBearerTokenValidator; use crate::discovery::handler::DiscoveryHandlers; use crate::discovery::state::DiscoveryState; use axum::routing::{delete, get, patch, post, put}; use axum::Router; +use switchgear_service_api::discovery::DiscoveryBackendStore; +use switchgear_service_api::service::StatusCode; #[derive(Debug)] pub struct DiscoveryService; @@ -49,14 +49,6 @@ impl DiscoveryService { #[cfg(test)] mod tests { - use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatchSparse, - DiscoveryBackendSparse, - }; - use crate::components::discovery::memory::MemoryDiscoveryBackendStore; - use crate::components::pool::lnd::grpc::config::{ - LndGrpcClientAuth, LndGrpcClientAuthPath, LndGrpcDiscoveryBackendImplementation, - }; use crate::discovery::auth::{DiscoveryAudience, DiscoveryClaims}; use crate::discovery::service::DiscoveryService; use crate::discovery::state::DiscoveryState; @@ -69,6 +61,10 @@ mod tests { use rand::{thread_rng, Rng}; use secp256k1::{PublicKey, Secp256k1, SecretKey}; use std::time::{SystemTime, UNIX_EPOCH}; + use switchgear_components::discovery::memory::MemoryDiscoveryBackendStore; + use switchgear_service_api::discovery::{ + DiscoveryBackend, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, + }; fn create_test_backend(partition: &str) -> DiscoveryBackend { let secp = Secp256k1::new(); @@ -83,17 +79,7 @@ mod tests { partitions: [partition.to_string()].into(), weight: 100, enabled: true, - implementation: DiscoveryBackendImplementation::LndGrpc( - LndGrpcDiscoveryBackendImplementation { - url: "https://localhost:9736".parse().unwrap(), - domain: None, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: None, - macaroon_path: "/path/to/macaroon_path".into(), - }), - amp_invoice: false, - }, - ), + implementation: "{}".as_bytes().to_vec(), }, } } @@ -239,18 +225,6 @@ mod tests { assert_eq!(response.status_code(), StatusCode::OK); let retrieved: DiscoveryBackend = response.json(); - assert_eq!( - retrieved.backend.implementation, - DiscoveryBackendImplementation::LndGrpc(LndGrpcDiscoveryBackendImplementation { - url: "https://localhost:9736".parse().unwrap(), - domain: None, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: None, - macaroon_path: "/path/to/macaroon_path".into(), - }), - amp_invoice: false, - }), - ); assert_eq!(retrieved.public_key, backend.public_key); } diff --git a/service/src/discovery/state.rs b/service/src/discovery/state.rs index 3ba2035..3c87f01 100644 --- a/service/src/discovery/state.rs +++ b/service/src/discovery/state.rs @@ -1,5 +1,5 @@ -use crate::api::discovery::DiscoveryBackendStore; use jsonwebtoken::DecodingKey; +use switchgear_service_api::discovery::DiscoveryBackendStore; #[derive(Clone)] pub struct DiscoveryState { diff --git a/service/src/lib.rs b/service/src/lib.rs index ce06dc8..c9e9d94 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -1,6 +1,4 @@ -pub mod api; pub(crate) mod axum; -pub mod components; pub(crate) mod discovery; pub(crate) mod lnurl; pub(crate) mod offer; diff --git a/service/src/lnurl/pay/error.rs b/service/src/lnurl/pay/error.rs index 0c243e4..1d0b79a 100644 --- a/service/src/lnurl/pay/error.rs +++ b/service/src/lnurl/pay/error.rs @@ -1,11 +1,11 @@ -use crate::api::lnurl::{LnUrlError, LnUrlErrorStatus}; -use crate::api::service::HasServiceErrorSource; use axum::http::header::InvalidHeaderValue; use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; use log::error; +use switchgear_service_api::lnurl::{LnUrlError, LnUrlErrorStatus}; +use switchgear_service_api::service::HasServiceErrorSource; use thiserror::Error; #[macro_export] diff --git a/service/src/lnurl/pay/handler.rs b/service/src/lnurl/pay/handler.rs index fbd0921..f13e8fc 100644 --- a/service/src/lnurl/pay/handler.rs +++ b/service/src/lnurl/pay/handler.rs @@ -1,6 +1,3 @@ -use crate::api::balance::LnBalancer; -use crate::api::lnurl::{LnUrlInvoice, LnUrlOffer, LnUrlOfferTag}; -use crate::api::offer::{Offer, OfferProvider}; use crate::axum::extract::host::ValidatedHost; use crate::axum::extract::scheme::Scheme; use crate::axum::extract::uuid::UuidParam; @@ -17,6 +14,9 @@ use qrcode::QrCode; use serde::Deserialize; use sqlx::types::JsonValue; use std::io::{self, Cursor}; +use switchgear_service_api::balance::LnBalancer; +use switchgear_service_api::lnurl::{LnUrlInvoice, LnUrlOffer, LnUrlOfferTag}; +use switchgear_service_api::offer::{Offer, OfferProvider}; use url::Url; use uuid::Uuid; diff --git a/service/src/lnurl/pay/state.rs b/service/src/lnurl/pay/state.rs index 0a8afed..5965037 100644 --- a/service/src/lnurl/pay/state.rs +++ b/service/src/lnurl/pay/state.rs @@ -1,9 +1,9 @@ -use crate::api::balance::LnBalancer; -use crate::api::offer::OfferProvider; use crate::axum::extract::host::AllowedHosts; use crate::axum::extract::scheme::Scheme; use axum::extract::FromRef; use std::collections::HashSet; +use switchgear_service_api::balance::LnBalancer; +use switchgear_service_api::offer::OfferProvider; #[derive(Debug, Clone)] pub struct LnUrlPayState { diff --git a/service/src/lnurl/service.rs b/service/src/lnurl/service.rs index e47a163..60de104 100644 --- a/service/src/lnurl/service.rs +++ b/service/src/lnurl/service.rs @@ -1,5 +1,3 @@ -use crate::api::balance::LnBalancer; -use crate::api::offer::OfferProvider; use crate::axum::partitions::PartitionsLayer; use crate::lnurl::pay::handler::LnUrlPayHandlers; use crate::lnurl::pay::state::LnUrlPayState; @@ -7,6 +5,8 @@ use axum::http::StatusCode; use axum::routing::get; use axum::Router; use std::sync::Arc; +use switchgear_service_api::balance::LnBalancer; +use switchgear_service_api::offer::OfferProvider; #[derive(Debug)] pub struct LnUrlBalancerService; @@ -44,15 +44,7 @@ impl LnUrlBalancerService { #[cfg(test)] mod tests { - use crate::api::balance::LnBalancer; - use crate::api::lnurl::{LnUrlInvoice, LnUrlOffer, LnUrlOfferMetadata}; - use crate::api::offer::{ - Offer, OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, - OfferRecordSparse, OfferStore, - }; - use crate::api::service::HasServiceErrorSource; use crate::axum::extract::scheme::Scheme; - use crate::components::offer::memory::MemoryOfferStore; use crate::lnurl::pay::state::LnUrlPayState; use crate::lnurl::service::LnUrlBalancerService; use async_trait::async_trait; @@ -60,6 +52,14 @@ mod tests { use axum_test::TestServer; use chrono::{Duration, Utc}; use std::collections::HashSet; + use switchgear_components::offer::memory::MemoryOfferStore; + use switchgear_service_api::balance::LnBalancer; + use switchgear_service_api::lnurl::{LnUrlInvoice, LnUrlOffer, LnUrlOfferMetadata}; + use switchgear_service_api::offer::{ + Offer, OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, + OfferRecordSparse, OfferStore, + }; + use switchgear_service_api::service::HasServiceErrorSource; use uuid::Uuid; // Mock LnBalancer implementation @@ -113,13 +113,13 @@ mod tests { } impl HasServiceErrorSource for MockLnBalancerCombinedError { - fn get_service_error_source(&self) -> crate::api::service::ServiceErrorSource { + fn get_service_error_source(&self) -> switchgear_service_api::service::ServiceErrorSource { match self { MockLnBalancerCombinedError::Internal => { - crate::api::service::ServiceErrorSource::Internal + switchgear_service_api::service::ServiceErrorSource::Internal } MockLnBalancerCombinedError::Upstream => { - crate::api::service::ServiceErrorSource::Upstream + switchgear_service_api::service::ServiceErrorSource::Upstream } } } diff --git a/service/src/offer/handler.rs b/service/src/offer/handler.rs index d1489e7..73097f7 100644 --- a/service/src/offer/handler.rs +++ b/service/src/offer/handler.rs @@ -1,7 +1,3 @@ -use crate::api::offer::{ - OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, OfferRecordSparse, - OfferStore, -}; use crate::axum::crud::error::CrudError; use crate::axum::crud::response::JsonCrudResponse; use crate::axum::extract::uuid::UuidParam; @@ -11,6 +7,10 @@ use axum::extract::Query; use axum::http::HeaderValue; use axum::{extract::State, Json}; use serde::Deserialize; +use switchgear_service_api::offer::{ + OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, OfferRecordSparse, + OfferStore, +}; #[derive(Deserialize, Debug)] pub struct GetAllOffersQueryParameters { diff --git a/service/src/offer/service.rs b/service/src/offer/service.rs index 8526fe2..392bd78 100644 --- a/service/src/offer/service.rs +++ b/service/src/offer/service.rs @@ -1,11 +1,11 @@ -use crate::api::offer::{OfferMetadataStore, OfferStore}; -use crate::api::service::StatusCode; use crate::axum::auth::BearerTokenAuthLayer; use crate::offer::auth::OfferBearerTokenValidator; use crate::offer::handler::OfferHandlers; use crate::offer::state::OfferState; use axum::routing::{delete, get, post, put}; use axum::Router; +use switchgear_service_api::offer::{OfferMetadataStore, OfferStore}; +use switchgear_service_api::service::StatusCode; #[derive(Debug)] pub struct OfferService; @@ -57,11 +57,6 @@ impl OfferService { #[cfg(test)] mod tests { - use crate::api::offer::{ - OfferMetadata, OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse, - OfferMetadataStore, OfferRecord, OfferRecordSparse, OfferStore, - }; - use crate::components::offer::memory::MemoryOfferStore; use crate::offer::service::OfferService; use crate::offer::state::OfferState; use crate::{OfferAudience, OfferClaims}; @@ -74,6 +69,11 @@ mod tests { use p256::pkcs8::EncodePublicKey; use rand::thread_rng; use std::time::{SystemTime, UNIX_EPOCH}; + use switchgear_components::offer::memory::MemoryOfferStore; + use switchgear_service_api::offer::{ + OfferMetadata, OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse, + OfferMetadataStore, OfferRecord, OfferRecordSparse, OfferStore, + }; use uuid::Uuid; fn create_test_offer_with_metadata_id(metadata_id: Uuid) -> OfferRecord { diff --git a/service/src/offer/state.rs b/service/src/offer/state.rs index 2d879e2..91cfb3a 100644 --- a/service/src/offer/state.rs +++ b/service/src/offer/state.rs @@ -1,5 +1,5 @@ -use crate::api::offer::{OfferMetadataStore, OfferStore}; use jsonwebtoken::DecodingKey; +use switchgear_service_api::offer::{OfferMetadataStore, OfferStore}; #[derive(Clone)] pub struct OfferState { diff --git a/service/tests/common/service.rs b/service/tests/common/service.rs deleted file mode 100644 index 9707218..0000000 --- a/service/tests/common/service.rs +++ /dev/null @@ -1,195 +0,0 @@ -use anyhow::bail; -use jsonwebtoken::{encode, Algorithm, DecodingKey, EncodingKey, Header}; -use p256::ecdsa::SigningKey; -use pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding}; -use rand::thread_rng; -use std::net::{Ipv4Addr, SocketAddr}; -use std::path::Path; -use std::sync::Arc; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use switchgear_service::components::discovery::memory::MemoryDiscoveryBackendStore; -use switchgear_service::components::offer::memory::MemoryOfferStore; -use switchgear_service::{ - DiscoveryAudience, DiscoveryClaims, DiscoveryService, DiscoveryState, OfferAudience, - OfferClaims, OfferService, OfferState, -}; -use switchgear_testing::ports::PortAllocator; -use tokio::net::TcpListener as TokioTcpListener; -use tokio::sync::Notify; -use tokio::time::{sleep as tokio_sleep, timeout}; - -pub struct TestService { - pub discovery_port: u16, - pub offer_port: u16, - _discovery_handle: tokio::task::JoinHandle>, - _offer_handle: tokio::task::JoinHandle>, - shutdown_notify: Arc, - pub discovery_authorization: String, - pub offer_authorization: String, -} - -impl TestService { - pub async fn start(ports_path: &Path) -> anyhow::Result { - let mut rng = thread_rng(); - let discovery_key_pair = SigningKey::random(&mut rng); - - let discovery_decoding_key = *discovery_key_pair.verifying_key(); - let discovery_decoding_key = - discovery_decoding_key.to_public_key_pem(LineEnding::default())?; - let discovery_decoding_key = DecodingKey::from_ec_pem(discovery_decoding_key.as_bytes())?; - - let discovery_encoding_key = discovery_key_pair; - let discovery_encoding_key = discovery_encoding_key.to_pkcs8_pem(LineEnding::default())?; - let discovery_encoding_key = EncodingKey::from_ec_pem(discovery_encoding_key.as_bytes())?; - - let discovery_token_header = Header::new(Algorithm::ES256); - let discovery_claims = DiscoveryClaims { - aud: DiscoveryAudience::Discovery, - exp: (SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() + 3600) as usize, - }; - let discovery_authorization = encode( - &discovery_token_header, - &discovery_claims, - &discovery_encoding_key, - )?; - - let offer_key_pair = SigningKey::random(&mut rng); - - let offer_decoding_key = *offer_key_pair.verifying_key(); - let offer_decoding_key = offer_decoding_key.to_public_key_pem(LineEnding::default())?; - let offer_decoding_key = DecodingKey::from_ec_pem(offer_decoding_key.as_bytes())?; - - let offer_encoding_key = offer_key_pair; - let offer_encoding_key = offer_encoding_key.to_pkcs8_pem(LineEnding::default())?; - let offer_encoding_key = EncodingKey::from_ec_pem(offer_encoding_key.as_bytes())?; - - let offer_token_header = Header::new(Algorithm::ES256); - let offer_claims = OfferClaims { - aud: OfferAudience::Offer, - exp: (SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() + 3600) as usize, - }; - let offer_authorization = encode(&offer_token_header, &offer_claims, &offer_encoding_key)?; - - // Generate random high ports - let discovery_port = PortAllocator::find_available_port(ports_path)?; - let offer_port = PortAllocator::find_available_port(ports_path)?; - - // Create DiscoveryState with MemoryDiscoveryBackendStore - let discovery_store = MemoryDiscoveryBackendStore::default(); - let discovery_state = DiscoveryState::new(discovery_store, discovery_decoding_key); - - // Create OfferState with MemoryOfferStore for both stores - let offer_store = MemoryOfferStore::default(); - let offer_state = - OfferState::new(offer_store.clone(), offer_store, offer_decoding_key, 100); - - // Create listeners - let discovery_listener = - TokioTcpListener::bind(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), discovery_port)) - .await?; - let offer_listener = - TokioTcpListener::bind(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), offer_port)).await?; - - // Create shutdown signal - let shutdown_notify = Arc::new(Notify::new()); - let discovery_shutdown = shutdown_notify.clone(); - let offer_shutdown = shutdown_notify.clone(); - - // Start the discovery service with graceful shutdown - let discovery_handle = tokio::spawn(async move { - axum::serve( - discovery_listener, - DiscoveryService::router(discovery_state), - ) - .with_graceful_shutdown(async move { - discovery_shutdown.notified().await; - }) - .await - }); - - // Start the offer service with graceful shutdown - let offer_handle = tokio::spawn(async move { - axum::serve(offer_listener, OfferService::router(offer_state)) - .with_graceful_shutdown(async move { - offer_shutdown.notified().await; - }) - .await - }); - - let service = TestService { - discovery_port, - offer_port, - _discovery_handle: discovery_handle, - _offer_handle: offer_handle, - shutdown_notify, - discovery_authorization, - offer_authorization, - }; - - // Wait for services to start up - service.wait_for_startup().await?; - - Ok(service) - } - - async fn wait_for_startup(&self) -> anyhow::Result<()> { - let client = reqwest::Client::builder() - .timeout(Duration::from_secs(2)) - .build()?; - let timeout_duration = Duration::from_secs(15); - let start = Instant::now(); - - while start.elapsed() < timeout_duration { - // Try to connect to both health endpoints - let discovery_health = timeout( - Duration::from_secs(1), - client - .get(format!("http://127.0.0.1:{}/health", self.discovery_port)) - .send(), - ) - .await; - - let offer_health = timeout( - Duration::from_secs(1), - client - .get(format!("http://127.0.0.1:{}/health", self.offer_port)) - .send(), - ) - .await; - - if discovery_health.is_ok() - && discovery_health?.is_ok() - && offer_health.is_ok() - && offer_health?.is_ok() - { - return Ok(()); - } - - tokio_sleep(Duration::from_millis(200)).await; - } - - bail!("Services failed to start within timeout") - } - - pub fn discovery_base_url(&self) -> String { - format!("http://127.0.0.1:{}", self.discovery_port) - } - - pub fn offer_base_url(&self) -> String { - format!("http://127.0.0.1:{}", self.offer_port) - } - - // Legacy method for backward compatibility - pub fn base_url(&self) -> String { - self.discovery_base_url() - } - - pub async fn shutdown(self) { - // Send shutdown signal to both services - self.shutdown_notify.notify_waiters(); - - // Wait for both services to complete - let _ = self._discovery_handle.await; - let _ = self._offer_handle.await; - } -} diff --git a/service/tests/ln/main.rs b/service/tests/ln/main.rs deleted file mode 100644 index aeff430..0000000 --- a/service/tests/ln/main.rs +++ /dev/null @@ -1,107 +0,0 @@ -use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendSparse, -}; -use switchgear_service::components::pool::cln::grpc::config::{ - ClnGrpcClientAuth, ClnGrpcClientAuthPath, ClnGrpcDiscoveryBackendImplementation, -}; -use switchgear_service::components::pool::lnd::grpc::config::{ - LndGrpcClientAuth, LndGrpcClientAuthPath, LndGrpcDiscoveryBackendImplementation, -}; -use switchgear_testing::credentials::lightning::{LnCredentials, RegTestLnNode}; -use url::Url; - -#[path = "../common/mod.rs"] -pub mod common; - -mod cln; -mod lnd; - -pub fn try_create_cln_backend( - credentials: &LnCredentials, -) -> anyhow::Result> { - let backends = credentials.get_backends()?; - - if backends.is_empty() { - return Ok(None); - } - - let cln_node = backends - .into_iter() - .filter_map(|b| match b { - RegTestLnNode::Cln(cln) => Some(cln), - _ => None, - }) - .next() - .ok_or_else(|| anyhow::anyhow!("no cln nodes available"))?; - - let url = Url::parse(&format!("https://{}", cln_node.address))?; - - let implementation = - DiscoveryBackendImplementation::ClnGrpc(ClnGrpcDiscoveryBackendImplementation { - url, - auth: ClnGrpcClientAuth::Path(ClnGrpcClientAuthPath { - ca_cert_path: cln_node.ca_cert_path.into(), - client_cert_path: cln_node.client_cert_path, - client_key_path: cln_node.client_key_path, - }), - domain: None, - }); - - let backend = DiscoveryBackend { - public_key: cln_node.public_key, - backend: DiscoveryBackendSparse { - name: None, - partitions: ["default".to_string()].into(), - weight: 1, - implementation, - enabled: true, - }, - }; - - Ok(Some(backend)) -} - -pub fn try_create_lnd_backend( - credentials: &LnCredentials, -) -> anyhow::Result> { - let backends = credentials.get_backends()?; - - if backends.is_empty() { - return Ok(None); - } - - let lnd_node = backends - .into_iter() - .filter_map(|b| match b { - RegTestLnNode::Lnd(lnd) => Some(lnd), - _ => None, - }) - .next() - .ok_or_else(|| anyhow::anyhow!("no lnd nodes available"))?; - - let url = Url::parse(&format!("https://{}", lnd_node.address))?; - - let implementation = - DiscoveryBackendImplementation::LndGrpc(LndGrpcDiscoveryBackendImplementation { - url, - auth: LndGrpcClientAuth::Path(LndGrpcClientAuthPath { - tls_cert_path: lnd_node.tls_cert_path.into(), - macaroon_path: lnd_node.macaroon_path, - }), - amp_invoice: false, - domain: None, - }); - - let backend = DiscoveryBackend { - public_key: lnd_node.public_key, - backend: DiscoveryBackendSparse { - name: None, - partitions: ["default".to_string()].into(), - weight: 1, - implementation, - enabled: true, - }, - }; - - Ok(Some(backend)) -} diff --git a/switchgear/README.md b/switchgear/README.md index 87d68e1..c76eb20 100644 --- a/switchgear/README.md +++ b/switchgear/README.md @@ -1235,34 +1235,3 @@ Metadata can include images in PNG or JPEG format, base64 encoded: Metadata identifiers can be: - Email: `{"email": "contact@example.com"}` - Text: `{"text": "contact@example.com"}` - -## SDK - -### Service - -The [switchgear-service](https://github.com/bitshock-src/switchgear/blob/HEAD/service) crate defines all services and their trait dependencies. See the `api` module for trait definitions and data models: [service/src/api](https://github.com/bitshock-src/switchgear/blob/HEAD/service/src/api) - -![image](https://raw.githubusercontent.com/bitshock-src/switchgear/main/doc/service_traits_component_diagram-Service_Layer_Trait_Relationships.png) - - -### Pingora - -`PingoraLnBalancer` is the default `LnBalancer` implementation. The [switchgear-pingora](https://github.com/bitshock-src/switchgear/blob/HEAD/pingora) crate holds the complete implementation, plus trait definitions it uses for itself. - -![image](https://raw.githubusercontent.com/bitshock-src/switchgear/main/doc/pingora_traits_component_diagram-PingoraLnBalancer_Trait_Dependencies.png) - - -### Components - -The `components` module in [switchgear-service](https://github.com/bitshock-src/switchgear/blob/HEAD/service/src/components) is a collection self-defined traits and implementations useful for implementing a complete `LnBalancer`. The module also holds different implementations of `DiscoveryBackendStore`, `OfferStore` and `OfferMetadataStore`. - -#### Service Components - -![image](https://raw.githubusercontent.com/bitshock-src/switchgear/main/doc/service_components_traits_diagram-Service_Components_Trait_Dependencies.png) - -#### Data Store Implementations - -![image](https://raw.githubusercontent.com/bitshock-src/switchgear/main/doc/service_discovery_traits_diagram-Discovery_Components_Trait_Dependencies.png) - -![image](https://raw.githubusercontent.com/bitshock-src/switchgear/main/doc/service_offer_traits_diagram-Offer_Components_Trait_Dependencies.png) - diff --git a/testing/README.md b/testing/README.md index 030c825..ab7a850 100644 --- a/testing/README.md +++ b/testing/README.md @@ -7,15 +7,15 @@ Docker-based regtest environment for testing with Lightning Network nodes (CLN, 1. **Start services:** ```bash cd testing - docker compose up -d --build --wait + docker compose --env-file ./testing.env up -d --build --wait ``` 2. **Copy environment configuration:** ```bash - cp testing/.env . + cp testing/testing.env ./testing.env ``` -3. **Edit `.env` and change all service names to localhost:** +3. **Edit `testing.env` and change all service names to localhost:** ```shell CLN_HOSTNAME=localhost @@ -30,11 +30,6 @@ POSTGRES_HOSTNAME=localhost cargo test ``` -**Skip integration tests with service dependencies:** -```bash -SWGR_SKIP_INTEGRATION_TESTS=true cargo test -``` - ## Docker-in-Docker CI Testing For running tests inside a container with Docker socket access. @@ -42,18 +37,18 @@ For running tests inside a container with Docker socket access. 1. **Start services:** ```bash cd testing - docker compose up -d --build --wait + docker compose --env-file ./testing.env up -d --build --wait ``` 2. **Connect container to services network:** ```bash - . testing/.env + . testing/testing.env docker network connect $SERVICES_NETWORK_NAME $(hostname) ``` 3. **Copy environment configuration:** ```bash - cp testing/.env . + cp testing/testing.env ./testing.env ``` 4. **Run tests:** diff --git a/testing/src/credentials/db.rs b/testing/src/credentials/db.rs index f22dd88..5f836ce 100644 --- a/testing/src/credentials/db.rs +++ b/testing/src/credentials/db.rs @@ -1,6 +1,6 @@ use crate::credentials::download_credentials; use crate::services::IntegrationTestServices; -use anyhow::Context; +use anyhow::{anyhow, Context}; use std::fs; use std::path::PathBuf; use tempfile::TempDir; @@ -13,15 +13,11 @@ pub struct TestDatabase { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TestDatabases { - pub postgres: Option, - pub mysql: Option, + pub postgres: TestDatabase, + pub mysql: TestDatabase, } pub struct DbCredentials { - inner: Option, -} - -struct DbCredentialsInner { credentials_dir: TempDir, postgres: String, mysql: String, @@ -29,39 +25,19 @@ struct DbCredentialsInner { impl DbCredentials { pub fn create() -> anyhow::Result { - let services = IntegrationTestServices::create()?; - - let inner = match ( - services.credentials(), - services.postgres(), - services.mysql(), - ) { - (Some(credentials), Some(postgres), Some(mysql)) => { - let credentials_dir = TempDir::new()?; - download_credentials(credentials_dir.path(), credentials)?; - Some(DbCredentialsInner { - credentials_dir, - postgres: postgres.to_string(), - mysql: mysql.to_string(), - }) - } - _ => None, - }; - Ok(Self { inner }) + let services = IntegrationTestServices::new(); + + let credentials_dir = TempDir::new()?; + download_credentials(credentials_dir.path(), services.credentials())?; + Ok(Self { + credentials_dir, + postgres: services.postgres().to_string(), + mysql: services.mysql().to_string(), + }) } pub fn get_databases(&self) -> anyhow::Result { - let inner = match &self.inner { - None => { - return Ok(TestDatabases { - postgres: None, - mysql: None, - }) - } - Some(inner) => inner, - }; - - let credentials = inner.credentials_dir.path().join("credentials"); + let credentials = self.credentials_dir.path().join("credentials"); let base_path = credentials.as_path(); let entries = fs::read_dir(base_path) @@ -89,14 +65,14 @@ impl DbCredentials { if dir_name == "postgres" { postgres = Some(TestDatabase { - address: inner.postgres.to_string(), + address: self.postgres.to_string(), ca_cert_path: path.join("server.pem"), }); } if dir_name == "mysql" { mysql = Some(TestDatabase { - address: inner.mysql.to_string(), + address: self.mysql.to_string(), ca_cert_path: path.join("server.pem"), }); } @@ -106,6 +82,19 @@ impl DbCredentials { } } - Ok(TestDatabases { postgres, mysql }) + Ok(TestDatabases { + postgres: postgres.ok_or_else(|| { + anyhow!( + "postgres credentials not found in {}", + self.credentials_dir.path().to_string_lossy() + ) + })?, + mysql: mysql.ok_or_else(|| { + anyhow!( + "mysql credentials not found in {}", + self.credentials_dir.path().to_string_lossy() + ) + })?, + }) } } diff --git a/testing/src/credentials/lightning.rs b/testing/src/credentials/lightning.rs index 4eb98db..230f285 100644 --- a/testing/src/credentials/lightning.rs +++ b/testing/src/credentials/lightning.rs @@ -1,6 +1,6 @@ use crate::credentials::download_credentials; use crate::services::{IntegrationTestServices, LightningIntegrationTestServices}; -use anyhow::Context; +use anyhow::{anyhow, Context}; use secp256k1::PublicKey; use std::fs; use std::path::{Path, PathBuf}; @@ -24,80 +24,42 @@ pub struct LndRegTestLnNode { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum RegTestLnNode { - Cln(ClnRegTestLnNode), - Lnd(LndRegTestLnNode), -} - -impl RegTestLnNode { - pub fn public_key(&self) -> &PublicKey { - match self { - RegTestLnNode::Cln(cln) => &cln.public_key, - RegTestLnNode::Lnd(lnd) => &lnd.public_key, - } - } - - pub fn address(&self) -> &str { - match self { - RegTestLnNode::Cln(cln) => &cln.address, - RegTestLnNode::Lnd(lnd) => &lnd.address, - } - } - - pub fn kind(&self) -> &'static str { - match self { - RegTestLnNode::Cln(_) => "cln", - RegTestLnNode::Lnd(_) => "lnd", - } - } +pub struct RegTestLnNodes { + pub cln: ClnRegTestLnNode, + pub lnd: LndRegTestLnNode, } #[derive(Copy, Clone, Debug)] -pub enum RegTestLnNodeType { +enum RegTestLnNodeType { Cln, Lnd, } pub struct LnCredentials { - inner: Option, -} - -struct LnCredentialsInner { credentials_dir: TempDir, lightning: LightningIntegrationTestServices, } impl LnCredentials { pub fn create() -> anyhow::Result { - let services = IntegrationTestServices::create()?; - let inner = match (services.credentials(), services.lightning()) { - (Some(credentials), Some(lightning)) => { - let credentials_dir = TempDir::new()?; - download_credentials(credentials_dir.path(), credentials)?; - Some(LnCredentialsInner { - credentials_dir, - lightning: lightning.clone(), - }) - } - _ => None, - }; - Ok(Self { inner }) + let services = IntegrationTestServices::new(); + let credentials_dir = TempDir::new()?; + download_credentials(credentials_dir.path(), services.credentials())?; + Ok(Self { + credentials_dir, + lightning: services.lightning().clone(), + }) } - pub fn get_backends(&self) -> anyhow::Result> { - let inner = match &self.inner { - None => return Ok(vec![]), - Some(inner) => inner, - }; - - let credentials = inner.credentials_dir.path().join("credentials"); + pub fn get_backends(&self) -> anyhow::Result { + let credentials = self.credentials_dir.path().join("credentials"); let base_path = credentials.as_path(); let entries = fs::read_dir(base_path) .with_context(|| format!("reading directory {}", base_path.display()))?; - let mut backends = Vec::new(); - + let mut cln = None; + let mut lnd = None; for entry in entries { let entry = entry .with_context(|| format!("reading directory entry in {}", base_path.display(),))?; @@ -136,23 +98,42 @@ impl LnCredentials { format!("parsing {} public key from bytes", node_id_path.display(),) })?; - let node = match node_type { - RegTestLnNodeType::Cln => RegTestLnNode::Cln(Self::build_cln_node( - public_key, - &inner.lightning.cln, - &path, - )?), - RegTestLnNodeType::Lnd => RegTestLnNode::Lnd(Self::build_lnd_node( - public_key, - &inner.lightning.lnd, - &path, - )?), - }; + match node_type { + RegTestLnNodeType::Cln => { + cln = Some(Self::build_cln_node( + public_key, + &self.lightning.cln, + &path, + )?); + } + RegTestLnNodeType::Lnd => { + lnd = Some(Self::build_lnd_node( + public_key, + &self.lightning.lnd, + &path, + )?); + } + } - backends.push(node); + if cln.is_some() && lnd.is_some() { + break; + } } - Ok(backends) + Ok(RegTestLnNodes { + cln: cln.ok_or_else(|| { + anyhow!( + "cln credentials not found in {}", + credentials.to_string_lossy() + ) + })?, + lnd: lnd.ok_or_else(|| { + anyhow!( + "lnd credentials not found in {}", + credentials.to_string_lossy() + ) + })?, + }) } fn build_cln_node( diff --git a/testing/src/services.rs b/testing/src/services.rs index 8be8936..ba71093 100644 --- a/testing/src/services.rs +++ b/testing/src/services.rs @@ -1,13 +1,13 @@ use std::env; -pub const SKIP_INTEGRATION_TESTS_ENV: &str = "SWGR_SKIP_INTEGRATION_TESTS"; +const TESTING_ENV_FILE_PATH: &str = "./testing.env"; #[derive(Debug)] pub struct IntegrationTestServices { - credentials: Option, - postgres: Option, - mysql: Option, - lightning: Option, + credentials: String, + postgres: String, + mysql: String, + lightning: LightningIntegrationTestServices, } #[derive(Debug, Clone)] @@ -16,121 +16,83 @@ pub struct LightningIntegrationTestServices { pub lnd: String, } -impl IntegrationTestServices { - pub fn create() -> anyhow::Result { - let _ = dotenvy::dotenv(); - - let credentials = match Self::env_or_panic("CREDENTIALS_SERVER_PORT") { - None => None, - Some(port) => { - let port = port.parse::()?; - Self::env_or_panic("CREDENTIALS_SERVER_HOSTNAME") - .map(|s| format!("http://{s}:{port}/credentials.tar.gz")) - } - }; - - if credentials.is_none() { - return Ok(Self { - credentials, - postgres: None, - mysql: None, - lightning: None, - }); - } +impl Default for IntegrationTestServices { + fn default() -> Self { + Self::new() + } +} - let postgres = match (&credentials, Self::env_or_panic("POSTGRES_PORT")) { - (Some(_), Some(port)) => { - let port = port.parse::()?; - Self::env_or_panic("POSTGRES_HOSTNAME").map(|s| format!("{s}:{port}")) - } - _ => None, - }; - - let mysql = match (&credentials, Self::env_or_panic("MYSQL_PORT")) { - (Some(_), Some(port)) => { - let port = port.parse::()?; - Self::env_or_panic("MYSQL_HOSTNAME").map(|s| format!("{s}:{port}")) - } - _ => None, - }; - - let cln = match Self::env_or_panic("CLN_PORT") { - None => None, - Some(port) => { - let port = port.parse::()?; - Self::env_or_panic("CLN_HOSTNAME").map(|s| format!("{s}:{port}")) - } - }; - - let lnd = match Self::env_or_panic("LND_PORT") { - None => None, - Some(port) => { - let port = port.parse::()?; - Self::env_or_panic("LND_HOSTNAME").map(|s| format!("{s}:{port}")) - } - }; - - let lightning = match (&credentials, cln, lnd) { - (Some(_), Some(cln), Some(lnd)) => Some(LightningIntegrationTestServices { cln, lnd }), - _ => None, - }; - - Ok(Self { +impl IntegrationTestServices { + pub fn new() -> Self { + let _ = dotenvy::from_filename(TESTING_ENV_FILE_PATH); + + let credentials = format!( + "http://{}:{}/credentials.tar.gz", + Self::env_or_panic("CREDENTIALS_SERVER_HOSTNAME"), + Self::env_or_panic("CREDENTIALS_SERVER_PORT") + ); + + let postgres = format!( + "{}:{}", + Self::env_or_panic("POSTGRES_HOSTNAME"), + Self::env_or_panic("POSTGRES_PORT") + ); + + let mysql = format!( + "{}:{}", + Self::env_or_panic("MYSQL_HOSTNAME"), + Self::env_or_panic("MYSQL_PORT") + ); + + let cln = format!( + "{}:{}", + Self::env_or_panic("CLN_HOSTNAME"), + Self::env_or_panic("CLN_PORT") + ); + + let lnd = format!( + "{}:{}", + Self::env_or_panic("LND_HOSTNAME"), + Self::env_or_panic("LND_PORT") + ); + + Self { credentials, postgres, mysql, - lightning, - }) - } - - fn env_or_panic(config_env: &str) -> Option { - if env::var(SKIP_INTEGRATION_TESTS_ENV).is_ok_and(|s| s.to_lowercase() == "true") { - eprintln!("⚠️ WARNING: {SKIP_INTEGRATION_TESTS_ENV} is true, skipping integration tests for {config_env}"); - return None; + lightning: LightningIntegrationTestServices { cln, lnd }, } + } - match env::var(config_env) { - Ok(r) => Some(r), - Err(_) => { - panic!( - " - -❌❌❌ ERROR ❌❌❌ - -Do one of: - -CONFIGURE INTEGRATION TEST ENVIRONMENT - -* configure integration tests - see testing/README.md -* set env {config_env} to configure the service - -- or - + fn env_or_panic(config_env: &str) -> String { + env::var(config_env).unwrap_or_else(|_| { + panic!( + " -SKIP INTEGRATION TESTS +❌ INVALID INTEGRATION TEST ENVIRONMENT ❌ -* set env {SKIP_INTEGRATION_TESTS_ENV}=true +Env var '{config_env}' is not set. -❌❌❌ ERROR ❌❌❌ +See testing/README.md to configure integration tests and services. -" - ); - } - } +", + ) + }) } - pub fn credentials(&self) -> Option<&String> { - self.credentials.as_ref() + pub fn credentials(&self) -> &str { + &self.credentials } - pub fn postgres(&self) -> Option<&String> { - self.postgres.as_ref() + pub fn postgres(&self) -> &str { + &self.postgres } - pub fn mysql(&self) -> Option<&String> { - self.mysql.as_ref() + pub fn mysql(&self) -> &str { + &self.mysql } - pub fn lightning(&self) -> Option<&LightningIntegrationTestServices> { - self.lightning.as_ref() + pub fn lightning(&self) -> &LightningIntegrationTestServices { + &self.lightning } } diff --git a/testing/.env b/testing/testing.env similarity index 100% rename from testing/.env rename to testing/testing.env index 6ccfc97..98f9eeb 100644 --- a/testing/.env +++ b/testing/testing.env @@ -1,16 +1,16 @@ -POSTGRES_PORT=5432 -POSTGRES_HOSTNAME=postgres - -MYSQL_PORT=3306 -MYSQL_HOSTNAME=mysql +CLN_HOSTNAME=cln +CLN_PORT=9736 -CREDENTIALS_SERVER_PORT=8888 CREDENTIALS_SERVER_HOSTNAME=credentials-server +CREDENTIALS_SERVER_PORT=8888 -CLN_PORT=9736 -CLN_HOSTNAME=cln - -LND_PORT=10009 LND_HOSTNAME=lnd +LND_PORT=10009 + +MYSQL_HOSTNAME=mysql +MYSQL_PORT=3306 + +POSTGRES_HOSTNAME=postgres +POSTGRES_PORT=5432 SERVICES_NETWORK_NAME=services_network \ No newline at end of file