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