1use std::collections::{BTreeMap, HashMap};
2use std::convert::identity;
3use std::time::Duration;
4
5use anyhow::anyhow;
6use bitcoin::hashes::sha256::{self, Hash as Sha256Hash};
7use fedimint_api_client::api::{
8 FederationApiExt, FederationResult, IModuleFederationApi, ServerError,
9};
10use fedimint_api_client::query::FilterMapThreshold;
11use fedimint_core::module::{ApiRequestErased, ModuleConsensusVersion};
12use fedimint_core::secp256k1::PublicKey;
13use fedimint_core::task::{MaybeSend, MaybeSync, timeout};
14use fedimint_core::{NumPeersExt, PeerId, apply, async_trait_maybe_send};
15use fedimint_ln_common::contracts::incoming::{IncomingContractAccount, IncomingContractOffer};
16use fedimint_ln_common::contracts::{ContractId, DecryptedPreimageStatus, Preimage};
17use fedimint_ln_common::federation_endpoint_constants::{
18 ACCOUNT_ENDPOINT, AWAIT_ACCOUNT_ENDPOINT, AWAIT_BLOCK_HEIGHT_ENDPOINT, AWAIT_OFFER_ENDPOINT,
19 AWAIT_OUTGOING_CONTRACT_CANCELLED_ENDPOINT, AWAIT_PREIMAGE_DECRYPTION, BLOCK_COUNT_ENDPOINT,
20 GET_DECRYPTED_PREIMAGE_STATUS, LIST_GATEWAYS_ENDPOINT, MODULE_CONSENSUS_VERSION_ENDPOINT,
21 OFFER_ENDPOINT, REGISTER_GATEWAY_ENDPOINT, REMOVE_GATEWAY_CHALLENGE_ENDPOINT,
22 REMOVE_GATEWAY_ENDPOINT,
23};
24use fedimint_ln_common::{
25 ContractAccount, FederationPublicKey, LightningGateway, LightningGatewayAnnouncement,
26 RemoveGatewayRequest,
27};
28use itertools::Itertools;
29use tracing::{info, warn};
30
31#[apply(async_trait_maybe_send!)]
32pub trait LnFederationApi {
33 async fn module_consensus_version(&self) -> FederationResult<ModuleConsensusVersion>;
42
43 async fn fetch_consensus_block_count(&self) -> FederationResult<Option<u64>>;
44
45 async fn fetch_contract(
46 &self,
47 contract: ContractId,
48 ) -> FederationResult<Option<ContractAccount>>;
49
50 async fn await_contract(&self, contract: ContractId) -> ContractAccount;
51
52 async fn wait_block_height(&self, block_height: u64);
53
54 async fn wait_outgoing_contract_cancelled(
55 &self,
56 contract: ContractId,
57 ) -> FederationResult<ContractAccount>;
58
59 async fn get_decrypted_preimage_status(
60 &self,
61 contract: ContractId,
62 ) -> FederationResult<(IncomingContractAccount, DecryptedPreimageStatus)>;
63
64 async fn wait_preimage_decrypted(
65 &self,
66 contract: ContractId,
67 ) -> FederationResult<(IncomingContractAccount, Option<Preimage>)>;
68
69 async fn fetch_offer(
70 &self,
71 payment_hash: Sha256Hash,
72 ) -> FederationResult<IncomingContractOffer>;
73
74 async fn fetch_gateways(
79 &self,
80 federation_public_key: FederationPublicKey,
81 ) -> FederationResult<Vec<LightningGatewayAnnouncement>>;
82
83 async fn register_gateway(
84 &self,
85 gateway: &LightningGatewayAnnouncement,
86 ) -> FederationResult<()>;
87
88 async fn get_remove_gateway_challenge(
92 &self,
93 gateway_id: PublicKey,
94 ) -> BTreeMap<PeerId, Option<sha256::Hash>>;
95
96 async fn remove_gateway(&self, remove_gateway_request: RemoveGatewayRequest);
100
101 async fn offer_exists(&self, payment_hash: Sha256Hash) -> FederationResult<bool>;
102}
103
104#[apply(async_trait_maybe_send!)]
105impl<T: ?Sized> LnFederationApi for T
106where
107 T: IModuleFederationApi + MaybeSend + MaybeSync + 'static,
108{
109 async fn module_consensus_version(&self) -> FederationResult<ModuleConsensusVersion> {
110 let response = self
111 .request_current_consensus(
112 MODULE_CONSENSUS_VERSION_ENDPOINT.to_string(),
113 ApiRequestErased::default(),
114 )
115 .await;
116
117 if let Err(e) = &response
118 && e.any_peer_error_method_not_found()
119 {
120 return Ok(ModuleConsensusVersion::new(2, 0));
121 }
122
123 response
124 }
125
126 async fn fetch_consensus_block_count(&self) -> FederationResult<Option<u64>> {
127 self.request_current_consensus(
128 BLOCK_COUNT_ENDPOINT.to_string(),
129 ApiRequestErased::default(),
130 )
131 .await
132 }
133
134 async fn fetch_contract(
135 &self,
136 contract: ContractId,
137 ) -> FederationResult<Option<ContractAccount>> {
138 self.request_current_consensus(
139 ACCOUNT_ENDPOINT.to_string(),
140 ApiRequestErased::new(contract),
141 )
142 .await
143 }
144
145 async fn await_contract(&self, contract: ContractId) -> ContractAccount {
146 self.request_current_consensus_retry(
147 AWAIT_ACCOUNT_ENDPOINT.to_string(),
148 ApiRequestErased::new(contract),
149 )
150 .await
151 }
152
153 async fn wait_block_height(&self, block_height: u64) {
154 self.request_current_consensus_retry::<()>(
155 AWAIT_BLOCK_HEIGHT_ENDPOINT.to_string(),
156 ApiRequestErased::new(block_height),
157 )
158 .await;
159 }
160
161 async fn wait_outgoing_contract_cancelled(
162 &self,
163 contract: ContractId,
164 ) -> FederationResult<ContractAccount> {
165 self.request_current_consensus(
166 AWAIT_OUTGOING_CONTRACT_CANCELLED_ENDPOINT.to_string(),
167 ApiRequestErased::new(contract),
168 )
169 .await
170 }
171
172 async fn get_decrypted_preimage_status(
173 &self,
174 contract: ContractId,
175 ) -> FederationResult<(IncomingContractAccount, DecryptedPreimageStatus)> {
176 self.request_current_consensus(
177 GET_DECRYPTED_PREIMAGE_STATUS.to_string(),
178 ApiRequestErased::new(contract),
179 )
180 .await
181 }
182
183 async fn wait_preimage_decrypted(
184 &self,
185 contract: ContractId,
186 ) -> FederationResult<(IncomingContractAccount, Option<Preimage>)> {
187 self.request_current_consensus(
188 AWAIT_PREIMAGE_DECRYPTION.to_string(),
189 ApiRequestErased::new(contract),
190 )
191 .await
192 }
193
194 async fn fetch_offer(
195 &self,
196 payment_hash: Sha256Hash,
197 ) -> FederationResult<IncomingContractOffer> {
198 self.request_current_consensus(
199 AWAIT_OFFER_ENDPOINT.to_string(),
200 ApiRequestErased::new(payment_hash),
201 )
202 .await
203 }
204
205 async fn fetch_gateways(
209 &self,
210 federation_public_key: FederationPublicKey,
211 ) -> FederationResult<Vec<LightningGatewayAnnouncement>> {
212 let gateway_announcements = self
213 .request_with_strategy(
214 FilterMapThreshold::new(
215 |_, gateways| Ok(gateways),
216 self.all_peers().to_num_peers(),
217 ),
218 LIST_GATEWAYS_ENDPOINT.to_string(),
219 ApiRequestErased::default(),
220 )
221 .await?;
222
223 Ok(filter_duplicate_gateways(
226 &gateway_announcements,
227 federation_public_key,
228 ))
229 }
230
231 async fn register_gateway(
232 &self,
233 gateway: &LightningGatewayAnnouncement,
234 ) -> FederationResult<()> {
235 self.request_current_consensus(
236 REGISTER_GATEWAY_ENDPOINT.to_string(),
237 ApiRequestErased::new(gateway),
238 )
239 .await
240 }
241
242 async fn get_remove_gateway_challenge(
243 &self,
244 gateway_id: PublicKey,
245 ) -> BTreeMap<PeerId, Option<sha256::Hash>> {
246 let mut responses = BTreeMap::new();
247
248 for peer in self.all_peers() {
249 if let Ok(response) = timeout(
251 Duration::from_secs(1),
252 self.request_single_peer::<Option<sha256::Hash>>(
253 REMOVE_GATEWAY_CHALLENGE_ENDPOINT.to_string(),
254 ApiRequestErased::new(gateway_id),
255 *peer,
256 ),
257 )
258 .await
259 .map_err(|e| ServerError::Transport(anyhow!("Request timed out: {e}")))
260 .and_then(identity)
261 {
262 responses.insert(*peer, response);
263 }
264 }
265
266 responses
267 }
268
269 async fn remove_gateway(&self, remove_gateway_request: RemoveGatewayRequest) {
270 let gateway_id = remove_gateway_request.gateway_id;
271
272 for peer in self.all_peers() {
273 if let Ok(response) = timeout(
275 Duration::from_secs(1),
276 self.request_single_peer::<bool>(
277 REMOVE_GATEWAY_ENDPOINT.to_string(),
278 ApiRequestErased::new(remove_gateway_request.clone()),
279 *peer,
280 ),
281 )
282 .await
283 .map_err(|e| ServerError::Transport(anyhow!("Request timed out: {e}")))
284 .and_then(identity)
285 {
286 if response {
287 info!("Successfully removed {gateway_id} gateway from peer: {peer}",);
288 } else {
289 warn!("Unable to remove gateway {gateway_id} registration from peer: {peer}");
290 }
291 }
292 }
293 }
294
295 async fn offer_exists(&self, payment_hash: Sha256Hash) -> FederationResult<bool> {
296 Ok(self
297 .request_current_consensus::<Option<IncomingContractOffer>>(
298 OFFER_ENDPOINT.to_string(),
299 ApiRequestErased::new(payment_hash),
300 )
301 .await?
302 .is_some())
303 }
304}
305
306fn filter_duplicate_gateways(
311 gateways: &BTreeMap<PeerId, Vec<LightningGatewayAnnouncement>>,
312 federation_public_key: FederationPublicKey,
313) -> Vec<LightningGatewayAnnouncement> {
314 let gateways_by_gateway_id = gateways
315 .values()
316 .flatten()
317 .filter(|announcement| announcement.registration_proof_is_valid(federation_public_key))
322 .cloned()
323 .map(|announcement| (announcement.info.gateway_id, announcement))
324 .into_group_map();
325
326 gateways_by_gateway_id
333 .into_values()
334 .flat_map(|mut announcements| {
335 if announcements.iter().any(|ann| ann.auth.is_some()) {
342 announcements.retain(|ann| ann.auth.is_some());
343 }
344
345 let mut gateways: HashMap<LightningGateway, LightningGatewayAnnouncement> =
346 HashMap::new();
347 for announcement in announcements {
348 gateways
349 .entry(announcement.info.clone())
350 .and_modify(|existing| {
351 if announcement.ttl > existing.ttl {
353 existing.ttl = announcement.ttl;
354 }
355 let nonce = |ann: &LightningGatewayAnnouncement| {
358 ann.auth.as_ref().map_or(0, |auth| auth.nonce)
359 };
360 if nonce(&announcement) > nonce(existing) {
361 existing.auth.clone_from(&announcement.auth);
362 }
363 })
364 .or_insert(LightningGatewayAnnouncement {
365 vetted: false,
366 ..announcement
367 });
368 }
369
370 gateways.into_values()
371 })
372 .collect()
373}