fedimint_client_module/
api.rs1use std::collections::BTreeSet;
2use std::string::ToString;
3
4use fedimint_api_client::api::{DynModuleApi, IRawFederationApi, PeerResult};
5use fedimint_core::core::ModuleInstanceId;
6use fedimint_core::db::{Database, DatabaseTransaction};
7use fedimint_core::module::ApiRequestErased;
8use fedimint_core::task::{MaybeSend, MaybeSync};
9use fedimint_core::{PeerId, apply, async_trait_maybe_send};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use tokio::sync::watch;
13
14#[derive(Serialize, Deserialize, Debug, Clone)]
19pub struct ApiCallStarted {
20 method: String,
21 peer_id: PeerId,
22}
23
24impl Event for ApiCallStarted {
25 const MODULE: Option<fedimint_core::core::ModuleKind> = None;
26
27 const KIND: EventKind = EventKind::from_static("api-call-started");
28
29 const PERSIST: bool = false;
32}
33
34#[derive(Serialize, Deserialize, Debug, Clone)]
41pub struct ApiCallDone {
42 method: String,
43 peer_id: PeerId,
44 duration_ms: u64,
45 success: bool,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 error_str: Option<String>,
48}
49
50impl Event for ApiCallDone {
51 const MODULE: Option<fedimint_core::core::ModuleKind> = None;
52
53 const KIND: EventKind = EventKind::from_static("api-call-done");
54 const PERSIST: bool = false;
55}
56
57use fedimint_eventlog::{DBTransactionEventLogExt as _, Event, EventKind};
58
59pub trait ClientRawFederationApiExt
62where
63 Self: Sized,
64{
65 fn with_client_ext(
66 self,
67 db: Database,
68 log_ordering_wakeup_tx: watch::Sender<()>,
69 ) -> ClientRawFederationApi<Self>;
70}
71
72impl<T> ClientRawFederationApiExt for T
73where
74 T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
75{
76 fn with_client_ext(
77 self,
78 db: Database,
79 log_ordering_wakeup_tx: watch::Sender<()>,
80 ) -> ClientRawFederationApi<T> {
81 db.ensure_global().expect("Must be given global db");
82 ClientRawFederationApi {
83 inner: self,
84 db,
85 log_ordering_wakeup_tx,
86 }
87 }
88}
89
90#[derive(Debug)]
94pub struct ClientRawFederationApi<I> {
95 inner: I,
96 db: Database,
97 log_ordering_wakeup_tx: watch::Sender<()>,
98}
99
100impl<I> ClientRawFederationApi<I> {
101 pub async fn log_event<E>(&self, event: E)
102 where
103 E: Event + Send,
104 {
105 let mut dbtx = self.db.begin_transaction().await;
106 self.log_event_dbtx(&mut dbtx, event).await;
107 dbtx.commit_tx().await;
108 }
109
110 pub async fn log_event_dbtx<E, Cap>(&self, dbtx: &mut DatabaseTransaction<'_, Cap>, event: E)
111 where
112 E: Event + Send,
113 Cap: Send,
114 {
115 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), None, event)
116 .await;
117 }
118}
119
120#[apply(async_trait_maybe_send!)]
121impl<I> IRawFederationApi for ClientRawFederationApi<I>
122where
123 I: IRawFederationApi,
124{
125 fn all_peers(&self) -> &BTreeSet<PeerId> {
126 self.inner.all_peers()
127 }
128
129 fn self_peer(&self) -> Option<PeerId> {
130 self.inner.self_peer()
131 }
132
133 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
134 self.inner.with_module(id)
135 }
136
137 async fn request_raw(
138 &self,
139 peer_id: PeerId,
140 method: &str,
141 params: &ApiRequestErased,
142 ) -> PeerResult<Value> {
143 self.log_event(ApiCallStarted {
144 method: method.to_string(),
145 peer_id,
146 })
147 .await;
148
149 let start = fedimint_core::time::now();
150 let res = self.inner.request_raw(peer_id, method, params).await;
151 let end = fedimint_core::time::now();
152
153 self.log_event(ApiCallDone {
154 method: method.to_string(),
155 peer_id,
156 duration_ms: end
157 .duration_since(start)
158 .unwrap_or_default()
159 .as_millis()
160 .try_into()
161 .unwrap_or(u64::MAX),
162 success: res.is_ok(),
163 error_str: res.as_ref().err().map(ToString::to_string),
164 })
165 .await;
166
167 res
168 }
169}