fedimint_lnv2_client/
api.rs1use 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 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}