Skip to main content

fedimint_gateway_common/
federation_status.rs

1use std::collections::BTreeMap;
2
3use fedimint_core::config::FederationId;
4use fedimint_core::module::ModuleConsensusVersion;
5use serde::{Deserialize, Deserializer, Serialize};
6
7use crate::RegisteredProtocol;
8
9/// Public gateway endpoint for federation-scoped capability and health queries.
10///
11/// Clients send an unauthenticated HTTP or Iroh `POST` containing
12/// `{"federation_id":"<hex federation id>"}`. The reply contains the echoed
13/// `federation_id` and a `federation_status` tagged as `served` or `unserved`.
14/// A served response also contains sanitized connectivity and independently
15/// tagged `lnv1` and `lnv2` module states.
16///
17/// Identifies the federation whose gateway status should be reported.
18#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
19pub struct FederationStatusRequest {
20    /// Federation to inspect.
21    pub federation_id: FederationId,
22}
23
24/// Sanitized health of one gateway connection to one requested federation.
25///
26/// This protocol is public over the gateway's HTTP and Iroh transports. It
27/// reports only the exact federation ID supplied by the caller; the
28/// authenticated `/info` endpoint remains the only global federation inventory.
29/// A configured gateway returns [`FederationStatus::Unserved`] instead of `404`
30/// for an unknown federation.
31///
32/// The tagged enums make valid combinations explicit: unserved responses have
33/// no connectivity or module claims, and registration exists only for supported
34/// modules. Supported LNv1 uses [`Lnv1RegistrationStatus::GatewayManaged`];
35/// supported LNv2 uses [`Lnv2RegistrationStatus::FederationManaged`]. Other
36/// pairings are invalid.
37#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
38pub struct FederationStatusResponse {
39    /// Echo of the federation identifier supplied by the caller.
40    federation_id: FederationId,
41    /// Whether the requested federation is served, and its status when served.
42    #[serde(flatten)]
43    status: FederationStatus,
44}
45
46impl FederationStatusResponse {
47    /// Constructs a response for a federation this gateway does not serve.
48    pub fn unserved(federation_id: FederationId) -> Self {
49        Self {
50            federation_id,
51            status: FederationStatus::Unserved,
52        }
53    }
54
55    /// Constructs a response for a served federation.
56    pub fn served(
57        federation_id: FederationId,
58        connectivity: FederationConnectivity,
59        lnv1: LightningModuleStatus<Lnv1RegistrationStatus>,
60        lnv2: LightningModuleStatus<Lnv2RegistrationStatus>,
61    ) -> Self {
62        Self {
63            federation_id,
64            status: FederationStatus::Served {
65                connectivity,
66                lnv1,
67                lnv2,
68            },
69        }
70    }
71
72    /// Returns the federation named by this response.
73    pub fn federation_id(&self) -> FederationId {
74        self.federation_id
75    }
76
77    /// Returns the validated scoped status.
78    pub fn status(&self) -> &FederationStatus {
79        &self.status
80    }
81}
82
83#[derive(Deserialize)]
84#[serde(
85    tag = "federation_status",
86    rename_all = "snake_case",
87    deny_unknown_fields
88)]
89enum FederationStatusResponseWire {
90    Unserved {
91        federation_id: FederationId,
92    },
93    Served {
94        federation_id: FederationId,
95        connectivity: FederationConnectivity,
96        lnv1: LightningModuleStatus<Lnv1RegistrationStatus>,
97        lnv2: LightningModuleStatus<Lnv2RegistrationStatus>,
98    },
99}
100
101impl<'de> Deserialize<'de> for FederationStatusResponse {
102    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103    where
104        D: Deserializer<'de>,
105    {
106        Ok(
107            match FederationStatusResponseWire::deserialize(deserializer)? {
108                FederationStatusResponseWire::Unserved { federation_id } => {
109                    Self::unserved(federation_id)
110                }
111                FederationStatusResponseWire::Served {
112                    federation_id,
113                    connectivity,
114                    lnv1,
115                    lnv2,
116                } => Self::served(federation_id, connectivity, lnv1, lnv2),
117            },
118        )
119    }
120}
121
122/// Whether this gateway serves the exact requested federation.
123#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
124#[serde(
125    tag = "federation_status",
126    rename_all = "snake_case",
127    deny_unknown_fields
128)]
129pub enum FederationStatus {
130    /// This gateway has no loaded client for the requested federation.
131    Unserved,
132    /// This gateway has a loaded client and can report its scoped status.
133    Served {
134        /// Aggregated current connection-pool state, not an active reachability
135        /// probe.
136        connectivity: FederationConnectivity,
137        /// Gateway support and registration health for the federation's LNv1
138        /// module.
139        lnv1: LightningModuleStatus<Lnv1RegistrationStatus>,
140        /// Gateway support and registration health for the federation's LNv2
141        /// module.
142        lnv2: LightningModuleStatus<Lnv2RegistrationStatus>,
143    },
144}
145
146/// Aggregated gateway-to-federation connectivity without guardian details.
147#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
148#[serde(rename_all = "snake_case")]
149pub enum FederationConnectivity {
150    /// No guardian connection is currently live.
151    Disconnected,
152    /// Some guardian connections are live, but fewer than the federation
153    /// threshold.
154    Degraded,
155    /// Enough guardian connections are live to satisfy the federation
156    /// threshold.
157    Connected,
158}
159
160/// Coherent module presence and registration state.
161#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
162#[serde(tag = "module_status", rename_all = "snake_case", deny_unknown_fields)]
163pub enum LightningModuleStatus<R> {
164    /// The federation configuration does not contain this module generation.
165    Absent {},
166    /// The gateway initialized the module.
167    Supported {
168        /// Module consensus version declared by the federation.
169        consensus_version: ModuleConsensusVersion,
170        /// How gateway discovery is configured for this module.
171        registration: R,
172    },
173}
174
175/// Gateway-managed discovery state for a supported LNv1 module.
176#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
177#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
178pub enum Lnv1RegistrationStatus {
179    /// The gateway periodically advertises its configured LNv1 transports.
180    GatewayManaged {
181        /// Whether the gateway and federation configuration allow registration.
182        configured: bool,
183        /// Per-transport results retained from registration attempts.
184        endpoints: BTreeMap<RegisteredProtocol, RegistrationEndpointStatus>,
185    },
186}
187
188/// Federation-managed discovery state for a supported LNv2 module.
189#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
190#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
191pub enum Lnv2RegistrationStatus {
192    /// Federation administrators configure LNv2 gateway URLs; there is no
193    /// gateway TTL.
194    FederationManaged,
195}
196
197/// Sanitized retained registration state for one public gateway transport.
198#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
199pub struct RegistrationEndpointStatus {
200    /// Completed attempt with the newest begin-order observed by this process.
201    ///
202    /// This is absent before the first attempt after startup.
203    pub last_attempt: Option<RegistrationAttempt>,
204    /// Approximate remaining lifetime of the successful LNv1 announcement with
205    /// the newest begin-order.
206    ///
207    /// This is absent before the first success and zero after that announcement
208    /// expires. A later failed refresh does not erase an earlier valid TTL.
209    pub advertised_ttl_remaining_secs: Option<u64>,
210}
211
212/// One completed, sanitized gateway registration attempt.
213#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
214pub struct RegistrationAttempt {
215    /// Unix timestamp when the attempt completed.
216    pub completed_at_unix_secs: u64,
217    /// Finite, detail-free result of the attempt.
218    pub result: RegistrationAttemptResult,
219}
220
221/// Sanitized result of a gateway registration attempt.
222#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
223#[serde(rename_all = "snake_case")]
224pub enum RegistrationAttemptResult {
225    /// The federation accepted the registration request.
226    Succeeded,
227    /// The registration request failed without exposing internal details.
228    Failed,
229}
230
231#[cfg(test)]
232mod tests;