1use std::collections::BTreeSet;
2use std::result;
3use std::string::ToString;
4
5use fedimint_api_client::api::{DynModuleApi, IRawFederationApi, JsonRpcClientError};
6use fedimint_core::core::ModuleInstanceId;
7use fedimint_core::db::{Database, DatabaseTransaction};
8use fedimint_core::task::{MaybeSend, MaybeSync};
9use fedimint_core::{apply, async_trait_maybe_send, PeerId};
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}
55
56use crate::db::event_log::{DBTransactionEventLogExt as _, Event, EventKind};
57
58pub trait ClientRawFederationApiExt
61where
62 Self: Sized,
63{
64 fn with_client_ext(
65 self,
66 db: Database,
67 log_ordering_wakeup_tx: watch::Sender<()>,
68 ) -> ClientRawFederationApi<Self>;
69}
70
71impl<T> ClientRawFederationApiExt for T
72where
73 T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
74{
75 fn with_client_ext(
76 self,
77 db: Database,
78 log_ordering_wakeup_tx: watch::Sender<()>,
79 ) -> ClientRawFederationApi<T> {
80 db.ensure_global().expect("Must be given global db");
81 ClientRawFederationApi {
82 inner: self,
83 db,
84 log_ordering_wakeup_tx,
85 }
86 }
87}
88
89#[derive(Debug)]
93pub struct ClientRawFederationApi<I> {
94 inner: I,
95 db: Database,
96 log_ordering_wakeup_tx: watch::Sender<()>,
97}
98
99impl<I> ClientRawFederationApi<I> {
100 pub async fn log_event<E>(&self, event: E)
101 where
102 E: Event + Send,
103 {
104 let mut dbtx = self.db.begin_transaction().await;
105 self.log_event_dbtx(&mut dbtx, event).await;
106 dbtx.commit_tx().await;
107 }
108
109 pub async fn log_event_dbtx<E, Cap>(&self, dbtx: &mut DatabaseTransaction<'_, Cap>, event: E)
110 where
111 E: Event + Send,
112 Cap: Send,
113 {
114 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), None, event)
115 .await;
116 }
117}
118
119#[apply(async_trait_maybe_send!)]
120impl<I> IRawFederationApi for ClientRawFederationApi<I>
121where
122 I: IRawFederationApi,
123{
124 fn all_peers(&self) -> &BTreeSet<PeerId> {
125 self.inner.all_peers()
126 }
127
128 fn self_peer(&self) -> Option<PeerId> {
129 self.inner.self_peer()
130 }
131
132 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
133 self.inner.with_module(id)
134 }
135
136 async fn request_raw(
137 &self,
138 peer_id: PeerId,
139 method: &str,
140 params: &[Value],
141 ) -> result::Result<Value, JsonRpcClientError> {
142 self.log_event(ApiCallStarted {
143 method: method.to_string(),
144 peer_id,
145 })
146 .await;
147
148 let start = fedimint_core::time::now();
149 let res = self.inner.request_raw(peer_id, method, params).await;
150 let end = fedimint_core::time::now();
151
152 self.log_event(ApiCallDone {
153 method: method.to_string(),
154 peer_id,
155 duration_ms: end
156 .duration_since(start)
157 .unwrap_or_default()
158 .as_millis()
159 .try_into()
160 .unwrap_or(u64::MAX),
161 success: res.is_ok(),
162 error_str: res.as_ref().err().map(ToString::to_string),
163 })
164 .await;
165
166 res
167 }
168}