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