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;