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