From 1d8689073d30196f5e91bf4e7f5c907d7085a303 Mon Sep 17 00:00:00 2001 From: curtis lee fulton Date: Thu, 4 Dec 2025 15:33:17 -0800 Subject: [PATCH] task: simplify Discovery Model and Service #68 --- Cargo.lock | 23 +- README.md | 47 ++-- doc/discovery-service-openapi.yaml | 144 ++---------- doc/offer-service-openapi.yaml | 103 +-------- .../src/m20220101_000001_create_table.rs | 8 +- pingora/Cargo.toml | 2 + pingora/src/discovery.rs | 140 +++++------- pingora/src/lib.rs | 1 - pingora/src/socket.rs | 26 --- server/Cargo.toml | 6 +- server/src/commands/discovery/backend.rs | 143 ++++++------ server/src/commands/offer/metadata.rs | 15 +- server/src/commands/offer/record.rs | 13 +- server/src/di/delegates.rs | 20 +- server/src/main.rs | 27 ++- server/tests/features/cli_discovery_manage.rs | 34 +-- .../tests/features/common/step_functions.rs | 145 +++++------- server/tests/features/service_logs.rs | 28 ++- service/src/api/discovery.rs | 210 +++++------------- service/src/api/offer.rs | 16 -- service/src/axum/extract/address.rs | 25 --- service/src/axum/extract/mod.rs | 1 - service/src/components/discovery/db.rs | 119 +++------- service/src/components/discovery/http.rs | 51 ++--- service/src/components/discovery/memory.rs | 38 ++-- service/src/components/offer/db.rs | 2 +- service/src/components/pool/default_pool.rs | 7 - service/src/discovery/handler.rs | 59 +++-- service/src/discovery/service.rs | 70 +++--- service/src/offer/handler.rs | 38 +--- service/src/offer/service.rs | 30 +-- service/tests/common/discovery.rs | 100 ++++++--- service/tests/ln/main.rs | 11 +- switchgear/README.md | 47 ++-- 34 files changed, 618 insertions(+), 1131 deletions(-) delete mode 100644 pingora/src/socket.rs delete mode 100644 service/src/axum/extract/address.rs diff --git a/Cargo.lock b/Cargo.lock index bf41307..560a59c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -467,9 +467,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -2201,9 +2201,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -4156,11 +4156,11 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "switchgear" -version = "0.1.20" +version = "0.1.22" [[package]] name = "switchgear-migration" -version = "0.1.20" +version = "0.1.22" dependencies = [ "sea-orm-migration", "tokio", @@ -4168,7 +4168,7 @@ dependencies = [ [[package]] name = "switchgear-pingora" -version = "0.1.20" +version = "0.1.22" dependencies = [ "arc-swap", "async-trait", @@ -4179,6 +4179,8 @@ dependencies = [ "pingora-core", "pingora-error", "pingora-load-balancing", + "rand 0.8.5", + "secp256k1 0.31.1", "switchgear-service", "thiserror 2.0.17", "tokio", @@ -4188,7 +4190,7 @@ dependencies = [ [[package]] name = "switchgear-server" -version = "0.1.20" +version = "0.1.22" dependencies = [ "anyhow", "async-trait", @@ -4211,6 +4213,7 @@ dependencies = [ "reqwest", "rustls", "rustls-pemfile", + "secp256k1 0.31.1", "serde", "serde-saphyr", "serde_json", @@ -4232,7 +4235,7 @@ dependencies = [ [[package]] name = "switchgear-service" -version = "0.1.20" +version = "0.1.22" dependencies = [ "anyhow", "async-trait", @@ -4287,7 +4290,7 @@ dependencies = [ [[package]] name = "switchgear-testing" -version = "0.1.20" +version = "0.1.22" dependencies = [ "anyhow", "dotenvy", diff --git a/README.md b/README.md index 676c494..e17e076 100644 --- a/README.md +++ b/README.md @@ -757,11 +757,9 @@ curl -X POST http://localhost:3001/discovery \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ - "partitions": ["default"], - "address": { - "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - }, + "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "name": "CLN Node 1", + "partitions": ["default"], "weight": 100, "enabled": true, "implementation": { @@ -782,11 +780,9 @@ curl -X POST http://localhost:3001/discovery \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ - "partitions": ["default", "us", "eu"], - "address": { - "url": "https://lnd-node.example.com" - }, + "publicKey": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "name": "LND Node 1", + "partitions": ["default", "us", "eu"], "weight": 50, "enabled": true, "implementation": { @@ -814,21 +810,18 @@ curl -X GET http://localhost:3001/discovery \ ```shell # By public key -curl -X GET "http://localhost:3001/discovery/pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ - -H "Authorization: Bearer $AUTH_TOKEN" - -# By URL (base64 encoded) -curl -X GET "http://localhost:3001/discovery/url/aHR0cHM6Ly9sbmQtbm9kZS5leGFtcGxlLmNvbS8" \ +curl -X GET "http://localhost:3001/discovery/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ -H "Authorization: Bearer $AUTH_TOKEN" ``` #### Update A Backend ```shell -curl -X PUT "http://localhost:3001/discovery/pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ +curl -X PUT "http://localhost:3001/discovery/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ + "name": "CLN Node 1 Updated", "partitions": ["default", "us"], "weight": 200, "enabled": false, @@ -849,7 +842,7 @@ curl -X PUT "http://localhost:3001/discovery/pk/0279be667ef9dcbbac55a06295ce870b #### Delete A Backend ```shell -curl -X DELETE "http://localhost:3001/discovery/pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ +curl -X DELETE "http://localhost:3001/discovery/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ -H "Authorization: Bearer $AUTH_TOKEN" ``` @@ -895,7 +888,7 @@ swgr discovery ls # Get backend details (JSON output) -swgr discovery get pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --output backend-details.json +swgr discovery get 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --output backend-details.json # Get all backends (JSON output) swgr discovery get @@ -904,19 +897,19 @@ swgr discovery get swgr discovery post --input cln-backend.json # Update an existing backend -swgr discovery put pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input updated-backend.json +swgr discovery put 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input updated-backend.json # Patch an existing backend -swgr discovery patch pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input backend-patch.json +swgr discovery patch 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input backend-patch.json # Enable an existing backend -swgr discovery enable pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +swgr discovery enable 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 # Disable an existing backend -swgr discovery disable pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +swgr discovery disable 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 # Delete a backend -swgr discovery delete pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +swgr discovery delete 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 ``` ### Discovery Data Model @@ -926,11 +919,9 @@ Discovery OpenAPI schema: [doc/discovery-service-openapi.yaml](./doc/discovery-s Example CLN backend configuration: ```json { - "partitions": ["default"], - "address": { - "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - }, + "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "name": "CLN Node 1", + "partitions": ["default"], "weight": 1, "enabled": true, "implementation": { @@ -950,11 +941,9 @@ Example CLN backend configuration: Example LND backend configuration: ```json { - "partitions": ["default", "us", "eu"], - "address": { - "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - }, + "publicKey": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "name": "LND Node 1", + "partitions": ["default", "us", "eu"], "weight": 1, "enabled": true, "implementation": { diff --git a/doc/discovery-service-openapi.yaml b/doc/discovery-service-openapi.yaml index bae3677..b612f4d 100644 --- a/doc/discovery-service-openapi.yaml +++ b/doc/discovery-service-openapi.yaml @@ -38,7 +38,6 @@ paths: description: ETag value from a previous response. If it matches the current ETag, returns 304 Not Modified. schema: type: string - example: "12345" responses: '200': description: List of backends @@ -46,62 +45,49 @@ paths: Cache-Control: schema: type: string - example: "no-store, no-cache, must-revalidate" Expires: schema: type: string - example: "Thu, 01 Jan 1970 00:00:00 GMT" Pragma: schema: type: string - example: "no-cache" ETag: schema: type: string description: Current version of the backend list - example: "12345" content: application/json: schema: type: array items: - $ref: '#/components/schemas/DiscoveryBackendRest' + $ref: '#/components/schemas/DiscoveryBackend' '304': description: Not Modified - backend list hasn't changed since the ETag provided in If-None-Match headers: Cache-Control: schema: type: string - example: "no-store, no-cache, must-revalidate" Expires: schema: type: string - example: "Thu, 01 Jan 1970 00:00:00 GMT" Pragma: schema: type: string - example: "no-cache" ETag: schema: type: string description: Current version of the backend list (same as the If-None-Match value) - example: "12345" - /discovery/{addr_variant}/{addr_value}: + /discovery/{public-key}: get: summary: Get specific backend - description: Retrieves detailed information about a specific Lightning node backend identified by its address. + description: Retrieves detailed information about a specific Lightning node backend identified by its public key. parameters: - - name: addr_variant - in: path - required: true - schema: - type: string - enum: [pk, url] - - name: addr_value + - name: public-key in: path required: true schema: type: string + description: Secp256k1 public key in hex format responses: '200': description: Backend details @@ -109,36 +95,28 @@ paths: Cache-Control: schema: type: string - example: "no-store, no-cache, must-revalidate" Expires: schema: type: string - example: "Thu, 01 Jan 1970 00:00:00 GMT" Pragma: schema: type: string - example: "no-cache" content: application/json: schema: - $ref: '#/components/schemas/DiscoveryBackendRest' + $ref: '#/components/schemas/DiscoveryBackend' '404': description: Backend not found put: summary: Create or update backend - description: Creates a new backend or updates an existing one with the provided configuration at the specified address. + description: Creates a new backend or updates an existing one with the provided configuration at the specified public key. parameters: - - name: addr_variant - in: path - required: true - schema: - type: string - enum: [pk, url] - - name: addr_value + - name: public-key in: path required: true schema: type: string + description: Secp256k1 public key in hex format requestBody: required: true content: @@ -154,17 +132,12 @@ paths: summary: Partially update backend description: Updates specific fields of an existing Lightning node backend without requiring the full backend configuration. parameters: - - name: addr_variant - in: path - required: true - schema: - type: string - enum: [pk, url] - - name: addr_value + - name: public-key in: path required: true schema: type: string + description: Secp256k1 public key in hex format requestBody: required: true content: @@ -180,17 +153,12 @@ paths: summary: Remove backend description: Removes a Lightning node backend from the discovery service, preventing it from receiving new traffic. parameters: - - name: addr_variant - in: path - required: true - schema: - type: string - enum: [pk, url] - - name: addr_value + - name: public-key in: path required: true schema: type: string + description: Secp256k1 public key in hex format responses: '204': description: Backend removed @@ -213,81 +181,19 @@ components: schemas: DiscoveryBackend: type: object + description: Complete backend configuration with public key identifier required: - - address + - publicKey - partitions - weight - enabled - implementation properties: - address: - oneOf: - - type: object - required: - - publicKey - properties: - publicKey: - type: string - description: Lightning node public key - - type: object - required: - - url - properties: - url: - type: string - format: uri - description: Backend URL - name: + publicKey: type: string - nullable: true - description: Optional human-readable name for the backend - partitions: - type: array - items: - type: string - description: Load balancing partitions - weight: - type: integer - minimum: 0 - description: Load balancing weight - enabled: - type: boolean - description: Whether the backend is enabled - implementation: - $ref: '#/components/schemas/DiscoveryBackendImplementation' - - DiscoveryBackendRest: - type: object - description: Backend response with location information - required: - - location - - address - - partitions - - weight - - enabled - - implementation - properties: - location: - type: string - description: Resource location path - example: "url/aHR0cHM6Ly8xOTIuMTY4LjEuMTo4MDgwLw" - address: - oneOf: - - type: object - required: - - publicKey - properties: - publicKey: - type: string - description: Lightning node public key - - type: object - required: - - url - properties: - url: - type: string - format: uri - description: Backend URL + description: Secp256k1 public key in hex format (66 hex characters, 33 bytes) + pattern: '^[0-9a-fA-F]{66}$' + example: "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" name: type: string nullable: true @@ -360,13 +266,11 @@ components: oneOf: - $ref: '#/components/schemas/ClnGrpcImplementation' - $ref: '#/components/schemas/LndGrpcImplementation' - - $ref: '#/components/schemas/RemoteHttpImplementation' discriminator: propertyName: type mapping: clnGrpc: '#/components/schemas/ClnGrpcImplementation' lndGrpc: '#/components/schemas/LndGrpcImplementation' - remoteHttp: '#/components/schemas/RemoteHttpImplementation' ClnGrpcImplementation: type: object @@ -455,13 +359,3 @@ components: macaroonPath: type: string description: Path to macaroon file - - RemoteHttpImplementation: - type: object - description: Remote HTTP backend implementation - required: - - type - properties: - type: - type: string - enum: [remoteHttp] \ No newline at end of file diff --git a/doc/offer-service-openapi.yaml b/doc/offer-service-openapi.yaml index 59b3ce6..50cd466 100644 --- a/doc/offer-service-openapi.yaml +++ b/doc/offer-service-openapi.yaml @@ -66,21 +66,18 @@ paths: Cache-Control: schema: type: string - example: "no-store, no-cache, must-revalidate" Expires: schema: type: string - example: "Thu, 01 Jan 1970 00:00:00 GMT" Pragma: schema: type: string - example: "no-cache" content: application/json: schema: type: array items: - $ref: '#/components/schemas/OfferRecordRest' + $ref: '#/components/schemas/OfferRecord' /offers/{partition}/{id}: get: summary: Get specific offer @@ -104,19 +101,16 @@ paths: Cache-Control: schema: type: string - example: "no-store, no-cache, must-revalidate" Expires: schema: type: string - example: "Thu, 01 Jan 1970 00:00:00 GMT" Pragma: schema: type: string - example: "no-cache" content: application/json: schema: - $ref: '#/components/schemas/OfferRecordRest' + $ref: '#/components/schemas/OfferRecord' '404': description: Offer not found put: @@ -223,21 +217,18 @@ paths: Cache-Control: schema: type: string - example: "no-store, no-cache, must-revalidate" Expires: schema: type: string - example: "Thu, 01 Jan 1970 00:00:00 GMT" Pragma: schema: type: string - example: "no-cache" content: application/json: schema: type: array items: - $ref: '#/components/schemas/OfferMetadataRest' + $ref: '#/components/schemas/OfferMetadata' /metadata/{partition}/{id}: get: summary: Get specific metadata @@ -261,19 +252,16 @@ paths: Cache-Control: schema: type: string - example: "no-store, no-cache, must-revalidate" Expires: schema: type: string - example: "Thu, 01 Jan 1970 00:00:00 GMT" Pragma: schema: type: string - example: "no-cache" content: application/json: schema: - $ref: '#/components/schemas/OfferMetadataRest' + $ref: '#/components/schemas/OfferMetadata' '404': description: Metadata not found put: @@ -341,7 +329,7 @@ components: schemas: OfferRecord: type: object - description: Offer request for POST endpoint + description: Complete offer configuration with partition and ID required: - partition - id @@ -381,53 +369,6 @@ components: nullable: true description: Optional expiration timestamp - OfferRecordRest: - type: object - description: Offer response with location information - required: - - location - - partition - - id - - maxSendable - - minSendable - - metadataId - - timestamp - properties: - location: - type: string - description: Resource location path - example: "default/123e4567-e89b-12d3-a456-426614174000" - partition: - type: string - description: Partition name - id: - type: string - format: uuid - description: Unique offer identifier - maxSendable: - type: integer - format: int64 - minimum: 0 - description: Maximum sendable amount in millisatoshis - minSendable: - type: integer - format: int64 - minimum: 0 - description: Minimum sendable amount in millisatoshis - metadataId: - type: string - format: uuid - description: Reference to offer metadata - timestamp: - type: string - format: date-time - description: Creation timestamp - expires: - type: string - format: date-time - nullable: true - description: Optional expiration timestamp - OfferRecordSparse: type: object description: Offer configuration without partition/id (used in PUT requests where partition/id are in path) @@ -463,7 +404,7 @@ components: OfferMetadata: type: object - description: Metadata request for POST endpoint + description: Complete metadata configuration with partition and ID required: - id - partition @@ -488,38 +429,6 @@ components: identifier: $ref: '#/components/schemas/OfferMetadataIdentifier' - OfferMetadataRest: - type: object - description: Metadata response with location information - required: - - location - - id - - partition - - text - properties: - location: - type: string - description: Resource location path - example: "default/123e4567-e89b-12d3-a456-426614174000" - id: - type: string - format: uuid - description: Unique metadata identifier - partition: - type: string - description: Partition name - text: - type: string - description: Short text description - longText: - type: string - nullable: true - description: Optional long text description - image: - $ref: '#/components/schemas/OfferMetadataImage' - identifier: - $ref: '#/components/schemas/OfferMetadataIdentifier' - OfferMetadataSparse: type: object description: Metadata configuration without id/partition (used in PUT requests where id/partition are in path) diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index 8e74209..6f413c7 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -17,8 +17,7 @@ impl MigrationTrait for DiscoveryBackendMigration { .json_binary() .not_null(), ) - .col(string(DiscoveryBackend::Address).not_null()) - .col(string(DiscoveryBackend::AddressType).not_null()) + .col(binary_len(DiscoveryBackend::Id, 33).not_null()) .col(string_null(DiscoveryBackend::Name)) .col(integer(DiscoveryBackend::Weight).not_null()) .col(boolean(DiscoveryBackend::Enabled).not_null()) @@ -29,7 +28,7 @@ impl MigrationTrait for DiscoveryBackendMigration { ) .col(timestamp_with_time_zone(DiscoveryBackend::CreatedAt).not_null()) .col(timestamp_with_time_zone(DiscoveryBackend::UpdatedAt).not_null()) - .primary_key(Index::create().col(DiscoveryBackend::Address)) + .primary_key(Index::create().col(DiscoveryBackend::Id)) .to_owned(), ) .await?; @@ -71,8 +70,7 @@ impl MigrationTrait for DiscoveryBackendMigration { enum DiscoveryBackend { Table, Partitions, - Address, - AddressType, + Id, Name, Weight, Enabled, diff --git a/pingora/Cargo.toml b/pingora/Cargo.toml index 09367ae..00be5be 100644 --- a/pingora/Cargo.toml +++ b/pingora/Cargo.toml @@ -30,5 +30,7 @@ url = "2" [dev-dependencies] chrono = "0.4" +rand = "0.8" +secp256k1 = { version = "0.31", features = ["recovery", "serde"] } tokio = { version = "1", features = ["full"] } uuid = "1" diff --git a/pingora/src/discovery.rs b/pingora/src/discovery.rs index ac8fc2f..4bf56ba 100644 --- a/pingora/src/discovery.rs +++ b/pingora/src/discovery.rs @@ -1,14 +1,15 @@ use crate::error::PingoraLnError; -use crate::socket::IntoPingoraSocketAddr; use crate::{PingoraBackendProvider, PingoraLnBackendExtension}; use arc_swap::ArcSwap; use async_trait::async_trait; use axum::http::Extensions; use log::error; +use pingora_core::protocols::l4::socket::SocketAddr; use pingora_load_balancing::discovery::ServiceDiscovery; use pingora_load_balancing::Backend; use std::collections::{BTreeSet, HashMap, HashSet}; 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; @@ -85,8 +86,17 @@ where ext.insert(PingoraLnBackendExtension { partitions: discovery_backend.backend.partitions.clone(), }); + + let addr = discovery_backend.public_key.serialize(); + let mut hasher = DefaultHasher::new(); + addr.hash(&mut hasher); + let addr = hasher.finish(); + // 👍 + let addr = Ipv6Addr::from_bits(addr as u128); + let addr = SocketAddr::Inet(SocketAddrV6::new(addr, 1, 0, 0).into()); + let pingora_backend = Backend { - addr: discovery_backend.address.as_pingora_socket_addr(), + addr, weight: discovery_backend.backend.weight, ext, }; @@ -166,20 +176,23 @@ mod tests { use async_trait::async_trait; use pingora_load_balancing::discovery::ServiceDiscovery; use pingora_load_balancing::Backend; + use rand::Rng; + use secp256k1::{PublicKey, Secp256k1, SecretKey}; use std::collections::{BTreeSet, HashSet}; use std::hash::{DefaultHasher, Hash, Hasher}; use std::sync::Arc; use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendImplementation, - DiscoveryBackendSparse, DiscoveryBackends, + 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::components::pool::LnClientPool; use switchgear_service::components::pool::LnMetrics; use tokio::sync::Mutex; - use url::Url; struct MockBackendProvider { backends_to_return: Arc>>>, @@ -259,22 +272,31 @@ mod tests { } } - fn create_discovery_backend( - partition: &str, - address: &str, - weight: usize, - enabled: bool, - ) -> DiscoveryBackend { + fn create_discovery_backend(partition: &str, weight: usize, enabled: bool) -> DiscoveryBackend { + let secp = Secp256k1::new(); + let mut rng = rand::thread_rng(); + + let backend_secret_key = SecretKey::from_byte_array(rng.gen::<[u8; 32]>()).unwrap(); + let backend_public_key = PublicKey::from_secret_key(&secp, &backend_secret_key); + DiscoveryBackend { - address: DiscoveryBackendAddress::Url( - Url::parse(&format!("https://{address}")).unwrap(), - ), + public_key: backend_public_key, backend: DiscoveryBackendSparse { name: None, partitions: [partition.to_string()].into(), weight, enabled, - implementation: DiscoveryBackendImplementation::RemoteHttp, + 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, + }, + ), }, } } @@ -300,8 +322,8 @@ mod tests { #[tokio::test] async fn discover_when_new_backends_exist_then_creates_pingora_backends() { - let backend1 = create_discovery_backend("default", "127.0.0.1:8001", 100, true); - let backend2 = create_discovery_backend("default", "127.0.0.1:8002", 200, false); // Disabled backend + let backend1 = create_discovery_backend("default", 100, true); + let backend2 = create_discovery_backend("default", 200, false); let mock_backend_provider = MockBackendProvider { backends_to_return: Arc::new(Mutex::new(Some(BTreeSet::from([ @@ -309,6 +331,7 @@ mod tests { backend2.clone(), ])))), }; + let mock_ln_client_pool = MockLnClientPool { should_fail_connect: false, }; @@ -321,28 +344,26 @@ mod tests { let (pingora_backends, enablement) = discovery.discover().await.unwrap(); assert_eq!(pingora_backends.len(), 2); + assert_eq!(enablement.len(), 2); - let pingora_backends_vec = pingora_backends.iter().collect::>(); - - // prove pingora_backends_vec order is what we expect - assert_eq!(pingora_backends_vec[0].weight, 200); - assert_eq!(pingora_backends_vec[1].weight, 100); + let pingora_backend1 = pingora_backends.iter().find(|b| b.weight == 100).unwrap(); + let pingora_backend2 = pingora_backends.iter().find(|b| b.weight == 200).unwrap(); let mut hasher = DefaultHasher::new(); - pingora_backends_vec[0].hash(&mut hasher); + pingora_backend1.hash(&mut hasher); let pingora_backend1_hash = hasher.finish(); let mut hasher = DefaultHasher::new(); - pingora_backends_vec[1].hash(&mut hasher); + pingora_backend2.hash(&mut hasher); let pingora_backend2_hash = hasher.finish(); assert!( - enablement.get(&pingora_backend2_hash).unwrap(), - "backend 2 enabled" + enablement.get(&pingora_backend1_hash).unwrap(), + "backend 1 enabled" ); assert!( - !enablement.get(&pingora_backend1_hash).unwrap(), - "backend 1 disabled" + !enablement.get(&pingora_backend2_hash).unwrap(), + "backend 2 disabled" ); } @@ -373,7 +394,7 @@ mod tests { #[tokio::test] async fn discover_when_pool_connect_fails_then_returns_empty_backends() { - let backend1 = create_discovery_backend("default", "127.0.0.1:8001", 100, true); + let backend1 = create_discovery_backend("default", 100, true); let mock_backend_provider = MockBackendProvider { backends_to_return: Arc::new(Mutex::new(Some(BTreeSet::from([backend1.clone()])))), }; @@ -396,9 +417,9 @@ mod tests { #[tokio::test] async fn discover_when_some_backends_fail_then_returns_successful_backends() { - let backend1 = create_discovery_backend("default", "127.0.0.1:8001", 100, true); - let backend2 = create_discovery_backend("default", "127.0.0.1:8002", 200, true); - let backend3 = create_discovery_backend("default", "127.0.0.1:8003", 300, true); + let backend1 = create_discovery_backend("default", 100, true); + let backend2 = create_discovery_backend("default", 200, true); + let backend3 = create_discovery_backend("default", 300, true); let mock_backend_provider = MockBackendProvider { backends_to_return: Arc::new(Mutex::new(Some(BTreeSet::from([ @@ -437,11 +458,11 @@ mod tests { _key: Self::Key, backend: &DiscoveryBackend, ) -> Result<(), Self::Error> { - let addr_str = format!("{:?}", backend.address); + let addr_str = backend.public_key.to_string(); if self .fail_addresses .iter() - .any(|fail_addr| addr_str.contains(fail_addr)) + .any(|fail_addr| addr_str.as_str() == fail_addr.as_str()) { Err(PingoraLnError::new( crate::error::PingoraLnErrorSourceKind::PoolError( @@ -461,7 +482,7 @@ mod tests { } let mock_ln_client_pool = SelectiveMockLnClientPool { - fail_addresses: vec!["8002".to_string()], // Fail only the second backend + fail_addresses: vec![backend2.public_key.to_string()], }; let discovery = DefaultPingoraLnDiscovery::new( @@ -482,53 +503,4 @@ mod tests { assert!(backend_weights.contains(&300)); // Third backend assert!(!backend_weights.contains(&200)); // Second backend should be missing } - - #[tokio::test] - async fn discover_when_backend_disabled_then_adds_to_enablement_map() { - let backend1 = create_discovery_backend("default", "127.0.0.1:8001", 100, false); // Disabled - let backend2 = create_discovery_backend("default", "127.0.0.1:8002", 200, true); // Enabled - - let mock_backend_provider = MockBackendProvider { - backends_to_return: Arc::new(Mutex::new(Some(BTreeSet::from([ - backend1.clone(), - backend2.clone(), - ])))), - }; - - let mock_ln_client_pool = MockLnClientPool { - should_fail_connect: false, - }; - let discovery = DefaultPingoraLnDiscovery::new( - mock_backend_provider, - mock_ln_client_pool, - HashSet::from(["default".to_string()]), - ); - - let (pingora_backends, enablement) = discovery.discover().await.unwrap(); - - let pingora_backends_vec = pingora_backends.iter().collect::>(); - - // prove pingora_backends_vec order is what we expect - assert_eq!(pingora_backends_vec[0].weight, 200); - assert_eq!(pingora_backends_vec[1].weight, 100); - - let mut hasher = DefaultHasher::new(); - pingora_backends_vec[0].hash(&mut hasher); - let pingora_backend1_hash = hasher.finish(); - - let mut hasher = DefaultHasher::new(); - pingora_backends_vec[1].hash(&mut hasher); - let pingora_backend2_hash = hasher.finish(); - - assert_eq!(pingora_backends.len(), 2); - - assert!( - !enablement.get(&pingora_backend2_hash).unwrap(), - "backend 2 disabled" - ); - assert!( - enablement.get(&pingora_backend1_hash).unwrap(), - "backend 1 enabled" - ); - } } diff --git a/pingora/src/lib.rs b/pingora/src/lib.rs index e76dac9..0c85a11 100644 --- a/pingora/src/lib.rs +++ b/pingora/src/lib.rs @@ -7,7 +7,6 @@ pub mod balance; pub mod discovery; pub mod error; pub mod health; -mod socket; #[derive(Debug, Clone)] pub struct PingoraLnBackendExtension { diff --git a/pingora/src/socket.rs b/pingora/src/socket.rs deleted file mode 100644 index 951d823..0000000 --- a/pingora/src/socket.rs +++ /dev/null @@ -1,26 +0,0 @@ -use pingora_core::protocols::l4::socket::SocketAddr; -use std::hash::{DefaultHasher, Hash, Hasher}; -use std::net::{Ipv6Addr, SocketAddrV6}; -use switchgear_service::api::discovery::DiscoveryBackendAddress; - -pub trait IntoPingoraSocketAddr { - fn into_pingora_socket_addr(self) -> SocketAddr; - fn as_pingora_socket_addr(&self) -> SocketAddr; -} - -impl IntoPingoraSocketAddr for DiscoveryBackendAddress { - fn into_pingora_socket_addr(self) -> SocketAddr { - let mut hasher = DefaultHasher::new(); - match self { - DiscoveryBackendAddress::PublicKey(h) => h.hash(&mut hasher), - DiscoveryBackendAddress::Url(h) => h.hash(&mut hasher), - } - // 👍 - let addr = Ipv6Addr::from_bits(hasher.finish() as u128); - SocketAddr::Inet(SocketAddrV6::new(addr, 1, 0, 0).into()) - } - - fn as_pingora_socket_addr(&self) -> SocketAddr { - self.clone().into_pingora_socket_addr() - } -} diff --git a/server/Cargo.toml b/server/Cargo.toml index 2dc3552..7d6ccd7 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -31,6 +31,7 @@ pkcs8 = { version = "0.10", features = ["pem"] } rand = "0.8" rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } rustls-pemfile = "2" +secp256k1 = { version = "0.31", features = ["recovery", "serde"] } serde = { version = "1", features = ["derive"] } serde-saphyr = "0.0.10" serde_json = "1" @@ -47,16 +48,17 @@ 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"] } -switchgear-testing.workspace = true +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"] } serde-saphyr = "0.0.10" +switchgear-testing.workspace = true sysinfo = "0.37" tempfile = "3" toml = "0.9" uuid = { version = "1", features = ["v4", "serde"] } -jemallocator = "0.5" [lib] name = "switchgear_server" diff --git a/server/src/commands/discovery/backend.rs b/server/src/commands/discovery/backend.rs index 38377ba..d2f24e2 100644 --- a/server/src/commands/discovery/backend.rs +++ b/server/src/commands/discovery/backend.rs @@ -6,13 +6,11 @@ use rustls::pki_types::pem::PemObject; use rustls::pki_types::CertificateDer; use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; -use std::str::FromStr; use std::time::Duration; use std::{env, fs}; use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendImplementation, - DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendRest, - DiscoveryBackendSparse, DiscoveryBackendStore, + DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatch, + DiscoveryBackendPatchSparse, DiscoveryBackendSparse, DiscoveryBackendStore, }; use switchgear_service::components::discovery::http::HttpDiscoveryBackendStore; use switchgear_service::components::pool::cln::grpc::config::{ @@ -57,8 +55,8 @@ pub enum DiscoveryBackendManagementCommands { /// Get a backend #[command(name = "get")] Get { - /// Optional backend location address, default returns all backends - address: Option, + /// Optional backend public key, default returns all backends + public_key: Option, /// Optional output path, defaults to stdout #[arg(short, long)] output: Option, @@ -79,8 +77,8 @@ pub enum DiscoveryBackendManagementCommands { /// Update or create a backend #[command(name = "put")] Put { - /// Backend location address - address: String, + /// Backend public key + public_key: String, /// Optional backend JSON source path, defaults to stdin #[arg(short, long)] input: Option, @@ -91,8 +89,8 @@ pub enum DiscoveryBackendManagementCommands { /// Patch an existing backend #[command(name = "patch")] Patch { - /// Backend location address - address: String, + /// Backend location public key + public_key: String, /// Optional backend patch JSON source path, defaults to stdin #[arg(short, long)] input: Option, @@ -103,8 +101,8 @@ pub enum DiscoveryBackendManagementCommands { /// Enable an existing backend #[command(name = "enable")] Enable { - /// Backend location address - address: String, + /// Backend location public key + public_key: String, #[clap(flatten)] client: DiscoveryBackendManagementClientConfig, }, @@ -112,8 +110,8 @@ pub enum DiscoveryBackendManagementCommands { /// Disable an existing backend #[command(name = "disable")] Disable { - /// Backend location address - address: String, + /// Backend location public key + public_key: String, #[clap(flatten)] client: DiscoveryBackendManagementClientConfig, }, @@ -121,8 +119,8 @@ pub enum DiscoveryBackendManagementCommands { /// Delete a backend #[command(name = "delete", visible_alias = "rm")] Delete { - /// Backend location address - address: String, + /// Backend location public key + public_key: String, #[clap(flatten)] client: DiscoveryBackendManagementClientConfig, }, @@ -190,7 +188,7 @@ pub fn new_backend( } }; let backend = DiscoveryBackend { - address: DiscoveryBackendAddress::PublicKey(public_key.parse()?), + public_key: public_key.parse()?, backend: DiscoveryBackendSparse { name: name.map(String::from), partitions: [partition.to_string()].into(), @@ -221,16 +219,16 @@ pub async fn list_backends( for backend in backends.backends.into_iter().flatten() { println!( r#" -## Address: {} +## Public key: {} * name: {} * location: {} * enabled: {} * weight: {} "#, - backend.address, + backend.public_key, backend.backend.name.unwrap_or_else(|| "[null]".to_string()), - backend.address.encoded(), + backend.public_key, backend.backend.enabled, backend.backend.weight ); @@ -239,21 +237,18 @@ pub async fn list_backends( } pub async fn get_backend( - address: Option<&str>, + public_key: Option<&str>, output: Option<&Path>, client_configuration: &DiscoveryBackendManagementClientConfig, ) -> anyhow::Result<()> { let client = create_backend_client(client_configuration)?; - if let Some(address) = address { - let address_parsed = DiscoveryBackendAddress::from_str(address) - .with_context(|| format!("reading address: {address}"))?; - if let Some(backend) = client.get(&address_parsed).await? { - let backend = DiscoveryBackendRest { - location: backend.address.encoded(), - backend, - }; + if let Some(public_key) = public_key { + let public_key = public_key + .parse() + .with_context(|| format!("parsing public key: {public_key}"))?; + if let Some(backend) = client.get(&public_key).await? { let backend = serde_json::to_string_pretty(&backend) - .with_context(|| format!("serializing backend {address}"))?; + .with_context(|| format!("serializing backend {}", public_key))?; cli_write_all(output, backend.as_bytes()).with_context(|| { format!( "writing backend to: {}", @@ -262,21 +257,12 @@ pub async fn get_backend( ) })?; } else { - bail!("Backend {address} not found"); + bail!("Backend {public_key} not found"); } } else { let backends = client.get_all(None).await?; - let backends = backends - .backends - .into_iter() - .flatten() - .map(|backend| DiscoveryBackendRest { - location: backend.address.encoded(), - backend, - }) - .collect::>(); - let backends = - serde_json::to_string_pretty(&backends).with_context(|| "serializing backends")?; + let backends = serde_json::to_string_pretty(&backends.backends) + .with_context(|| "serializing backends")?; cli_write_all(output, backends.as_bytes()).with_context(|| { format!( "writing backend to: {}", @@ -307,24 +293,25 @@ pub async fn post_backend( backend_path.map_or_else(|| "stdin".to_string(), |b| b.to_string_lossy().to_string()) ) })?; - let address_encoded = backend.address.encoded(); + let public_key = backend.public_key.to_string(); if let Some(created) = client.post(backend).await? { - info!("Backend created: {}", created.encoded()); + info!("Backend created: {}", created); } else { - bail!("Conflict. A backend already exists at: {address_encoded}",); + bail!("Conflict. A backend already exists at: {public_key}"); } Ok(()) } pub async fn put_backend( - address: &str, + public_key: &str, backend_path: Option<&Path>, client_configuration: &DiscoveryBackendManagementClientConfig, ) -> anyhow::Result<()> { - let client = create_backend_client(client_configuration)?; + let public_key = public_key + .parse() + .with_context(|| format!("parsing public key: {public_key}"))?; - let address = DiscoveryBackendAddress::from_str(address) - .with_context(|| format!("reading address: {address}"))?; + let client = create_backend_client(client_configuration)?; let mut backend = String::new(); cli_read_to_string(backend_path, &mut backend).with_context(|| { @@ -339,25 +326,28 @@ pub async fn put_backend( backend_path.map_or_else(|| "stdin".to_string(), |b| b.to_string_lossy().to_string()) ) })?; - let address_encoded = address.encoded(); - let backend = DiscoveryBackend { address, backend }; + let backend = DiscoveryBackend { + public_key, + backend, + }; if client.put(backend).await? { - info!("Backend created: {address_encoded}"); + info!("Backend created: {public_key}"); } else { - info!("Backend updated: {address_encoded}"); + info!("Backend updated: {public_key}"); } Ok(()) } pub async fn patch_backend( - address: &str, + public_key: &str, backend_path: Option<&Path>, client_configuration: &DiscoveryBackendManagementClientConfig, ) -> anyhow::Result<()> { - let client = create_backend_client(client_configuration)?; + let public_key = public_key + .parse() + .with_context(|| format!("parsing public key: {public_key}"))?; - let address = DiscoveryBackendAddress::from_str(address) - .with_context(|| format!("reading address: {address}"))?; + let client = create_backend_client(client_configuration)?; let mut backend = String::new(); cli_read_to_string(backend_path, &mut backend).with_context(|| { @@ -374,29 +364,31 @@ pub async fn patch_backend( .map_or_else(|| "stdin".to_string(), |b| b.to_string_lossy().to_string()) ) })?; - let address_encoded = address.encoded(); - let backend = DiscoveryBackendPatch { address, backend }; + let backend = DiscoveryBackendPatch { + public_key, + backend, + }; if client.patch(backend).await? { - info!("Backend patched: {address_encoded}"); + info!("Backend patched: {public_key}"); } else { - bail!("Backend not found: {address_encoded}"); + bail!("Backend not found: {public_key}"); } Ok(()) } pub async fn enable_backend( - address: &str, + public_key: &str, enable: bool, client_configuration: &DiscoveryBackendManagementClientConfig, ) -> anyhow::Result<()> { - let client = create_backend_client(client_configuration)?; + let public_key = public_key + .parse() + .with_context(|| format!("parsing public key: {public_key}"))?; - let address = DiscoveryBackendAddress::from_str(address) - .with_context(|| format!("reading address: {address}"))?; + let client = create_backend_client(client_configuration)?; - let address_encoded = address.encoded(); let backend = DiscoveryBackendPatch { - address, + public_key, backend: DiscoveryBackendPatchSparse { name: None, partitions: None, @@ -405,24 +397,25 @@ pub async fn enable_backend( }, }; if client.patch(backend).await? { - info!("Backend patched: {address_encoded}: enabled:{enable}"); + info!("Backend patched: {public_key}: enabled:{enable}"); } else { - bail!("Backend not found: {address_encoded}"); + bail!("Backend not found: {public_key}"); } Ok(()) } pub async fn delete_backend( - address: &str, + public_key: &str, client_configuration: &DiscoveryBackendManagementClientConfig, ) -> anyhow::Result<()> { let client = create_backend_client(client_configuration)?; - let address = DiscoveryBackendAddress::from_str(address) - .with_context(|| format!("reading address: {address}"))?; - if client.delete(&address).await? { - info!("Backend deleted: {}", address.encoded()); + let public_key = public_key + .parse() + .with_context(|| format!("parsing public key: {public_key}"))?; + if client.delete(&public_key).await? { + info!("Backend deleted: {public_key}"); } else { - bail!("Backend not found: {}", address.encoded()); + bail!("Backend not found: {public_key}"); } Ok(()) } diff --git a/server/src/commands/offer/metadata.rs b/server/src/commands/offer/metadata.rs index 10f9f1a..1b4e177 100644 --- a/server/src/commands/offer/metadata.rs +++ b/server/src/commands/offer/metadata.rs @@ -4,9 +4,7 @@ use anyhow::{bail, Context}; use clap::Parser; use log::info; use std::path::{Path, PathBuf}; -use switchgear_service::api::offer::{ - OfferMetadata, OfferMetadataRest, OfferMetadataSparse, OfferMetadataStore, -}; +use switchgear_service::api::offer::{OfferMetadata, OfferMetadataSparse, OfferMetadataStore}; use uuid::Uuid; #[derive(Parser, Debug)] @@ -117,10 +115,6 @@ pub async fn get_metadata( let client = create_offer_client(client_configuration)?; if let Some(id) = id { if let Some(metadata) = client.get_metadata(partition, id).await? { - let metadata = OfferMetadataRest { - location: format!("{}/{}", metadata.partition, metadata.id), - metadata, - }; let metadata = serde_json::to_string_pretty(&metadata) .with_context(|| format!("serializing metadata {id}"))?; cli_write_all(output, metadata.as_bytes()).with_context(|| { @@ -135,13 +129,6 @@ pub async fn get_metadata( } } else { let metadata = client.get_all_metadata(partition, start, count).await?; - let metadata = metadata - .into_iter() - .map(|metadata| OfferMetadataRest { - location: format!("{}/{}", metadata.partition, metadata.id), - metadata, - }) - .collect::>(); let metadata = serde_json::to_string_pretty(&metadata) .with_context(|| format!("serializing metadata for {partition}"))?; cli_write_all(output, metadata.as_bytes()).with_context(|| { diff --git a/server/src/commands/offer/record.rs b/server/src/commands/offer/record.rs index ee5c49b..ec13719 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, OfferRecordRest, OfferRecordSparse, OfferStore}; +use switchgear_service::api::offer::{OfferRecord, OfferRecordSparse, OfferStore}; use uuid::Uuid; #[derive(Parser, Debug)] @@ -122,10 +122,6 @@ pub async fn get_offer( let client = create_offer_client(client_configuration)?; if let Some(id) = id { if let Some(offer) = client.get_offer(partition, id).await? { - let offer = OfferRecordRest { - location: format!("{}/{}", offer.partition, offer.id), - offer, - }; let offer = serde_json::to_string_pretty(&offer) .with_context(|| format!("serializing offer {id}"))?; cli_write_all(output, offer.as_bytes()).with_context(|| { @@ -140,13 +136,6 @@ pub async fn get_offer( } } else { let offers = client.get_offers(partition, start, count).await?; - let offers = offers - .into_iter() - .map(|offer| OfferRecordRest { - location: format!("{}/{}", offer.partition, offer.id), - offer, - }) - .collect::>(); let offers = serde_json::to_string_pretty(&offers) .with_context(|| format!("serializing offer for {partition}"))?; cli_write_all(output, offers.as_bytes()).with_context(|| { diff --git a/server/src/di/delegates.rs b/server/src/di/delegates.rs index 2d2ed6a..b812759 100644 --- a/server/src/di/delegates.rs +++ b/server/src/di/delegates.rs @@ -5,6 +5,7 @@ use crate::di::macros::{ use anyhow::Result; use async_trait::async_trait; use pingora_load_balancing::selection::{Consistent, Random, RoundRobin}; +use secp256k1::PublicKey; use switchgear_pingora::balance::{ ConsistentMaxIterations, RandomMaxIterations, RoundRobinMaxIterations, }; @@ -12,8 +13,7 @@ use switchgear_pingora::error::PingoraLnError; use switchgear_pingora::PingoraBackendProvider; use switchgear_service::api::balance::{LnBalancer, LnBalancerBackgroundServices}; use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendPatch, DiscoveryBackendStore, - DiscoveryBackends, + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, }; use switchgear_service::api::offer::Offer; use switchgear_service::api::offer::{OfferMetadataStore, OfferProvider, OfferStore}; @@ -205,21 +205,15 @@ pub enum DiscoveryBackendStoreDelegate { impl DiscoveryBackendStore for DiscoveryBackendStoreDelegate { type Error = DiscoveryBackendStoreError; - async fn get( - &self, - addr: &DiscoveryBackendAddress, - ) -> Result, Self::Error> { - delegate_to_discovery_store_variants!(self, get, addr).await + async fn get(&self, public_key: &PublicKey) -> Result, Self::Error> { + delegate_to_discovery_store_variants!(self, get, public_key).await } async fn get_all(&self, etag: Option) -> Result { delegate_to_discovery_store_variants!(self, get_all, etag).await } - async fn post( - &self, - backend: DiscoveryBackend, - ) -> Result, Self::Error> { + async fn post(&self, backend: DiscoveryBackend) -> Result, Self::Error> { delegate_to_discovery_store_variants!(self, post, backend).await } @@ -234,8 +228,8 @@ impl DiscoveryBackendStore for DiscoveryBackendStoreDelegate { delegate_to_discovery_store_variants!(self, patch, backend).await } - async fn delete(&self, addr: &DiscoveryBackendAddress) -> Result { - delegate_to_discovery_store_variants!(self, delete, addr).await + async fn delete(&self, public_key: &PublicKey) -> Result { + delegate_to_discovery_store_variants!(self, delete, public_key).await } } diff --git a/server/src/main.rs b/server/src/main.rs index 4d47386..e77d310 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -271,7 +271,7 @@ async fn _main(args: CliArgs) -> anyhow::Result<()> { commands::discovery::backend::list_backends(&client).await } DiscoveryBackendManagementCommands::Get { - address, + public_key: address, output, client, } => { @@ -286,7 +286,7 @@ async fn _main(args: CliArgs) -> anyhow::Result<()> { commands::discovery::backend::post_backend(input.as_deref(), &client).await } DiscoveryBackendManagementCommands::Put { - address, + public_key: address, input, client, } => { @@ -294,22 +294,25 @@ async fn _main(args: CliArgs) -> anyhow::Result<()> { .await } DiscoveryBackendManagementCommands::Patch { - address, + public_key: address, input, client, } => { commands::discovery::backend::patch_backend(&address, input.as_deref(), &client) .await } - DiscoveryBackendManagementCommands::Enable { address, client } => { - commands::discovery::backend::enable_backend(&address, true, &client).await - } - DiscoveryBackendManagementCommands::Disable { address, client } => { - commands::discovery::backend::enable_backend(&address, false, &client).await - } - DiscoveryBackendManagementCommands::Delete { address, client } => { - commands::discovery::backend::delete_backend(&address, &client).await - } + DiscoveryBackendManagementCommands::Enable { + public_key: address, + client, + } => commands::discovery::backend::enable_backend(&address, true, &client).await, + DiscoveryBackendManagementCommands::Disable { + public_key: address, + client, + } => commands::discovery::backend::enable_backend(&address, false, &client).await, + DiscoveryBackendManagementCommands::Delete { + public_key: address, + client, + } => commands::discovery::backend::delete_backend(&address, &client).await, }, }, } diff --git a/server/tests/features/cli_discovery_manage.rs b/server/tests/features/cli_discovery_manage.rs index 4641545..8ecce13 100644 --- a/server/tests/features/cli_discovery_manage.rs +++ b/server/tests/features/cli_discovery_manage.rs @@ -341,7 +341,7 @@ async fn test_discovery_get() { step_when_i_run_swgr_discovery_get( &mut ctx, &mut cli_ctx, - &expected_backend.address.encoded(), + &expected_backend.public_key.to_string(), root_location, ) .await @@ -475,7 +475,7 @@ async fn test_discovery_put() { step_given_a_valid_backend_json_exists(&mut ctx, &mut cli_ctx) .await .expect("assert"); - let backend_address = extract_backend_address(&cli_ctx).await.expect("assert"); + let backend_address = extract_backend_public_key(&cli_ctx).await.expect("assert"); step_when_i_run_swgr_discovery_post(&mut ctx, &mut cli_ctx, CertificateLocation::Arg) .await .expect("assert"); @@ -487,7 +487,7 @@ async fn test_discovery_put() { .expect("assert"); // Scenario steps - step_when_i_run_swgr_discovery_put(&mut ctx, &mut cli_ctx, &backend_address) + step_when_i_run_swgr_discovery_put(&mut ctx, &mut cli_ctx, &backend_address.to_string()) .await .expect("assert"); step_then_the_command_should_succeed(&mut cli_ctx) @@ -496,7 +496,7 @@ async fn test_discovery_put() { step_when_i_run_swgr_discovery_get( &mut ctx, &mut cli_ctx, - &backend_address, + &backend_address.to_string(), CertificateLocation::Arg, ) .await @@ -559,7 +559,7 @@ async fn test_discovery_delete() { step_given_a_valid_backend_json_exists(&mut ctx, &mut cli_ctx) .await .expect("assert"); - let backend_address = extract_backend_address(&cli_ctx).await.expect("assert"); + let backend_address = extract_backend_public_key(&cli_ctx).await.expect("assert"); step_when_i_run_swgr_discovery_post(&mut ctx, &mut cli_ctx, CertificateLocation::Arg) .await .expect("assert"); @@ -568,7 +568,7 @@ async fn test_discovery_delete() { .expect("assert"); // Scenario steps - step_when_i_run_swgr_discovery_delete(&mut ctx, &mut cli_ctx, &backend_address) + step_when_i_run_swgr_discovery_delete(&mut ctx, &mut cli_ctx, &backend_address.to_string()) .await .expect("assert"); step_then_the_command_should_succeed(&mut cli_ctx) @@ -577,7 +577,7 @@ async fn test_discovery_delete() { step_when_i_run_swgr_discovery_get( &mut ctx, &mut cli_ctx, - &backend_address, + &backend_address.to_string(), CertificateLocation::Arg, ) .await @@ -640,7 +640,7 @@ async fn test_discovery_patch() { step_given_a_valid_backend_json_exists(&mut ctx, &mut cli_ctx) .await .expect("assert"); - let backend_address = extract_backend_address(&cli_ctx).await.expect("assert"); + let backend_address = extract_backend_public_key(&cli_ctx).await.expect("assert"); step_when_i_run_swgr_discovery_post(&mut ctx, &mut cli_ctx, CertificateLocation::Arg) .await .expect("assert"); @@ -652,7 +652,7 @@ async fn test_discovery_patch() { .expect("assert"); // Scenario steps - step_when_i_run_swgr_discovery_patch(&mut ctx, &mut cli_ctx, &backend_address) + step_when_i_run_swgr_discovery_patch(&mut ctx, &mut cli_ctx, &backend_address.to_string()) .await .expect("assert"); step_then_the_command_should_succeed(&mut cli_ctx) @@ -661,7 +661,7 @@ async fn test_discovery_patch() { step_when_i_run_swgr_discovery_get( &mut ctx, &mut cli_ctx, - &backend_address, + &backend_address.to_string(), CertificateLocation::Arg, ) .await @@ -724,7 +724,7 @@ async fn test_discovery_enable() { step_given_a_valid_backend_json_exists(&mut ctx, &mut cli_ctx) .await .expect("assert"); - let backend_address = extract_backend_address(&cli_ctx).await.expect("assert"); + let backend_address = extract_backend_public_key(&cli_ctx).await.expect("assert"); step_when_i_run_swgr_discovery_post(&mut ctx, &mut cli_ctx, CertificateLocation::Arg) .await .expect("assert"); @@ -733,13 +733,13 @@ async fn test_discovery_enable() { .expect("assert"); // Scenario steps - disable first then enable - step_when_i_run_swgr_discovery_disable(&mut ctx, &mut cli_ctx, &backend_address) + step_when_i_run_swgr_discovery_disable(&mut ctx, &mut cli_ctx, &backend_address.to_string()) .await .expect("assert"); step_then_the_command_should_succeed(&mut cli_ctx) .await .expect("assert"); - step_when_i_run_swgr_discovery_enable(&mut ctx, &mut cli_ctx, &backend_address) + step_when_i_run_swgr_discovery_enable(&mut ctx, &mut cli_ctx, &backend_address.to_string()) .await .expect("assert"); step_then_the_command_should_succeed(&mut cli_ctx) @@ -748,7 +748,7 @@ async fn test_discovery_enable() { step_when_i_run_swgr_discovery_get( &mut ctx, &mut cli_ctx, - &backend_address, + &backend_address.to_string(), CertificateLocation::Arg, ) .await @@ -811,7 +811,7 @@ async fn test_discovery_disable() { step_given_a_valid_backend_json_exists(&mut ctx, &mut cli_ctx) .await .expect("assert"); - let backend_address = extract_backend_address(&cli_ctx).await.expect("assert"); + let backend_address = extract_backend_public_key(&cli_ctx).await.expect("assert"); step_when_i_run_swgr_discovery_post(&mut ctx, &mut cli_ctx, CertificateLocation::Arg) .await .expect("assert"); @@ -820,7 +820,7 @@ async fn test_discovery_disable() { .expect("assert"); // Scenario steps - step_when_i_run_swgr_discovery_disable(&mut ctx, &mut cli_ctx, &backend_address) + step_when_i_run_swgr_discovery_disable(&mut ctx, &mut cli_ctx, &backend_address.to_string()) .await .expect("assert"); step_then_the_command_should_succeed(&mut cli_ctx) @@ -829,7 +829,7 @@ async fn test_discovery_disable() { step_when_i_run_swgr_discovery_get( &mut ctx, &mut cli_ctx, - &backend_address, + &backend_address.to_string(), CertificateLocation::Arg, ) .await diff --git a/server/tests/features/common/step_functions.rs b/server/tests/features/common/step_functions.rs index 2d29d13..076afb0 100644 --- a/server/tests/features/common/step_functions.rs +++ b/server/tests/features/common/step_functions.rs @@ -15,13 +15,12 @@ use anyhow::{anyhow, Context, Result}; use chrono::{Duration as ChronoDuration, Utc}; use rand::{distributions::Alphanumeric, Rng}; use reqwest::{StatusCode, Url}; -use std::str::FromStr; +use secp256k1::PublicKey; use std::time::{Duration, SystemTime}; use std::vec; use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendImplementation, - DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, - DiscoveryBackendStore, + DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatch, + DiscoveryBackendPatchSparse, DiscoveryBackendSparse, DiscoveryBackendStore, }; use switchgear_service::api::offer::{ OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, OfferRecordSparse, @@ -431,8 +430,6 @@ pub async fn step_when_the_payee_registers_their_lightning_node_as_a_backend( let client = ctx.get_active_discovery_client()?; - let address = DiscoveryBackendAddress::PublicKey(*node.public_key()); - let url = Url::parse(&format!("https://{}", node.address()))?; let implementation = match &node { @@ -469,7 +466,7 @@ pub async fn step_when_the_payee_registers_their_lightning_node_as_a_backend( }; let backend = DiscoveryBackend { - address, + public_key: *node.public_key(), backend: DiscoveryBackendSparse { name: None, partitions: ["default".to_string()].into(), @@ -709,8 +706,6 @@ pub async fn register_payee_node_as_backend(ctx: &mut GlobalContext, payee_id: & let client = ctx.get_active_discovery_client()?; - let address = DiscoveryBackendAddress::PublicKey(*node.public_key()); - let url = Url::parse(&format!("https://{}", node.address()))?; let implementation = match &node { @@ -739,7 +734,7 @@ pub async fn register_payee_node_as_backend(ctx: &mut GlobalContext, payee_id: & }; let backend = DiscoveryBackend { - address, + public_key: *node.public_key(), backend: DiscoveryBackendSparse { name: None, partitions: ["default".to_string()].into(), @@ -917,11 +912,10 @@ pub async fn step_when_the_admin_disables_the_first_backend(ctx: &mut GlobalCont // Get LND backend location let lnd_payee = ctx .get_payee("lnd") - .ok_or_else(|| anyhow_log!("LND payee not found in context"))?; + .ok_or_else(|| anyhow_log!("LND payee not found in context"))? + .clone(); - let backend_location = DiscoveryBackendAddress::PublicKey(*lnd_payee.node.public_key()); - let backend_location = backend_location.encoded(); - enable_disable_backend(ctx, &backend_location, false).await?; + enable_disable_backend(ctx, lnd_payee.node.public_key(), false).await?; Ok(()) } @@ -945,11 +939,10 @@ pub async fn step_when_the_admin_disables_the_second_backend( // Get CLN backend location let cln_payee = ctx .get_payee("cln") - .ok_or_else(|| anyhow_log!("CLN payee not found in context"))?; + .ok_or_else(|| anyhow_log!("CLN payee not found in context"))? + .clone(); - let backend_location = DiscoveryBackendAddress::PublicKey(*cln_payee.node.public_key()); - let backend_location = backend_location.encoded(); - enable_disable_backend(ctx, &backend_location, false).await?; + enable_disable_backend(ctx, cln_payee.node.public_key(), false).await?; Ok(()) } @@ -1004,11 +997,10 @@ pub async fn step_when_the_admin_enables_any_backend(ctx: &mut GlobalContext) -> // Re-enable LND backend let lnd_payee = ctx .get_payee("lnd") - .ok_or_else(|| anyhow_log!("LND payee not found in context"))?; + .ok_or_else(|| anyhow_log!("LND payee not found in context"))? + .clone(); - let backend_location = DiscoveryBackendAddress::PublicKey(*lnd_payee.node.public_key()); - let backend_location = backend_location.encoded(); - enable_disable_backend(ctx, &backend_location, true).await?; + enable_disable_backend(ctx, lnd_payee.node.public_key(), true).await?; Ok(()) } @@ -1026,16 +1018,13 @@ pub async fn step_then_the_payer_can_again_generate_invoices( async fn enable_disable_backend( ctx: &mut GlobalContext, - location: &str, + public_key: &PublicKey, enabled: bool, ) -> Result<()> { let client = ctx.get_active_discovery_client()?; - // Parse the location to get the RawSocketAddress - let address = DiscoveryBackendAddress::from_str(location)?; - let patch = DiscoveryBackendPatch { - address: address.clone(), + public_key: *public_key, backend: DiscoveryBackendPatchSparse { name: None, partitions: None, @@ -1047,7 +1036,7 @@ async fn enable_disable_backend( // PATCH the backend let patched = client.patch(patch).await?; if !patched { - bail_log!("PATCH {address} failed"); + bail_log!("PATCH {public_key} failed"); } Ok(()) @@ -1059,11 +1048,10 @@ pub async fn step_when_the_admin_deletes_the_first_backend(ctx: &mut GlobalConte // Get LND backend location let lnd_payee = ctx .get_payee("lnd") - .ok_or_else(|| anyhow_log!("LND payee not found in context"))?; + .ok_or_else(|| anyhow_log!("LND payee not found in context"))? + .clone(); - let backend_location = DiscoveryBackendAddress::PublicKey(*lnd_payee.node.public_key()); - let backend_location = backend_location.encoded(); - delete_backend(ctx, &backend_location).await?; + delete_backend(ctx, lnd_payee.node.public_key()).await?; Ok(()) } @@ -1074,11 +1062,10 @@ pub async fn step_when_the_admin_deletes_the_second_backend(ctx: &mut GlobalCont // Get CLN backend location let cln_payee = ctx .get_payee("cln") - .ok_or_else(|| anyhow_log!("CLN payee not found in context"))?; + .ok_or_else(|| anyhow_log!("CLN payee not found in context"))? + .clone(); - let backend_location = DiscoveryBackendAddress::PublicKey(*cln_payee.node.public_key()); - let backend_location = backend_location.encoded(); - delete_backend(ctx, &backend_location).await?; + delete_backend(ctx, cln_payee.node.public_key()).await?; Ok(()) } @@ -1100,14 +1087,11 @@ pub async fn step_and_both_nodes_are_ready_to_be_registered_as_backends( } /// Helper function to delete backends via the discovery API -async fn delete_backend(ctx: &mut GlobalContext, location: &str) -> Result<()> { +async fn delete_backend(ctx: &mut GlobalContext, public_key: &PublicKey) -> Result<()> { let client = ctx.get_active_discovery_client()?; - // Parse the encoded location back to RawSocketAddress - let address: DiscoveryBackendAddress = location.parse()?; - // Delete the backend - client.delete(&address).await?; + client.delete(public_key).await?; Ok(()) } @@ -1272,11 +1256,13 @@ pub async fn step_when_i_request_an_invoice_for_a_non_existent_offer( /// Step: "When I try to get a missing backend" /// Attempts to get a backend that doesn't exist to trigger error logging -pub async fn step_when_i_try_to_get_a_missing_backend(ctx: &mut GlobalContext) -> Result<()> { +pub async fn step_when_i_try_to_get_a_missing_backend( + ctx: &mut GlobalContext, + public_key: &PublicKey, +) -> Result<()> { let client = ctx.get_active_discovery_client()?; - let address = DiscoveryBackendAddress::Url(Url::parse("http://fake.com")?); - let backend = client.get(&address).await?; + let backend = client.get(public_key).await?; if backend.is_some() { bail_log!("backend found unexpectedly") @@ -1561,6 +1547,7 @@ pub async fn step_and_the_server_logs_should_contain_invalid_offer_error_respons /// Verifies that invalid backend get errors are logged pub async fn step_and_the_server_logs_should_contain_invalid_backend_get_errors( ctx: &mut GlobalContext, + invalid_backend_patterns: &[&str], ) -> Result<()> { // Get captured server logs from stderr buffer let stderr_lines = if let Ok(lines) = ctx.get_active_stderr_buffer()?.lock() { @@ -1575,14 +1562,8 @@ pub async fn step_and_the_server_logs_should_contain_invalid_backend_get_errors( let server_logs = stderr_lines.join("\n"); - let invalid_backend_patterns = [ - "clf::discovery", - "GET /discovery/url/aHR0cDovL2Zha2UuY29tLw", - "HTTP/1.1 404", - " WARN ", - ]; let expected_count = 1; // We make exactly 1 invalid backend GET request in the test - let invalid_backend_count = count_log_patterns(&server_logs, &invalid_backend_patterns); + let invalid_backend_count = count_log_patterns(&server_logs, invalid_backend_patterns); if invalid_backend_count == expected_count { Ok(()) @@ -2528,11 +2509,11 @@ pub async fn step_then_the_backend_json_file_should_exist( }; // Verify expected values match - if backend.address.encoded() != expected_backend.address.encoded() { + if backend.public_key != expected_backend.public_key { bail_log!( - "Backend address mismatch. Expected: {}, Got: {}", - expected_backend.address.encoded(), - backend.address.encoded() + "Backend public key mismatch. Expected: {}, Got: {}", + expected_backend.public_key, + backend.public_key ); } @@ -2642,15 +2623,11 @@ pub async fn step_when_i_run_swgr_discovery_post( Ok(()) } -pub async fn extract_backend_address(cli_ctx: &CliContext) -> Result { +pub async fn extract_backend_public_key(cli_ctx: &CliContext) -> Result { // Load the backend JSON to extract the address and partition let content = std::fs::read_to_string(&cli_ctx.backend_json_path)?; let backend: DiscoveryBackend = serde_json::from_str(&content)?; - - // Use the encoded() method to get the properly formatted address string - let address = backend.address.encoded(); - - Ok(address) + Ok(backend.public_key) } /// Extract backend information from the CLI context's backend JSON @@ -2751,10 +2728,10 @@ pub async fn step_then_backend_list_should_be_output( for expected in expected_backends { let entry = format!( - "## Address: {} * name: {} * location: {} * enabled: {} * weight: {}", - expected.address, + "## Public key: {} * name: {} * location: {} * enabled: {} * weight: {}", + expected.public_key, expected.backend.name.as_deref().unwrap_or("[null]"), - expected.address.encoded(), + expected.public_key, expected.backend.enabled, expected.backend.weight ); @@ -2881,11 +2858,11 @@ pub async fn step_then_backend_details_should_be_output( }; // Verify expected values match - if backend.address.encoded() != expected_backend.address.encoded() { + if backend.public_key != expected_backend.public_key { bail_log!( "Backend address mismatch. Expected: {}, Got: {}", - expected_backend.address.encoded(), - backend.address.encoded() + expected_backend.public_key, + backend.public_key ); } @@ -2934,7 +2911,7 @@ pub async fn step_then_all_backends_should_be_output( // Verify the expected backend is in the array let found = backends.iter().any(|backend| { - backend.address.encoded() == expected_backend.address.encoded() + backend.public_key == expected_backend.public_key && backend.backend.weight == expected_backend.backend.weight && backend.backend.enabled == expected_backend.backend.enabled }); @@ -2942,7 +2919,7 @@ pub async fn step_then_all_backends_should_be_output( if !found { bail_log!( "Expected backend with address {} not found in output. Got {} backends", - expected_backend.address.encoded(), + expected_backend.public_key, backends.len() ); } @@ -3279,8 +3256,8 @@ pub async fn step_when_i_run_swgr_discovery_get_for_non_existent_backend( ctx: &mut GlobalContext, cli_ctx: &mut CliContext, ) -> Result<()> { - let non_existent_address = - "pk/03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + let non_existent_public_key = + "03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; let discovery_profile = ctx.get_active_discovery_service_profile()?; let base_url = format!( @@ -3299,7 +3276,7 @@ pub async fn step_when_i_run_swgr_discovery_get_for_non_existent_backend( let args = vec![ "discovery", "get", - non_existent_address, + non_existent_public_key, "--base-url", &base_url, "--trusted-roots", @@ -3319,8 +3296,8 @@ pub async fn step_when_i_run_swgr_discovery_patch_for_non_existent_backend( ctx: &mut GlobalContext, cli_ctx: &mut CliContext, ) -> Result<()> { - let non_existent_address = - "pk/03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + let non_existent_public_key = + "03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; let discovery_profile = ctx.get_active_discovery_service_profile()?; let base_url = format!( @@ -3340,7 +3317,7 @@ pub async fn step_when_i_run_swgr_discovery_patch_for_non_existent_backend( let args = vec![ "discovery", "patch", - non_existent_address, + non_existent_public_key, "--base-url", &base_url, "--trusted-roots", @@ -3362,8 +3339,8 @@ pub async fn step_when_i_run_swgr_discovery_enable_for_non_existent_backend( ctx: &mut GlobalContext, cli_ctx: &mut CliContext, ) -> Result<()> { - let non_existent_address = - "pk/03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + let non_existent_public_key = + "03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; let discovery_profile = ctx.get_active_discovery_service_profile()?; let base_url = format!( @@ -3382,7 +3359,7 @@ pub async fn step_when_i_run_swgr_discovery_enable_for_non_existent_backend( let args = vec![ "discovery", "enable", - non_existent_address, + non_existent_public_key, "--base-url", &base_url, "--trusted-roots", @@ -3402,8 +3379,8 @@ pub async fn step_when_i_run_swgr_discovery_disable_for_non_existent_backend( ctx: &mut GlobalContext, cli_ctx: &mut CliContext, ) -> Result<()> { - let non_existent_address = - "pk/03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + let non_existent_public_key = + "03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; let discovery_profile = ctx.get_active_discovery_service_profile()?; let base_url = format!( @@ -3422,7 +3399,7 @@ pub async fn step_when_i_run_swgr_discovery_disable_for_non_existent_backend( let args = vec![ "discovery", "disable", - non_existent_address, + non_existent_public_key, "--base-url", &base_url, "--trusted-roots", @@ -3442,8 +3419,8 @@ pub async fn step_when_i_run_swgr_discovery_delete_for_non_existent_backend( ctx: &mut GlobalContext, cli_ctx: &mut CliContext, ) -> Result<()> { - let non_existent_address = - "pk/03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + let non_existent_public_key = + "03eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; let discovery_profile = ctx.get_active_discovery_service_profile()?; let base_url = format!( @@ -3462,7 +3439,7 @@ pub async fn step_when_i_run_swgr_discovery_delete_for_non_existent_backend( let args = vec![ "discovery", "delete", - non_existent_address, + non_existent_public_key, "--base-url", &base_url, "--trusted-roots", diff --git a/server/tests/features/service_logs.rs b/server/tests/features/service_logs.rs index 0009a9c..fb52172 100644 --- a/server/tests/features/service_logs.rs +++ b/server/tests/features/service_logs.rs @@ -2,6 +2,8 @@ use crate::common::context::global::GlobalContext; use crate::common::context::Protocol; use crate::common::step_functions::*; use crate::FEATURE_TEST_CONFIG_PATH; +use rand::Rng; +use secp256k1::{PublicKey, Secp256k1, SecretKey}; use std::path::PathBuf; use switchgear_testing::credentials::lightning::RegTestLnNodeType; @@ -199,7 +201,16 @@ async fn test_error_conditions_are_properly_logged() { step_when_i_request_an_invoice_for_a_non_existent_offer(&mut ctx) .await .expect("assert"); - step_when_i_try_to_get_a_missing_backend(&mut ctx) + + let secp = Secp256k1::new(); + let mut rng = rand::thread_rng(); + + let missing_backend_private_key = + SecretKey::from_byte_array(rng.gen::<[u8; 32]>()).expect("assert"); + let missing_backend_public_key = + PublicKey::from_secret_key(&secp, &missing_backend_private_key); + + step_when_i_try_to_get_a_missing_backend(&mut ctx, &missing_backend_public_key) .await .expect("assert"); @@ -218,9 +229,18 @@ async fn test_error_conditions_are_properly_logged() { step_and_the_server_logs_should_contain_invalid_offer_error_responses(&mut ctx) .await .expect("assert"); - step_and_the_server_logs_should_contain_invalid_backend_get_errors(&mut ctx) - .await - .expect("assert"); + let invalid_backend_patterns = [ + "clf::discovery", + &format!("GET /discovery/{}", missing_backend_public_key), + "HTTP/1.1 404", + " WARN ", + ]; + step_and_the_server_logs_should_contain_invalid_backend_get_errors( + &mut ctx, + &invalid_backend_patterns, + ) + .await + .expect("assert"); ctx.stop_all_servers().expect("assert"); } diff --git a/service/src/api/discovery.rs b/service/src/api/discovery.rs index e93f0aa..2654e66 100644 --- a/service/src/api/discovery.rs +++ b/service/src/api/discovery.rs @@ -2,38 +2,27 @@ 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 base64::engine::general_purpose::URL_SAFE_NO_PAD; -use base64::Engine; use secp256k1::PublicKey; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use std::error::Error; -use std::fmt::{Display, Formatter}; use std::io; -use std::str::FromStr; -use url::Url; #[async_trait] pub trait DiscoveryBackendStore { type Error: Error + Send + Sync + 'static + HasServiceErrorSource; - async fn get( - &self, - addr: &DiscoveryBackendAddress, - ) -> Result, Self::Error>; + 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 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, addr: &DiscoveryBackendAddress) -> Result; + async fn delete(&self, public_key: &PublicKey) -> Result; } #[async_trait] @@ -68,35 +57,11 @@ impl DiscoveryBackends { #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DiscoveryBackend { - pub address: DiscoveryBackendAddress, + 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 DiscoveryBackendRest { - pub location: String, - #[serde(flatten)] - pub backend: DiscoveryBackend, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum DiscoveryBackendAddress { - PublicKey(PublicKey), - Url(Url), -} - -impl Display for DiscoveryBackendAddress { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - DiscoveryBackendAddress::PublicKey(addr) => write!(f, "{addr}"), - DiscoveryBackendAddress::Url(addr) => write!(f, "{addr}"), - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DiscoveryBackendSparse { @@ -111,7 +76,7 @@ pub struct DiscoveryBackendSparse { #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DiscoveryBackendPatch { - pub address: DiscoveryBackendAddress, + pub public_key: PublicKey, #[serde(flatten)] pub backend: DiscoveryBackendPatchSparse, } @@ -129,155 +94,90 @@ pub struct DiscoveryBackendPatchSparse { pub enabled: Option, } -impl DiscoveryBackendAddress { - pub fn encoded(&self) -> String { - match self { - DiscoveryBackendAddress::PublicKey(k) => format!("pk/{k}"), - DiscoveryBackendAddress::Url(u) => { - format!("url/{}", URL_SAFE_NO_PAD.encode(u.to_string().as_bytes())) - } - } - } -} - -impl FromStr for DiscoveryBackendAddress { - type Err = io::Error; - fn from_str(s: &str) -> io::Result { - let parts: Vec<&str> = s.splitn(2, '/').collect(); - if parts.len() != 2 { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "invalid format: expected 'variant/base64'", - )); - } - - let variant = parts[0]; - let encoded_addr = parts[1]; - - match variant { - "pk" => { - let pk = encoded_addr - .parse() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - Ok(Self::PublicKey(pk)) - } - "url" => { - let url = URL_SAFE_NO_PAD - .decode(encoded_addr) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let url = str::from_utf8(&url) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let url = - Url::parse(url).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - Ok(Self::Url(url)) - } - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("unknown variant '{variant}'"), - )), - } - } -} - -impl TryFrom<(S, S)> for DiscoveryBackendAddress -where - S: AsRef + Display, -{ - type Error = io::Error; - - fn try_from(value: (S, S)) -> Result { - let formatted_str = format!("{}/{}", value.0, value.1); - Self::from_str(&formatted_str) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] pub enum DiscoveryBackendImplementation { ClnGrpc(ClnGrpcDiscoveryBackendImplementation), LndGrpc(LndGrpcDiscoveryBackendImplementation), - RemoteHttp, } #[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 key = SecretKey::from_byte_array([ + 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 key = key.public_key(&Secp256k1::new()); - - let backend = DiscoveryBackendSparse { - name: None, - partitions: ["default".to_string()].into(), - weight: 0, - enabled: true, - implementation: DiscoveryBackendImplementation::RemoteHttp, - }; - let address = DiscoveryBackendAddress::PublicKey(key); - let backend1 = DiscoveryBackend { address, backend }; - - let backend = DiscoveryBackendSparse { - name: None, - partitions: ["default".to_string()].into(), - weight: 0, - enabled: true, - implementation: DiscoveryBackendImplementation::RemoteHttp, + 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 address = DiscoveryBackendAddress::Url(Url::parse("http://example.com/").unwrap()); - let backend2 = DiscoveryBackend { address, backend }; - - let backends = vec![backend1, backend2]; - let backends = serde_json::to_string(&backends).unwrap(); - eprintln!("backends: {}", backends); - let backends_expected = r#"[{"address":{"publicKey":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad"},"partitions":["default"],"weight":0,"enabled":true,"implementation":{"type":"remoteHttp"}},{"address":{"url":"http://example.com/"},"partitions":["default"],"weight":0,"enabled":true,"implementation":{"type":"remoteHttp"}}]"#; + 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 key = SecretKey::from_byte_array([ + 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 key = key.public_key(&Secp256k1::new()); - - let backend = DiscoveryBackendSparse { - name: None, - partitions: ["default".to_string()].into(), - weight: 0, - enabled: true, - implementation: DiscoveryBackendImplementation::RemoteHttp, - }; - let address = DiscoveryBackendAddress::PublicKey(key); - let backend1 = DiscoveryBackend { address, backend }; - - let backend = DiscoveryBackendSparse { - name: None, - partitions: ["default".to_string()].into(), - weight: 0, - enabled: true, - implementation: DiscoveryBackendImplementation::RemoteHttp, + 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 address = DiscoveryBackendAddress::Url(Url::parse("http://example.com/").unwrap()); - let backend2 = DiscoveryBackend { address, backend }; - - let backends_expected = vec![backend1, backend2]; - let backends = r#"[{"address":{"publicKey":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad"},"partitions":["default"],"weight":0,"enabled":true,"implementation":{"type":"remoteHttp"}},{"address":{"url":"http://example.com/"},"partitions":["default"],"weight":0,"enabled":true,"implementation":{"type":"remoteHttp"}}]"#; + 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(); - let backends: Vec = serde_json::from_str(backends).unwrap(); - assert_eq!(backends_expected, backends); + assert_eq!(backend_expected, backend); } } diff --git a/service/src/api/offer.rs b/service/src/api/offer.rs index 3ca65b9..79566a7 100644 --- a/service/src/api/offer.rs +++ b/service/src/api/offer.rs @@ -122,14 +122,6 @@ pub struct OfferRecord { pub offer: OfferRecordSparse, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct OfferRecordRest { - pub location: String, - #[serde(flatten)] - pub offer: OfferRecord, -} - #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OfferMetadataSparse { @@ -151,14 +143,6 @@ pub struct OfferMetadata { pub metadata: OfferMetadataSparse, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct OfferMetadataRest { - pub location: String, - #[serde(flatten)] - pub metadata: OfferMetadata, -} - #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum OfferMetadataImage { diff --git a/service/src/axum/extract/address.rs b/service/src/axum/extract/address.rs deleted file mode 100644 index a6defe2..0000000 --- a/service/src/axum/extract/address.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::api::discovery::DiscoveryBackendAddress; -use axum::{extract::FromRequestParts, extract::Path, http::request::Parts, http::StatusCode}; - -#[derive(Debug, Clone)] -pub struct DiscoveryBackendAddressParam { - pub address: DiscoveryBackendAddress, -} - -impl FromRequestParts for DiscoveryBackendAddressParam -where - S: Send + Sync, -{ - type Rejection = StatusCode; - - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let Path((variant, value)): Path<(String, String)> = Path::from_request_parts(parts, state) - .await - .map_err(|_| StatusCode::NOT_FOUND)?; - - let raw_addr = DiscoveryBackendAddress::try_from((variant, value)) - .map_err(|_| StatusCode::NOT_FOUND)?; - - Ok(DiscoveryBackendAddressParam { address: raw_addr }) - } -} diff --git a/service/src/axum/extract/mod.rs b/service/src/axum/extract/mod.rs index 1813762..5ed6d6c 100644 --- a/service/src/axum/extract/mod.rs +++ b/service/src/axum/extract/mod.rs @@ -1,4 +1,3 @@ -pub mod address; pub mod host; pub mod scheme; pub mod uuid; diff --git a/service/src/components/discovery/db.rs b/service/src/components/discovery/db.rs index 32e64eb..1b3b380 100644 --- a/service/src/components/discovery/db.rs +++ b/service/src/components/discovery/db.rs @@ -1,6 +1,6 @@ use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendImplementation, - DiscoveryBackendPatch, DiscoveryBackendSparse, DiscoveryBackendStore, DiscoveryBackends, + DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatch, + DiscoveryBackendSparse, DiscoveryBackendStore, DiscoveryBackends, }; use crate::api::service::ServiceErrorSource; use crate::components::discovery::error::DiscoveryBackendStoreError; @@ -12,6 +12,7 @@ use sea_orm::{ ActiveModelTrait, ColumnTrait, Database, DatabaseConnection, EntityTrait, FromJsonQueryResult, QueryFilter, QueryOrder, QuerySelect, Set, TransactionTrait, }; +use secp256k1::PublicKey; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use switchgear_migration::{MigratorTrait, DISCOVERY_BACKEND_GET_ALL_ETAG_ID}; @@ -25,8 +26,7 @@ pub struct Model { #[sea_orm(column_type = "JsonBinary")] pub partitions: DiscoveryBackendPartitions, #[sea_orm(primary_key, auto_increment = false)] - pub address: String, - pub address_type: String, + pub id: Vec, pub name: Option, pub weight: i32, pub enabled: bool, @@ -69,7 +69,7 @@ impl DbDiscoveryBackendStore { ) -> Result { let mut opt = sea_orm::ConnectOptions::new(uri); opt.max_connections(max_connections); - let db = Database::connect(opt.clone()).await.map_err(|e| { + let db = Database::connect(opt).await.map_err(|e| { DiscoveryBackendStoreError::from_db( ServiceErrorSource::Internal, "connecting to discovery backend database", @@ -110,15 +110,7 @@ impl DbDiscoveryBackendStore { Self { db } } - fn address_type_from_address(address: &DiscoveryBackendAddress) -> &'static str { - match address { - DiscoveryBackendAddress::PublicKey(_) => "publicKey", - DiscoveryBackendAddress::Url(_) => "url", - } - } - fn model_to_domain(model: Model) -> Result { - let address = Self::parse_address(&model.address, &model.address_type)?; let implementation: DiscoveryBackendImplementation = serde_json::from_value(model.implementation).map_err(|e| { DiscoveryBackendStoreError::json_serialization_error( @@ -129,7 +121,13 @@ impl DbDiscoveryBackendStore { })?; Ok(DiscoveryBackend { - address, + public_key: PublicKey::from_slice(&model.id).map_err(|e| { + DiscoveryBackendStoreError::internal_error( + ServiceErrorSource::Internal, + format!("deserializing public key {:?} from database", model.id), + format!("deserializing failure: {e}"), + ) + })?, backend: DiscoveryBackendSparse { name: model.name, partitions: model.partitions.0, @@ -139,58 +137,20 @@ impl DbDiscoveryBackendStore { }, }) } - - fn parse_address( - address_str: &str, - address_type: &str, - ) -> Result { - match address_type { - "publicKey" => address_str - .parse() - .map(DiscoveryBackendAddress::PublicKey) - .map_err(|e| { - DiscoveryBackendStoreError::internal_error( - ServiceErrorSource::Internal, - "parsing public key from database", - format!("Invalid public key: {e}"), - ) - }), - "url" => address_str - .parse() - .map(DiscoveryBackendAddress::Url) - .map_err(|e| { - DiscoveryBackendStoreError::internal_error( - ServiceErrorSource::Internal, - "parsing URL from database", - format!("Invalid URL: {e}"), - ) - }), - _ => Err(DiscoveryBackendStoreError::internal_error( - ServiceErrorSource::Internal, - "parsing address from database", - format!("Unknown address type: {address_type}"), - )), - } - } } #[async_trait] impl DiscoveryBackendStore for DbDiscoveryBackendStore { type Error = DiscoveryBackendStoreError; - async fn get( - &self, - addr: &DiscoveryBackendAddress, - ) -> Result, Self::Error> { - let address_str = addr.to_string(); - - let result = Entity::find_by_id(&address_str) + async fn get(&self, public_key: &PublicKey) -> Result, Self::Error> { + let result = Entity::find_by_id(public_key.serialize()) .one(&self.db) .await .map_err(|e| { DiscoveryBackendStoreError::from_db( ServiceErrorSource::Internal, - format!("fetching backend for address {address_str}",), + format!("fetching backend for public key {public_key}",), e, ) })?; @@ -223,7 +183,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { } else { let models = Entity::find() .order_by_asc(Column::CreatedAt) - .order_by_asc(Column::Address) + .order_by_asc(Column::Id) .all(&self.db) .await .map_err(|e| { @@ -245,13 +205,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { } } - async fn post( - &self, - backend: DiscoveryBackend, - ) -> Result, Self::Error> { - let address_str = backend.address.to_string(); - let address_type = Self::address_type_from_address(&backend.address); - + 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( @@ -264,8 +218,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { let now = Utc::now(); let active_model = ActiveModel { partitions: Set(DiscoveryBackendPartitions(backend.backend.partitions)), - address: Set(address_str), - address_type: Set(address_type.to_string()), + id: Set(backend.public_key.serialize().to_vec()), name: Set(backend.backend.name), weight: Set(backend.backend.weight as i32), enabled: Set(backend.backend.enabled), @@ -314,7 +267,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { })?; match insert_result { - Ok(_) => Ok(Some(backend.address)), + Ok(_) => Ok(Some(backend.public_key)), // PostgreSQL unique constraint violation Err(sea_orm::DbErr::Query(sea_orm::RuntimeErr::SqlxError(sqlx::Error::Database( db_err, @@ -325,16 +278,13 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { )))) if db_err.is_unique_violation() => Ok(None), Err(e) => Err(DiscoveryBackendStoreError::from_db( ServiceErrorSource::Internal, - format!("inserting backend for address {}", backend.address), + format!("inserting backend for public key {}", backend.public_key), e, )), } } async fn put(&self, backend: DiscoveryBackend) -> Result { - let address_str = backend.address.to_string(); - let address_type = Self::address_type_from_address(&backend.address); - let implementation_json = serde_json::to_value(&backend.backend.implementation).map_err(|e| { DiscoveryBackendStoreError::json_serialization_error( @@ -347,10 +297,10 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { let now = Utc::now(); let future_timestamp = now + chrono::Duration::seconds(1); + let id = backend.public_key.serialize(); let active_model = ActiveModel { partitions: Set(DiscoveryBackendPartitions(backend.backend.partitions)), - address: Set(address_str.clone()), - address_type: Set(address_type.to_string()), + id: Set(id.to_vec()), name: Set(backend.backend.name), weight: Set(backend.backend.weight as i32), enabled: Set(backend.backend.enabled), @@ -366,7 +316,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { Box::pin(async move { let upsert = Entity::insert(active_model) .on_conflict( - OnConflict::columns([Column::Address]) + OnConflict::columns([Column::Id]) .update_columns([ Column::Name, Column::Weight, @@ -381,7 +331,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { let timestamps = if upsert.is_ok() { Entity::find() - .filter(Column::Address.eq(&address_str)) + .filter(Column::Id.eq(id.as_slice())) .select_only() .column(Column::CreatedAt) .column(Column::UpdatedAt) @@ -423,7 +373,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { upsert_result.map_err(|e| { DiscoveryBackendStoreError::from_db( ServiceErrorSource::Internal, - format!("upserting backend for address {}", backend.address), + format!("upserting backend for public key {}", backend.public_key), e, ) })?; @@ -441,8 +391,8 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { DiscoveryBackendStoreError::from_db( ServiceErrorSource::Internal, format!( - "fetching backend after upsert for address {}", - backend.address + "fetching backend after upsert for public key {}", + backend.public_key ), e, ) @@ -460,9 +410,8 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { } async fn patch(&self, backend: DiscoveryBackendPatch) -> Result { - let address_str = backend.address.to_string(); - - let mut update = Entity::update_many().filter(Column::Address.eq(&address_str)); + let mut update = + Entity::update_many().filter(Column::Id.eq(backend.public_key.serialize().as_slice())); if let Some(name) = backend.backend.name { update = update.col_expr(Column::Name, Expr::value(name)); @@ -531,7 +480,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { let result = patch_result.map_err(|e| { DiscoveryBackendStoreError::from_db( ServiceErrorSource::Internal, - format!("patching backend for address {address_str}"), + format!("patching backend for public key {}", backend.public_key), e, ) })?; @@ -539,14 +488,14 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { Ok(result.rows_affected > 0) } - async fn delete(&self, addr: &DiscoveryBackendAddress) -> Result { - let address_str = addr.to_string(); + async fn delete(&self, public_key: &PublicKey) -> Result { + let id = public_key.serialize(); let (delete_result, etag_result) = self .db .transaction::<_, _, _>(|txn| { Box::pin(async move { - let delete = Entity::delete_by_id(&address_str).exec(txn).await; + let delete = Entity::delete_by_id(id).exec(txn).await; let etag = if delete .as_ref() @@ -591,7 +540,7 @@ impl DiscoveryBackendStore for DbDiscoveryBackendStore { let result = delete_result.map_err(|e| { DiscoveryBackendStoreError::from_db( ServiceErrorSource::Internal, - format!("deleting backend for address {addr}"), + format!("deleting backend for public key {public_key}"), e, ) })?; diff --git a/service/src/components/discovery/http.rs b/service/src/components/discovery/http.rs index 9c7fd89..bf02124 100644 --- a/service/src/components/discovery/http.rs +++ b/service/src/components/discovery/http.rs @@ -1,6 +1,6 @@ use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendPatch, DiscoveryBackendStore, - DiscoveryBackends, HttpDiscoveryBackendClient, + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, + HttpDiscoveryBackendClient, }; use crate::api::service::ServiceErrorSource; use crate::components::discovery::error::DiscoveryBackendStoreError; @@ -8,6 +8,7 @@ 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 url::Url; @@ -97,8 +98,8 @@ impl HttpDiscoveryBackendStore { }) } - fn discovery_address_url(&self, addr: &DiscoveryBackendAddress) -> String { - format!("{}/{}", self.discovery_url, addr.encoded()) + fn discovery_public_key_url(&self, public_key: &PublicKey) -> String { + format!("{}/{}", self.discovery_url, public_key) } fn general_error(status: StatusCode, context: &str) -> DiscoveryBackendStoreError { @@ -127,11 +128,8 @@ impl HttpDiscoveryBackendStore { impl DiscoveryBackendStore for HttpDiscoveryBackendStore { type Error = DiscoveryBackendStoreError; - async fn get( - &self, - addr: &DiscoveryBackendAddress, - ) -> Result, Self::Error> { - let url = self.discovery_address_url(addr); + async fn get(&self, public_key: &PublicKey) -> Result, Self::Error> { + let url = self.discovery_public_key_url(public_key); let response = self.client.get(&url).send().await.map_err(|e| { DiscoveryBackendStoreError::http_error( @@ -231,10 +229,7 @@ impl DiscoveryBackendStore for HttpDiscoveryBackendStore { } } - async fn post( - &self, - backend: DiscoveryBackend, - ) -> Result, Self::Error> { + async fn post(&self, backend: DiscoveryBackend) -> Result, Self::Error> { let response = self .client .post(&self.discovery_url) @@ -246,27 +241,27 @@ impl DiscoveryBackendStore for HttpDiscoveryBackendStore { ServiceErrorSource::Upstream, format!( "post backend: {}, url: {}", - backend.address, &self.discovery_url + backend.public_key, &self.discovery_url ), e, ) })?; match response.status() { - StatusCode::CREATED => Ok(Some(backend.address)), + StatusCode::CREATED => Ok(Some(backend.public_key)), StatusCode::CONFLICT => Ok(None), status => Err(Self::general_error( status, &format!( "post backend: {}, url: {}", - backend.address, &self.discovery_url + backend.public_key, &self.discovery_url ), )), } } async fn put(&self, backend: DiscoveryBackend) -> Result { - let url = self.discovery_address_url(&backend.address); + let url = self.discovery_public_key_url(&backend.public_key); let response = self .client @@ -290,7 +285,7 @@ impl DiscoveryBackendStore for HttpDiscoveryBackendStore { } async fn patch(&self, backend: DiscoveryBackendPatch) -> Result { - let url = self.discovery_address_url(&backend.address); + let url = self.discovery_public_key_url(&backend.public_key); let response = self .client @@ -313,8 +308,8 @@ impl DiscoveryBackendStore for HttpDiscoveryBackendStore { } } - async fn delete(&self, addr: &DiscoveryBackendAddress) -> Result { - let url = self.discovery_address_url(addr); + async fn delete(&self, public_key: &PublicKey) -> Result { + let url = self.discovery_public_key_url(public_key); let response = self.client.delete(&url).send().await.map_err(|e| { DiscoveryBackendStoreError::http_error( @@ -363,9 +358,10 @@ impl HttpDiscoveryBackendClient for HttpDiscoveryBackendStore { #[cfg(test)] mod tests { - use crate::api::discovery::DiscoveryBackendAddress; use crate::components::discovery::http::HttpDiscoveryBackendStore; use anyhow::anyhow; + use rand::Rng; + use secp256k1::{PublicKey, Secp256k1, SecretKey}; use url::Url; #[test] @@ -392,11 +388,16 @@ mod tests { assert_eq!(&client.health_check_url, "https://base.com/health"); - let addr = DiscoveryBackendAddress::Url("https://remote.com/backend".parse().unwrap()); - let discovery_partition_address_url = client.discovery_address_url(&addr); + let secp = Secp256k1::new(); + let mut rng = rand::thread_rng(); + + let secret_key = SecretKey::from_byte_array(rng.gen::<[u8; 32]>()).unwrap(); + let public_key = PublicKey::from_secret_key(&secp, &secret_key); + + let discovery_partition_public_key_url = client.discovery_public_key_url(&public_key); assert_eq!( - format!("https://base.com/discovery/{}", addr.encoded()), - discovery_partition_address_url, + format!("https://base.com/discovery/{public_key}"), + discovery_partition_public_key_url, ); } } diff --git a/service/src/components/discovery/memory.rs b/service/src/components/discovery/memory.rs index f91986f..a4f349b 100644 --- a/service/src/components/discovery/memory.rs +++ b/service/src/components/discovery/memory.rs @@ -1,9 +1,9 @@ use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendPatch, DiscoveryBackendStore, - DiscoveryBackends, + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendStore, DiscoveryBackends, }; use crate::components::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; @@ -17,7 +17,7 @@ struct DiscoveryBackendTimestamped { #[derive(Clone, Debug)] pub struct MemoryDiscoveryBackendStore { - store: Arc>>, + store: Arc>>, etag: Arc, } @@ -40,12 +40,9 @@ impl MemoryDiscoveryBackendStore { impl DiscoveryBackendStore for MemoryDiscoveryBackendStore { type Error = DiscoveryBackendStoreError; - async fn get( - &self, - addr: &DiscoveryBackendAddress, - ) -> Result, Self::Error> { + async fn get(&self, public_key: &PublicKey) -> Result, Self::Error> { let store = self.store.lock().await; - Ok(store.get(addr).map(|b| b.backend.clone())) + Ok(store.get(public_key).map(|b| b.backend.clone())) } async fn get_all(&self, request_etag: Option) -> Result { @@ -55,7 +52,7 @@ impl DiscoveryBackendStore for MemoryDiscoveryBackendStore { backends.sort_by(|a, b| { a.created .cmp(&b.created) - .then_with(|| a.backend.address.cmp(&b.backend.address)) + .then_with(|| a.backend.public_key.cmp(&b.backend.public_key)) }); let response_etag = self.etag.load(Ordering::Relaxed); @@ -74,30 +71,26 @@ impl DiscoveryBackendStore for MemoryDiscoveryBackendStore { } } - async fn post( - &self, - backend: DiscoveryBackend, - ) -> Result, Self::Error> { + async fn post(&self, backend: DiscoveryBackend) -> Result, Self::Error> { let mut store = self.store.lock().await; - let key = backend.address.clone(); - if store.contains_key(&key) { + if store.contains_key(&backend.public_key) { return Ok(None); } - let addr = backend.address.clone(); + let key = backend.public_key; store.insert( - key, + backend.public_key, DiscoveryBackendTimestamped { created: chrono::Utc::now(), backend, }, ); self.etag.fetch_add(1, Ordering::Relaxed); - Ok(Some(addr)) + Ok(Some(key)) } async fn put(&self, backend: DiscoveryBackend) -> Result { let mut store = self.store.lock().await; - let key = backend.address.clone(); + let key = backend.public_key; let (created, was_new) = match store.get(&key) { Some(existing) => (existing.created, false), None => (chrono::Utc::now(), true), @@ -109,7 +102,7 @@ impl DiscoveryBackendStore for MemoryDiscoveryBackendStore { async fn patch(&self, backend: DiscoveryBackendPatch) -> Result { let mut store = self.store.lock().await; - let entry = match store.get_mut(&backend.address) { + let entry = match store.get_mut(&backend.public_key) { None => return Ok(false), Some(entry) => entry, }; @@ -129,10 +122,9 @@ impl DiscoveryBackendStore for MemoryDiscoveryBackendStore { Ok(true) } - async fn delete(&self, addr: &DiscoveryBackendAddress) -> Result { + async fn delete(&self, public_key: &PublicKey) -> Result { let mut store = self.store.lock().await; - let key = addr.clone(); - let was_found = store.remove(&key).is_some(); + let was_found = store.remove(public_key).is_some(); if was_found { self.etag.fetch_add(1, Ordering::Relaxed); } diff --git a/service/src/components/offer/db.rs b/service/src/components/offer/db.rs index bb34021..b878c4c 100644 --- a/service/src/components/offer/db.rs +++ b/service/src/components/offer/db.rs @@ -26,7 +26,7 @@ impl DbOfferStore { pub async fn connect(uri: &str, max_connections: u32) -> Result { let mut opt = sea_orm::ConnectOptions::new(uri); opt.max_connections(max_connections); - let db = Database::connect(opt.clone()).await.map_err(|e| { + let db = Database::connect(opt).await.map_err(|e| { OfferStoreError::from_db( ServiceErrorSource::Internal, "connecting to offer database", diff --git a/service/src/components/pool/default_pool.rs b/service/src/components/pool/default_pool.rs index 117d88c..6685472 100644 --- a/service/src/components/pool/default_pool.rs +++ b/service/src/components/pool/default_pool.rs @@ -125,13 +125,6 @@ where c.clone(), &self.trusted_roots, )?), - DiscoveryBackendImplementation::RemoteHttp => { - return Err(LnPoolError::from_invalid_configuration( - "RemoteHttp backends not available".to_string(), - ServiceErrorSource::Internal, - format!("connecting ln client {key:?}"), - )); - } }; let mut pool = self.pool.lock().map_err(|e| { diff --git a/service/src/discovery/handler.rs b/service/src/discovery/handler.rs index 0d7d5fb..2b9ad11 100644 --- a/service/src/discovery/handler.rs +++ b/service/src/discovery/handler.rs @@ -1,12 +1,12 @@ use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendRest, - DiscoveryBackendSparse, DiscoveryBackendStore, DiscoveryBackends, + DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, + DiscoveryBackendStore, DiscoveryBackends, }; use crate::axum::crud::error::CrudError; use crate::axum::crud::response::JsonCrudResponse; -use crate::axum::extract::address::DiscoveryBackendAddressParam; use crate::axum::header::no_cache_headers; use crate::discovery::state::DiscoveryState; +use axum::extract::Path; use axum::http::{HeaderMap, HeaderValue}; use axum::{extract::State, Json}; @@ -14,24 +14,21 @@ pub struct DiscoveryHandlers; impl DiscoveryHandlers { pub async fn get_backend( - DiscoveryBackendAddressParam { address }: DiscoveryBackendAddressParam, + Path(public_key): Path, State(state): State>, - ) -> Result, CrudError> + ) -> Result, CrudError> where S: DiscoveryBackendStore, { + let public_key = public_key.parse().map_err(|_| CrudError::bad())?; + let backend = state .store() - .get(&address) + .get(&public_key) .await .map_err(|e| crate::crud_error_from_service!(e))? .ok_or(CrudError::not_found())?; - let backend = DiscoveryBackendRest { - location: backend.address.encoded(), - backend, - }; - let headers = no_cache_headers(); Ok(JsonCrudResponse::ok(backend, headers)) @@ -40,7 +37,7 @@ impl DiscoveryHandlers { pub async fn get_backends( headers: HeaderMap, State(state): State>, - ) -> Result>, CrudError> + ) -> Result>, CrudError> where S: DiscoveryBackendStore, { @@ -66,17 +63,7 @@ impl DiscoveryHandlers { match backends.backends { None => Ok(JsonCrudResponse::not_modified(headers)), - Some(backends) => { - let backends = backends - .into_iter() - .map(|backend| DiscoveryBackendRest { - location: backend.address.encoded(), - backend, - }) - .collect(); - - Ok(JsonCrudResponse::ok(backends, headers)) - } + Some(backends) => Ok(JsonCrudResponse::ok(backends, headers)), } } @@ -93,7 +80,7 @@ impl DiscoveryHandlers { .await .map_err(|e| crate::crud_error_from_service!(e))?; - let location = backend.address.encoded(); + let location = backend.public_key.to_string(); let location = HeaderValue::from_str(&location)?; match result { @@ -104,13 +91,18 @@ impl DiscoveryHandlers { pub async fn put_backend( State(state): State>, - DiscoveryBackendAddressParam { address }: DiscoveryBackendAddressParam, + Path(public_key): Path, Json(backend): Json, ) -> Result, CrudError> where S: DiscoveryBackendStore, { - let backend = DiscoveryBackend { address, backend }; + let public_key = public_key.parse().map_err(|_| CrudError::bad())?; + + let backend = DiscoveryBackend { + public_key, + backend, + }; let was_created = state .store() @@ -127,13 +119,18 @@ impl DiscoveryHandlers { pub async fn patch_backend( State(state): State>, - DiscoveryBackendAddressParam { address }: DiscoveryBackendAddressParam, + Path(public_key): Path, Json(backend): Json, ) -> Result, CrudError> where S: DiscoveryBackendStore, { - let backend = DiscoveryBackendPatch { address, backend }; + let public_key = public_key.parse().map_err(|_| CrudError::bad())?; + + let backend = DiscoveryBackendPatch { + public_key, + backend, + }; let patched = state .store() @@ -149,15 +146,17 @@ impl DiscoveryHandlers { } pub async fn delete_backend( - DiscoveryBackendAddressParam { address }: DiscoveryBackendAddressParam, + Path(public_key): Path, State(state): State>, ) -> Result, CrudError> where S: DiscoveryBackendStore, { + let public_key = public_key.parse().map_err(|_| CrudError::bad())?; + if state .store() - .delete(&address) + .delete(&public_key) .await .map_err(|e| crate::crud_error_from_service!(e))? { diff --git a/service/src/discovery/service.rs b/service/src/discovery/service.rs index f74cf3a..ff59428 100644 --- a/service/src/discovery/service.rs +++ b/service/src/discovery/service.rs @@ -17,19 +17,19 @@ impl DiscoveryService { { Router::new() .route( - "/discovery/{addr_variant}/{addr_value}", + "/discovery/{public_key}", get(DiscoveryHandlers::get_backend), ) .route( - "/discovery/{addr_variant}/{addr_value}", + "/discovery/{public_key}", put(DiscoveryHandlers::put_backend), ) .route( - "/discovery/{addr_variant}/{addr_value}", + "/discovery/{public_key}", patch(DiscoveryHandlers::patch_backend), ) .route( - "/discovery/{addr_variant}/{addr_value}", + "/discovery/{public_key}", delete(DiscoveryHandlers::delete_backend), ) .route("/discovery", get(DiscoveryHandlers::get_backends)) @@ -50,10 +50,13 @@ impl DiscoveryService { #[cfg(test)] mod tests { use crate::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendImplementation, - DiscoveryBackendPatchSparse, DiscoveryBackendRest, DiscoveryBackendSparse, + 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; @@ -74,13 +77,23 @@ mod tests { let public_key = PublicKey::from_secret_key(&secp, &secret_key); DiscoveryBackend { - address: DiscoveryBackendAddress::PublicKey(public_key), + public_key, backend: DiscoveryBackendSparse { name: None, partitions: [partition.to_string()].into(), weight: 100, enabled: true, - implementation: DiscoveryBackendImplementation::RemoteHttp, + 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, + }, + ), }, } } @@ -174,7 +187,7 @@ mod tests { assert_eq!(response.status_code(), StatusCode::CREATED); let location = response.header("location").to_str().unwrap().to_string(); - assert_eq!(location, backend.address.encoded()); + assert_eq!(location, backend.public_key.to_string()); } #[tokio::test] @@ -228,9 +241,17 @@ mod tests { let retrieved: DiscoveryBackend = response.json(); assert_eq!( retrieved.backend.implementation, - DiscoveryBackendImplementation::RemoteHttp + 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.address, backend.address); + assert_eq!(retrieved.public_key, backend.public_key); } #[tokio::test] @@ -253,7 +274,7 @@ mod tests { let response = server .server - .put("/discovery/url/aHR0cHM6Ly8xOTIuMTY4LjEuMTo4MDgwLw") + .put(&format!("/discovery/{}", backend.public_key)) .authorization_bearer(server.authorization.clone()) .json(&backend.backend) .await; @@ -346,7 +367,7 @@ mod tests { let server = setup_test_server().await; let mut backend = create_test_backend("default"); - let location = backend.address.encoded(); + let location = backend.public_key.to_string(); let patch = DiscoveryBackendPatchSparse { name: None, @@ -421,21 +442,21 @@ mod tests { let backend2 = create_test_backend("default"); // Sort backends by address before posting - let mut backends = [backend1, backend2]; - backends.sort_by(|a, b| a.address.to_string().cmp(&b.address.to_string())); + let mut expected_backends = [backend1, backend2]; + expected_backends.sort_by(|a, b| a.public_key.to_string().cmp(&b.public_key.to_string())); // Create multiple backends server .server .post("/discovery") .authorization_bearer(server.authorization.clone()) - .json(&backends[0]) + .json(&expected_backends[0]) .await; server .server .post("/discovery") .authorization_bearer(server.authorization.clone()) - .json(&backends[1]) + .json(&expected_backends[1]) .await; // Get all backends (first request) @@ -446,18 +467,9 @@ mod tests { .await; assert_eq!(response.status_code(), StatusCode::OK); - let response_backends: Vec = response.json(); - - // Build expected backends from the sorted list - let expected: Vec = backends - .iter() - .map(|b| DiscoveryBackendRest { - location: b.address.encoded(), - backend: b.clone(), - }) - .collect(); - - assert_eq!(response_backends, expected); + let response_backends: Vec = response.json(); + + assert_eq!(response_backends, expected_backends); // Collect etag from first response let etag = response.header("etag"); diff --git a/service/src/offer/handler.rs b/service/src/offer/handler.rs index 7a7e3b2..d1489e7 100644 --- a/service/src/offer/handler.rs +++ b/service/src/offer/handler.rs @@ -1,6 +1,6 @@ use crate::api::offer::{ - OfferMetadata, OfferMetadataRest, OfferMetadataSparse, OfferMetadataStore, OfferRecord, - OfferRecordRest, OfferRecordSparse, OfferStore, + OfferMetadata, OfferMetadataSparse, OfferMetadataStore, OfferRecord, OfferRecordSparse, + OfferStore, }; use crate::axum::crud::error::CrudError; use crate::axum::crud::response::JsonCrudResponse; @@ -30,7 +30,7 @@ impl OfferHandlers { pub async fn get_offer( UuidParam { partition, id }: UuidParam, State(state): State>, - ) -> Result, CrudError> + ) -> Result, CrudError> where S: OfferStore, M: OfferMetadataStore, @@ -44,11 +44,6 @@ impl OfferHandlers { let headers = no_cache_headers(); - let offer = OfferRecordRest { - location: format!("{partition}/{}", offer.id), - offer, - }; - Ok(JsonCrudResponse::ok(offer, headers)) } @@ -56,7 +51,7 @@ impl OfferHandlers { axum::extract::Path(partition): axum::extract::Path, Query(params): Query, State(state): State>, - ) -> Result>, CrudError> + ) -> Result>, CrudError> where S: OfferStore, M: OfferMetadataStore, @@ -73,14 +68,6 @@ impl OfferHandlers { let headers = no_cache_headers(); - let offers = offers - .into_iter() - .map(|offer| OfferRecordRest { - location: format!("{partition}/{}", offer.id), - offer, - }) - .collect(); - Ok(JsonCrudResponse::ok(offers, headers)) } @@ -158,7 +145,7 @@ impl OfferHandlers { pub async fn get_metadata( UuidParam { partition, id }: UuidParam, State(state): State>, - ) -> Result, CrudError> + ) -> Result, CrudError> where S: OfferStore, M: OfferMetadataStore, @@ -172,11 +159,6 @@ impl OfferHandlers { let headers = no_cache_headers(); - let metadata = OfferMetadataRest { - location: format!("{partition}/{}", metadata.id), - metadata, - }; - Ok(JsonCrudResponse::ok(metadata, headers)) } @@ -184,7 +166,7 @@ impl OfferHandlers { axum::extract::Path(partition): axum::extract::Path, Query(params): Query, State(state): State>, - ) -> Result>, CrudError> + ) -> Result>, CrudError> where S: OfferStore, M: OfferMetadataStore, @@ -199,14 +181,6 @@ impl OfferHandlers { .await .map_err(|e| crate::crud_error_from_service!(e))?; - let metadata = metadata - .into_iter() - .map(|metadata| OfferMetadataRest { - location: format!("{partition}/{}", metadata.id), - metadata, - }) - .collect(); - let headers = no_cache_headers(); Ok(JsonCrudResponse::ok(metadata, headers)) diff --git a/service/src/offer/service.rs b/service/src/offer/service.rs index b10d467..8526fe2 100644 --- a/service/src/offer/service.rs +++ b/service/src/offer/service.rs @@ -59,7 +59,7 @@ impl OfferService { mod tests { use crate::api::offer::{ OfferMetadata, OfferMetadataIdentifier, OfferMetadataImage, OfferMetadataSparse, - OfferMetadataStore, OfferRecord, OfferRecordRest, OfferRecordSparse, OfferStore, + OfferMetadataStore, OfferRecord, OfferRecordSparse, OfferStore, }; use crate::components::offer::memory::MemoryOfferStore; use crate::offer::service::OfferService; @@ -364,8 +364,7 @@ mod tests { .authorization_bearer(server.authorization.clone()) .await; assert_eq!(response.status_code(), StatusCode::OK); - let all_offers: Vec = response.json(); - let all_offers: Vec = all_offers.into_iter().map(|r| r.offer).collect(); + let all_offers: Vec = response.json(); assert_eq!(all_offers.as_slice(), expected_offers.as_slice()); let response = server @@ -374,8 +373,7 @@ mod tests { .authorization_bearer(server.authorization.clone()) .await; assert_eq!(response.status_code(), StatusCode::OK); - let next_nine: Vec = response.json(); - let next_nine: Vec = next_nine.into_iter().map(|r| r.offer).collect(); + let next_nine: Vec = response.json(); assert_eq!(next_nine.as_slice(), &expected_offers[1..]); let response = server @@ -384,8 +382,7 @@ mod tests { .authorization_bearer(server.authorization.clone()) .await; assert_eq!(response.status_code(), StatusCode::OK); - let first: Vec = response.json(); - let first: Vec = first.into_iter().map(|r| r.offer).collect(); + let first: Vec = response.json(); assert_eq!(first.as_slice(), &expected_offers[0..1]); let response = server @@ -394,8 +391,7 @@ mod tests { .authorization_bearer(server.authorization.clone()) .await; assert_eq!(response.status_code(), StatusCode::OK); - let middle_offers: Vec = response.json(); - let middle_offers: Vec = middle_offers.into_iter().map(|r| r.offer).collect(); + let middle_offers: Vec = response.json(); assert_eq!(middle_offers.as_slice(), &expected_offers[3..7]); let response = server @@ -694,8 +690,6 @@ mod tests { #[tokio::test] async fn get_all_metadata_when_exists_then_returns_list() { - use crate::api::offer::OfferMetadataRest; - let mut expected_metadata = Vec::new(); for i in 0..10 { let mut metadata = create_test_metadata(); @@ -711,9 +705,7 @@ mod tests { .authorization_bearer(server.authorization.clone()) .await; assert_eq!(response.status_code(), StatusCode::OK); - let all_metadata: Vec = response.json(); - let all_metadata: Vec = - all_metadata.into_iter().map(|r| r.metadata).collect(); + let all_metadata: Vec = response.json(); assert_eq!(all_metadata.as_slice(), expected_metadata.as_slice()); let response = server @@ -722,8 +714,7 @@ mod tests { .authorization_bearer(server.authorization.clone()) .await; assert_eq!(response.status_code(), StatusCode::OK); - let next_nine: Vec = response.json(); - let next_nine: Vec = next_nine.into_iter().map(|r| r.metadata).collect(); + let next_nine: Vec = response.json(); assert_eq!(next_nine.as_slice(), &expected_metadata[1..]); let response = server @@ -732,8 +723,7 @@ mod tests { .authorization_bearer(server.authorization.clone()) .await; assert_eq!(response.status_code(), StatusCode::OK); - let first: Vec = response.json(); - let first: Vec = first.into_iter().map(|r| r.metadata).collect(); + let first: Vec = response.json(); assert_eq!(first.as_slice(), &expected_metadata[0..1]); let response = server @@ -742,9 +732,7 @@ mod tests { .authorization_bearer(server.authorization.clone()) .await; assert_eq!(response.status_code(), StatusCode::OK); - let middle_metadata: Vec = response.json(); - let middle_metadata: Vec = - middle_metadata.into_iter().map(|r| r.metadata).collect(); + let middle_metadata: Vec = response.json(); assert_eq!(middle_metadata.as_slice(), &expected_metadata[3..7]); let response = server diff --git a/service/tests/common/discovery.rs b/service/tests/common/discovery.rs index 7de4847..6d1422b 100644 --- a/service/tests/common/discovery.rs +++ b/service/tests/common/discovery.rs @@ -1,9 +1,11 @@ use rand::Rng; use secp256k1::{PublicKey, Secp256k1, SecretKey}; use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendImplementation, - DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendSparse, - DiscoveryBackendStore, + DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendPatch, + DiscoveryBackendPatchSparse, DiscoveryBackendSparse, DiscoveryBackendStore, +}; +use switchgear_service::components::pool::lnd::grpc::config::{ + LndGrpcClientAuth, LndGrpcClientAuthPath, LndGrpcDiscoveryBackendImplementation, }; pub fn gen_backends() -> (DiscoveryBackend, DiscoveryBackend, DiscoveryBackend) { @@ -19,43 +21,73 @@ pub fn gen_backends() -> (DiscoveryBackend, DiscoveryBackend, DiscoveryBackend) // Create the two distinct backends let backend1 = DiscoveryBackend { - address: DiscoveryBackendAddress::PublicKey(public_key1), + public_key: public_key1, backend: DiscoveryBackendSparse { name: None, partitions: ["default".to_string()].into(), weight: 100, enabled: true, - implementation: DiscoveryBackendImplementation::RemoteHttp, + 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 backend2 = DiscoveryBackend { - address: DiscoveryBackendAddress::PublicKey(public_key2), + public_key: public_key2, backend: DiscoveryBackendSparse { name: Some("new_backend2".to_string()), partitions: ["default".to_string()].into(), weight: 200, enabled: true, - implementation: DiscoveryBackendImplementation::RemoteHttp, + 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, + }, + ), }, }; // Sort the two backends by address string representation let mut backends = [backend1, backend2]; - backends.sort_by(|a, b| a.address.to_string().cmp(&b.address.to_string())); + backends.sort_by(|a, b| a.public_key.to_string().cmp(&b.public_key.to_string())); let new_backend1 = backends[0].clone(); let new_backend2 = backends[1].clone(); // Create modified_backend2 with the same address as new_backend2 let modified_backend2 = DiscoveryBackend { - address: new_backend2.address.clone(), + public_key: new_backend2.public_key, backend: DiscoveryBackendSparse { name: Some("new_backend2_modified".to_string()), partitions: ["default".to_string()].into(), weight: 10, enabled: false, - implementation: DiscoveryBackendImplementation::RemoteHttp, + 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, + }, + ), }, }; @@ -71,10 +103,10 @@ where // Test posting new backends returns their addresses let addr = store.post(new_backend1.clone()).await.unwrap(); - assert_eq!(addr, Some(new_backend1.address.clone())); + assert_eq!(addr, Some(new_backend1.public_key)); let addr = store.post(new_backend2.clone()).await.unwrap(); - assert_eq!(addr, Some(new_backend2.address.clone())); + assert_eq!(addr, Some(new_backend2.public_key)); // Test posting duplicate returns None let addr = store.post(modified_backend2.clone()).await.unwrap(); @@ -93,15 +125,15 @@ where let _ = store.post(modified_backend2.clone()).await.unwrap(); // Test individual gets - let backend = store.get(&new_backend1.address).await.unwrap().unwrap(); + let backend = store.get(&new_backend1.public_key).await.unwrap().unwrap(); assert_eq!(backend, new_backend1); - let backend = store.get(&new_backend2.address).await.unwrap().unwrap(); + let backend = store.get(&new_backend2.public_key).await.unwrap().unwrap(); assert_eq!(backend, new_backend2); // Modified backend should not have been stored let backend = store - .get(&modified_backend2.address) + .get(&modified_backend2.public_key) .await .unwrap() .unwrap(); @@ -126,21 +158,21 @@ where let _ = store.post(modified_backend2.clone()).await.unwrap(); // Delete and verify return values - let deleted = store.delete(&new_backend1.address).await.unwrap(); + let deleted = store.delete(&new_backend1.public_key).await.unwrap(); assert!(deleted); - let deleted = store.delete(&new_backend2.address).await.unwrap(); + let deleted = store.delete(&new_backend2.public_key).await.unwrap(); assert!(deleted); // Modified backend was never stored, so delete returns None - let deleted = store.delete(&modified_backend2.address).await.unwrap(); + let deleted = store.delete(&modified_backend2.public_key).await.unwrap(); assert!(!deleted); // Verify all backends are gone - assert!(store.get(&new_backend1.address).await.unwrap().is_none()); - assert!(store.get(&new_backend2.address).await.unwrap().is_none()); + assert!(store.get(&new_backend1.public_key).await.unwrap().is_none()); + assert!(store.get(&new_backend2.public_key).await.unwrap().is_none()); assert!(store - .get(&modified_backend2.address) + .get(&modified_backend2.public_key) .await .unwrap() .is_none()); @@ -173,10 +205,10 @@ where assert!(store.put(new_backend2.clone()).await.unwrap()); // Verify initial state - let backend = store.get(&new_backend1.address).await.unwrap().unwrap(); + let backend = store.get(&new_backend1.public_key).await.unwrap().unwrap(); assert_eq!(backend, new_backend1); - let backend = store.get(&new_backend2.address).await.unwrap().unwrap(); + let backend = store.get(&new_backend2.public_key).await.unwrap().unwrap(); assert_eq!(backend, new_backend2); let actual_backends = store.get_all(None).await.unwrap().backends.unwrap(); @@ -188,7 +220,7 @@ where // Verify update let backend = store - .get(&modified_backend2.address) + .get(&modified_backend2.public_key) .await .unwrap() .unwrap(); @@ -207,10 +239,10 @@ where assert!(store.put(new_backend2.clone()).await.unwrap()); // Verify initial state - let backend = store.get(&new_backend1.address).await.unwrap().unwrap(); + let backend = store.get(&new_backend1.public_key).await.unwrap().unwrap(); assert_eq!(backend, new_backend1); - let backend = store.get(&new_backend2.address).await.unwrap().unwrap(); + let backend = store.get(&new_backend2.public_key).await.unwrap().unwrap(); assert_eq!(backend, new_backend2); let actual_backends = store.get_all(None).await.unwrap().backends.unwrap(); @@ -219,7 +251,7 @@ where // Patch backend2 let backend_patch = DiscoveryBackendPatch { - address: modified_backend2.address.clone(), + public_key: modified_backend2.public_key, backend: DiscoveryBackendPatchSparse { name: Some(modified_backend2.backend.name.clone()), partitions: None, @@ -232,7 +264,7 @@ where // Verify update let backend = store - .get(&modified_backend2.address) + .get(&modified_backend2.public_key) .await .unwrap() .unwrap(); @@ -250,7 +282,7 @@ where assert!(store.put(new_backend1.clone()).await.unwrap()); // Verify initial state - let backend = store.get(&new_backend1.address).await.unwrap().unwrap(); + let backend = store.get(&new_backend1.public_key).await.unwrap().unwrap(); assert_eq!(backend, new_backend1); let actual_backends = store.get_all(None).await.unwrap().backends.unwrap(); @@ -259,7 +291,7 @@ where // Patch backend2 let backend_patch = DiscoveryBackendPatch { - address: modified_backend2.address.clone(), + public_key: modified_backend2.public_key, backend: DiscoveryBackendPatchSparse { name: Some(modified_backend2.backend.name.clone()), partitions: None, @@ -311,7 +343,7 @@ where // Modify backend using patch - etag should change let backend_patch = DiscoveryBackendPatch { - address: new_backend1.address.clone(), + public_key: new_backend1.public_key, backend: DiscoveryBackendPatchSparse { name: Some(Some("patched_backend1".to_string())), partitions: None, @@ -329,7 +361,7 @@ where // Modify backend using put again - etag should change let another_modified_backend2 = DiscoveryBackend { - address: modified_backend2.address.clone(), + public_key: modified_backend2.public_key, backend: DiscoveryBackendSparse { name: Some("backend2_modified_again".to_string()), weight: 5, @@ -345,7 +377,7 @@ where ); // Delete a backend - etag should change - let _ = store.delete(&new_backend1.address).await.unwrap(); + let _ = store.delete(&new_backend1.public_key).await.unwrap(); let result_after_delete = store.get_all(None).await.unwrap(); let etag_after_delete = result_after_delete.etag; assert_ne!( @@ -447,7 +479,7 @@ where ); // Delete a backend - let _ = store.delete(&new_backend1.address).await.unwrap(); + let _ = store.delete(&new_backend1.public_key).await.unwrap(); let result5 = store.get_all(None).await.unwrap(); let etag5 = result5.etag; diff --git a/service/tests/ln/main.rs b/service/tests/ln/main.rs index 698b223..aeff430 100644 --- a/service/tests/ln/main.rs +++ b/service/tests/ln/main.rs @@ -1,6 +1,5 @@ use switchgear_service::api::discovery::{ - DiscoveryBackend, DiscoveryBackendAddress, DiscoveryBackendImplementation, - DiscoveryBackendSparse, + DiscoveryBackend, DiscoveryBackendImplementation, DiscoveryBackendSparse, }; use switchgear_service::components::pool::cln::grpc::config::{ ClnGrpcClientAuth, ClnGrpcClientAuthPath, ClnGrpcDiscoveryBackendImplementation, @@ -37,8 +36,6 @@ pub fn try_create_cln_backend( let url = Url::parse(&format!("https://{}", cln_node.address))?; - let address = DiscoveryBackendAddress::PublicKey(cln_node.public_key); - let implementation = DiscoveryBackendImplementation::ClnGrpc(ClnGrpcDiscoveryBackendImplementation { url, @@ -51,7 +48,7 @@ pub fn try_create_cln_backend( }); let backend = DiscoveryBackend { - address, + public_key: cln_node.public_key, backend: DiscoveryBackendSparse { name: None, partitions: ["default".to_string()].into(), @@ -82,8 +79,6 @@ pub fn try_create_lnd_backend( .next() .ok_or_else(|| anyhow::anyhow!("no lnd nodes available"))?; - let address = DiscoveryBackendAddress::PublicKey(lnd_node.public_key); - let url = Url::parse(&format!("https://{}", lnd_node.address))?; let implementation = @@ -98,7 +93,7 @@ pub fn try_create_lnd_backend( }); let backend = DiscoveryBackend { - address: address.clone(), + public_key: lnd_node.public_key, backend: DiscoveryBackendSparse { name: None, partitions: ["default".to_string()].into(), diff --git a/switchgear/README.md b/switchgear/README.md index 51ef677..87d68e1 100644 --- a/switchgear/README.md +++ b/switchgear/README.md @@ -757,11 +757,9 @@ curl -X POST http://localhost:3001/discovery \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ - "partitions": ["default"], - "address": { - "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - }, + "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "name": "CLN Node 1", + "partitions": ["default"], "weight": 100, "enabled": true, "implementation": { @@ -782,11 +780,9 @@ curl -X POST http://localhost:3001/discovery \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ - "partitions": ["default", "us", "eu"], - "address": { - "url": "https://lnd-node.example.com" - }, + "publicKey": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "name": "LND Node 1", + "partitions": ["default", "us", "eu"], "weight": 50, "enabled": true, "implementation": { @@ -814,21 +810,18 @@ curl -X GET http://localhost:3001/discovery \ ```shell # By public key -curl -X GET "http://localhost:3001/discovery/pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ - -H "Authorization: Bearer $AUTH_TOKEN" - -# By URL (base64 encoded) -curl -X GET "http://localhost:3001/discovery/url/aHR0cHM6Ly9sbmQtbm9kZS5leGFtcGxlLmNvbS8" \ +curl -X GET "http://localhost:3001/discovery/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ -H "Authorization: Bearer $AUTH_TOKEN" ``` #### Update A Backend ```shell -curl -X PUT "http://localhost:3001/discovery/pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ +curl -X PUT "http://localhost:3001/discovery/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ + "name": "CLN Node 1 Updated", "partitions": ["default", "us"], "weight": 200, "enabled": false, @@ -849,7 +842,7 @@ curl -X PUT "http://localhost:3001/discovery/pk/0279be667ef9dcbbac55a06295ce870b #### Delete A Backend ```shell -curl -X DELETE "http://localhost:3001/discovery/pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ +curl -X DELETE "http://localhost:3001/discovery/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" \ -H "Authorization: Bearer $AUTH_TOKEN" ``` @@ -895,7 +888,7 @@ swgr discovery ls # Get backend details (JSON output) -swgr discovery get pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --output backend-details.json +swgr discovery get 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --output backend-details.json # Get all backends (JSON output) swgr discovery get @@ -904,19 +897,19 @@ swgr discovery get swgr discovery post --input cln-backend.json # Update an existing backend -swgr discovery put pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input updated-backend.json +swgr discovery put 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input updated-backend.json # Patch an existing backend -swgr discovery patch pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input backend-patch.json +swgr discovery patch 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 --input backend-patch.json # Enable an existing backend -swgr discovery enable pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +swgr discovery enable 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 # Disable an existing backend -swgr discovery disable pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +swgr discovery disable 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 # Delete a backend -swgr discovery delete pk/0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +swgr discovery delete 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 ``` ### Discovery Data Model @@ -926,11 +919,9 @@ Discovery OpenAPI schema: [doc/discovery-service-openapi.yaml](https://github.co Example CLN backend configuration: ```json { - "partitions": ["default"], - "address": { - "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - }, + "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "name": "CLN Node 1", + "partitions": ["default"], "weight": 1, "enabled": true, "implementation": { @@ -950,11 +941,9 @@ Example CLN backend configuration: Example LND backend configuration: ```json { - "partitions": ["default", "us", "eu"], - "address": { - "publicKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - }, + "publicKey": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "name": "LND Node 1", + "partitions": ["default", "us", "eu"], "weight": 1, "enabled": true, "implementation": {