1use std::collections::BTreeMap;
2use std::sync::Arc;
3use std::time::Duration;
4
5use fedimint_api_client::api::{DynGlobalApi, ServerError};
6use fedimint_core::db::{Database, IDatabaseTransactionOpsCoreTyped};
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::envs::is_running_in_test_env;
9use fedimint_core::net::guardian_metadata::{SignedGuardianMetadata, VerificationError};
10use fedimint_core::runtime::{self, sleep};
11use fedimint_core::secp256k1::SECP256K1;
12use fedimint_core::util::FmtCompact as _;
13use fedimint_core::util::backoff_util::custom_backoff;
14use fedimint_core::{NumPeersExt as _, PeerId, impl_db_lookup, impl_db_record};
15use fedimint_logging::LOG_CLIENT;
16use futures::stream::{FuturesUnordered, StreamExt as _};
17use tracing::debug;
18
19use crate::Client;
20use crate::db::DbKeyPrefix;
21
22#[derive(Clone, Debug, Encodable, Decodable)]
23pub struct GuardianMetadataKey(pub PeerId);
24
25#[derive(Clone, Debug, Encodable, Decodable)]
26pub struct GuardianMetadataPrefix;
27
28impl_db_record!(
29 key = GuardianMetadataKey,
30 value = SignedGuardianMetadata,
31 db_prefix = DbKeyPrefix::GuardianMetadata,
32 notify_on_modify = false,
33);
34impl_db_lookup!(
35 key = GuardianMetadataKey,
36 query_prefix = GuardianMetadataPrefix
37);
38
39pub(crate) async fn run_guardian_metadata_refresh_task(client_inner: Arc<Client>) {
42 let guardian_pub_keys = client_inner.get_guardian_public_keys_blocking().await;
44 loop {
45 let api: &DynGlobalApi = &client_inner.api;
46 let results = fetch_guardian_metadata_from_at_least_num_of_peers(
47 1,
48 api,
49 &guardian_pub_keys,
50 if is_running_in_test_env() {
51 Duration::from_millis(1)
52 } else {
53 Duration::from_secs(30)
54 },
55 )
56 .await;
57 store_guardian_metadata_updates_from_peers(client_inner.db(), &guardian_pub_keys, &results)
58 .await;
59
60 let duration = if is_running_in_test_env() {
61 Duration::from_secs(1)
62 } else {
63 Duration::from_secs(3600)
65 };
66 sleep(duration).await;
67 }
68}
69
70pub(crate) async fn store_guardian_metadata_updates_from_peers(
71 db: &Database,
72 guardian_pub_keys: &BTreeMap<PeerId, bitcoin::secp256k1::PublicKey>,
73 updates: &[BTreeMap<PeerId, SignedGuardianMetadata>],
74) {
75 for metadata_map in updates {
76 store_guardian_metadata_updates(db, guardian_pub_keys, metadata_map).await;
77 }
78}
79
80pub(crate) type PeersSignedGuardianMetadata = BTreeMap<PeerId, SignedGuardianMetadata>;
81
82pub(crate) async fn fetch_guardian_metadata_from_at_least_num_of_peers(
87 num_responses_required: usize,
88 api: &DynGlobalApi,
89 guardian_pub_keys: &BTreeMap<PeerId, bitcoin::secp256k1::PublicKey>,
90 extra_response_wait: Duration,
91) -> Vec<PeersSignedGuardianMetadata> {
92 let num_peers = guardian_pub_keys.to_num_peers();
93 let mut backoff = custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None);
96
97 async fn make_request(
99 delay: Duration,
100 peer_id: PeerId,
101 api: &DynGlobalApi,
102 guardian_pub_keys: &BTreeMap<PeerId, bitcoin::secp256k1::PublicKey>,
103 ) -> (
104 PeerId,
105 Result<PeersSignedGuardianMetadata, FetchGuardianMetadataError>,
106 ) {
107 runtime::sleep(delay).await;
108
109 let result = async {
110 let metadata_map = api
111 .guardian_metadata(peer_id)
112 .await
113 .map_err(|source| FetchGuardianMetadataError::Request { peer_id, source })?;
114
115 for (peer_id, metadata) in &metadata_map {
118 let Some(guardian_pub_key) = guardian_pub_keys.get(peer_id) else {
119 return Err(FetchGuardianMetadataError::UnknownGuardian { peer_id: *peer_id });
120 };
121
122 let now = fedimint_core::time::duration_since_epoch();
123 if let Err(source) = metadata.verify(SECP256K1, guardian_pub_key, now) {
124 return Err(FetchGuardianMetadataError::InvalidMetadata {
125 peer_id: *peer_id,
126 source,
127 });
128 }
129 }
130 Ok(metadata_map)
131 }
132 .await;
133
134 (peer_id, result)
135 }
136
137 let mut requests = FuturesUnordered::new();
138
139 for peer_id in num_peers.peer_ids() {
140 requests.push(make_request(
141 Duration::ZERO,
142 peer_id,
143 api,
144 guardian_pub_keys,
145 ));
146 }
147
148 let mut responses = Vec::new();
149
150 loop {
151 let next_response = if responses.len() < num_responses_required {
152 requests.next().await
154 } else {
155 fedimint_core::runtime::timeout(extra_response_wait, requests.next())
159 .await
160 .ok()
161 .flatten()
162 };
163
164 let Some((peer_id, response)) = next_response else {
165 break;
166 };
167
168 match response {
169 Err(err) => {
170 debug!(
171 target: LOG_CLIENT,
172 %peer_id,
173 err = %err.fmt_compact(),
174 "Failed to fetch guardian metadata from peer"
175 );
176 requests.push(make_request(
177 backoff.next().expect("Keeps retrying"),
178 peer_id,
179 api,
180 guardian_pub_keys,
181 ));
182 }
183 Ok(metadata) => {
184 responses.push(metadata);
185 }
186 }
187 }
188
189 responses
190}
191
192pub(crate) async fn store_guardian_metadata_updates(
193 db: &Database,
194 guardian_pub_keys: &BTreeMap<PeerId, bitcoin::secp256k1::PublicKey>,
195 metadata_map: &BTreeMap<PeerId, SignedGuardianMetadata>,
196) {
197 let now = fedimint_core::time::duration_since_epoch();
198
199 db.autocommit(
200 |dbtx, _| {
201 let metadata_map_inner = metadata_map.clone();
202 let guardian_pub_keys_inner = guardian_pub_keys.clone();
203 Box::pin(async move {
204 for (peer, new_metadata) in metadata_map_inner {
205 let Some(guardian_pub_key) = guardian_pub_keys_inner.get(&peer) else {
207 debug!(
208 target: LOG_CLIENT,
209 ?peer,
210 "Skipping metadata update: guardian public key not found"
211 );
212 continue;
213 };
214
215 if let Err(e) = new_metadata.verify(SECP256K1, guardian_pub_key, now) {
216 debug!(
217 target: LOG_CLIENT,
218 ?peer,
219 err = %e.fmt_compact(),
220 "Skipping metadata update: verification failed"
221 );
222 continue;
223 }
224
225 let replace_current_metadata = dbtx
226 .get_value(&GuardianMetadataKey(peer))
227 .await
228 .is_none_or(|current_metadata| {
229 current_metadata.guardian_metadata().timestamp_secs
231 < new_metadata.guardian_metadata().timestamp_secs
232 });
233 if replace_current_metadata {
234 debug!(target: LOG_CLIENT, ?peer, "Updating guardian metadata");
235 dbtx.insert_entry(&GuardianMetadataKey(peer), &new_metadata)
236 .await;
237 }
238 }
239
240 Result::<(), ()>::Ok(())
241 })
242 },
243 None,
244 )
245 .await
246 .expect("Will never return an error");
247}
248
249#[derive(Debug, thiserror::Error)]
251enum FetchGuardianMetadataError {
252 #[error("Fetching guardian metadata from peer {peer_id} failed")]
254 Request {
255 peer_id: PeerId,
256 #[source]
257 source: ServerError,
258 },
259
260 #[error("Guardian public key not found for peer {peer_id}")]
262 UnknownGuardian { peer_id: PeerId },
263
264 #[error("Failed to verify metadata for peer {peer_id}")]
266 InvalidMetadata {
267 peer_id: PeerId,
268 #[source]
269 source: VerificationError,
270 },
271}