Skip to main content

fedimint_lnv2_client/
api.rs

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