fedimint_lnv2_client/
api.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use fedimint_api_client::api::{
4    FederationApiExt, FederationResult, IModuleFederationApi, PeerResult,
5};
6use fedimint_api_client::query::FilterMapThreshold;
7use fedimint_core::module::{ApiAuth, ApiRequestErased};
8use fedimint_core::task::{MaybeSend, MaybeSync};
9use fedimint_core::util::SafeUrl;
10use fedimint_core::{NumPeersExt, PeerId, apply, async_trait_maybe_send};
11use fedimint_lnv2_common::ContractId;
12use fedimint_lnv2_common::endpoint_constants::{
13    ADD_GATEWAY_ENDPOINT, AWAIT_INCOMING_CONTRACT_ENDPOINT, AWAIT_PREIMAGE_ENDPOINT,
14    CONSENSUS_BLOCK_COUNT_ENDPOINT, GATEWAYS_ENDPOINT, REMOVE_GATEWAY_ENDPOINT,
15};
16use rand::seq::SliceRandom;
17
18#[apply(async_trait_maybe_send!)]
19pub trait LightningFederationApi {
20    async fn consensus_block_count(&self) -> FederationResult<u64>;
21
22    async fn await_incoming_contract(&self, contract_id: &ContractId, expiration: u64) -> bool;
23
24    async fn await_preimage(&self, contract_id: &ContractId, expiration: u64) -> Option<[u8; 32]>;
25
26    async fn gateways(&self) -> FederationResult<Vec<SafeUrl>>;
27
28    async fn gateways_from_peer(&self, peer: PeerId) -> PeerResult<Vec<SafeUrl>>;
29
30    async fn add_gateway(&self, auth: ApiAuth, gateway: SafeUrl) -> FederationResult<bool>;
31
32    async fn remove_gateway(&self, auth: ApiAuth, gateway: SafeUrl) -> FederationResult<bool>;
33}
34
35#[apply(async_trait_maybe_send!)]
36impl<T: ?Sized> LightningFederationApi for T
37where
38    T: IModuleFederationApi + MaybeSend + MaybeSync + 'static,
39{
40    async fn consensus_block_count(&self) -> FederationResult<u64> {
41        self.request_current_consensus(
42            CONSENSUS_BLOCK_COUNT_ENDPOINT.to_string(),
43            ApiRequestErased::new(()),
44        )
45        .await
46    }
47
48    async fn await_incoming_contract(&self, contract_id: &ContractId, expiration: u64) -> bool {
49        self.request_current_consensus_retry::<Option<ContractId>>(
50            AWAIT_INCOMING_CONTRACT_ENDPOINT.to_string(),
51            ApiRequestErased::new((contract_id, expiration)),
52        )
53        .await
54        .is_some()
55    }
56
57    async fn await_preimage(&self, contract_id: &ContractId, expiration: u64) -> Option<[u8; 32]> {
58        self.request_current_consensus_retry(
59            AWAIT_PREIMAGE_ENDPOINT.to_string(),
60            ApiRequestErased::new((contract_id, expiration)),
61        )
62        .await
63    }
64
65    async fn gateways(&self) -> FederationResult<Vec<SafeUrl>> {
66        let gateways: BTreeMap<PeerId, Vec<SafeUrl>> = self
67            .request_with_strategy(
68                FilterMapThreshold::new(
69                    |_, gateways| Ok(gateways),
70                    self.all_peers().to_num_peers(),
71                ),
72                GATEWAYS_ENDPOINT.to_string(),
73                ApiRequestErased::default(),
74            )
75            .await?;
76
77        let mut union = gateways
78            .values()
79            .flatten()
80            .cloned()
81            .collect::<BTreeSet<SafeUrl>>()
82            .into_iter()
83            .collect::<Vec<SafeUrl>>();
84
85        // Shuffling the gateways ensures that payments are distributed over the
86        // gateways evenly.
87        union.shuffle(&mut rand::thread_rng());
88
89        union.sort_by_cached_key(|r| {
90            gateways
91                .values()
92                .filter(|response| !response.contains(r))
93                .count()
94        });
95
96        Ok(union)
97    }
98
99    async fn gateways_from_peer(&self, peer: PeerId) -> PeerResult<Vec<SafeUrl>> {
100        let gateways = self
101            .request_single_peer::<Vec<SafeUrl>>(
102                GATEWAYS_ENDPOINT.to_string(),
103                ApiRequestErased::default(),
104                peer,
105            )
106            .await?;
107
108        Ok(gateways)
109    }
110
111    async fn add_gateway(&self, auth: ApiAuth, gateway: SafeUrl) -> FederationResult<bool> {
112        let is_new_entry: bool = self
113            .request_admin(ADD_GATEWAY_ENDPOINT, ApiRequestErased::new(gateway), auth)
114            .await?;
115
116        Ok(is_new_entry)
117    }
118
119    async fn remove_gateway(&self, auth: ApiAuth, gateway: SafeUrl) -> FederationResult<bool> {
120        let entry_existed: bool = self
121            .request_admin(
122                REMOVE_GATEWAY_ENDPOINT,
123                ApiRequestErased::new(gateway),
124                auth,
125            )
126            .await?;
127
128        Ok(entry_existed)
129    }
130}