1pub mod audit;
20pub mod registry;
21
22use std::fmt::{self, Debug, Formatter};
23use std::marker::PhantomData;
24use std::pin::Pin;
25use std::sync::Arc;
26use std::sync::atomic::{AtomicU64, Ordering};
27
28use fedimint_logging::LOG_NET_API;
29use futures::Future;
30use jsonrpsee_core::JsonValue;
31use registry::ModuleRegistry;
32use serde::{Deserialize, Serialize};
33use tracing::Instrument;
34
35mod version;
37pub use self::version::*;
38use crate::config::P2PMessage;
39use crate::core::{
40 ClientConfig, Decoder, DecoderBuilder, Input, InputError, ModuleConsensusItem,
41 ModuleInstanceId, ModuleKind, Output, OutputError, OutputOutcome,
42};
43use crate::db::{
44 Committable, Database, DatabaseKey, DatabaseKeyWithNotify, DatabaseRecord, DatabaseTransaction,
45 NonCommittable,
46};
47use crate::encoding::{Decodable, DecodeError, Encodable};
48use crate::fmt_utils::AbbreviateHexBytes;
49use crate::net::peers::DynP2PConnections;
50use crate::task::MaybeSend;
51use crate::{Amount, PeerId, apply, async_trait_maybe_send, maybe_add_send, maybe_add_send_sync};
52
53#[derive(Debug, PartialEq, Eq)]
54pub struct InputMeta {
55 pub amount: TransactionItemAmount,
56 pub pub_key: secp256k1::PublicKey,
57}
58
59#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
65pub struct TransactionItemAmount {
66 pub amount: Amount,
67 pub fee: Amount,
68}
69
70impl TransactionItemAmount {
71 pub const ZERO: Self = Self {
72 amount: Amount::ZERO,
73 fee: Amount::ZERO,
74 };
75}
76
77#[derive(Debug, Serialize, Deserialize, Clone)]
79pub struct ApiRequest<T> {
80 pub auth: Option<ApiAuth>,
82 pub params: T,
84}
85
86pub type ApiRequestErased = ApiRequest<JsonValue>;
87
88impl Default for ApiRequestErased {
89 fn default() -> Self {
90 Self {
91 auth: None,
92 params: JsonValue::Null,
93 }
94 }
95}
96
97impl ApiRequestErased {
98 pub fn new<T: Serialize>(params: T) -> Self {
99 Self {
100 auth: None,
101 params: serde_json::to_value(params)
102 .expect("parameter serialization error - this should not happen"),
103 }
104 }
105
106 pub fn to_json(&self) -> JsonValue {
107 serde_json::to_value(self).expect("parameter serialization error - this should not happen")
108 }
109
110 pub fn with_auth(self, auth: ApiAuth) -> Self {
111 Self {
112 auth: Some(auth),
113 params: self.params,
114 }
115 }
116
117 pub fn to_typed<T: serde::de::DeserializeOwned>(
118 self,
119 ) -> Result<ApiRequest<T>, serde_json::Error> {
120 Ok(ApiRequest {
121 auth: self.auth,
122 params: serde_json::from_value::<T>(self.params)?,
123 })
124 }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub enum ApiMethod {
129 Core(String),
130 Module(ModuleInstanceId, String),
131}
132
133impl fmt::Display for ApiMethod {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 match self {
136 Self::Core(s) => f.write_str(s),
137 Self::Module(module_id, s) => f.write_fmt(format_args!("{module_id}-{s}")),
138 }
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct IrohApiRequest {
144 pub method: ApiMethod,
145 pub request: ApiRequestErased,
146}
147
148pub const FEDIMINT_API_ALPN: &[u8] = b"FEDIMINT_API_ALPN";
149
150#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub struct ApiAuth(pub String);
153
154impl Debug for ApiAuth {
155 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
156 write!(f, "ApiAuth(****)")
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ApiError {
162 pub code: i32,
163 pub message: String,
164}
165
166impl ApiError {
167 pub fn new(code: i32, message: String) -> Self {
168 Self { code, message }
169 }
170
171 pub fn not_found(message: String) -> Self {
172 Self::new(404, message)
173 }
174
175 pub fn bad_request(message: String) -> Self {
176 Self::new(400, message)
177 }
178
179 pub fn unauthorized() -> Self {
180 Self::new(401, "Invalid authorization".to_string())
181 }
182
183 pub fn server_error(message: String) -> Self {
184 Self::new(500, message)
185 }
186}
187
188pub struct ApiEndpointContext<'dbtx> {
190 db: Database,
191 dbtx: DatabaseTransaction<'dbtx, Committable>,
192 has_auth: bool,
193 request_auth: Option<ApiAuth>,
194}
195
196impl<'a> ApiEndpointContext<'a> {
197 pub fn new(
199 db: Database,
200 dbtx: DatabaseTransaction<'a, Committable>,
201 has_auth: bool,
202 request_auth: Option<ApiAuth>,
203 ) -> Self {
204 Self {
205 db,
206 dbtx,
207 has_auth,
208 request_auth,
209 }
210 }
211
212 pub fn dbtx<'s, 'mtx>(&'s mut self) -> DatabaseTransaction<'mtx, NonCommittable>
214 where
215 'a: 'mtx,
216 's: 'mtx,
217 {
218 self.dbtx.to_ref_nc()
220 }
221
222 pub fn request_auth(&self) -> Option<ApiAuth> {
225 self.request_auth.clone()
226 }
227
228 pub fn has_auth(&self) -> bool {
231 self.has_auth
232 }
233
234 pub fn db(&self) -> Database {
235 self.db.clone()
236 }
237
238 pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
240 where
241 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
242 {
243 let db = self.db.clone();
244 async move { db.wait_key_exists(&key).await }
247 }
248
249 pub fn wait_value_matches<K>(
251 &self,
252 key: K,
253 matcher: impl Fn(&K::Value) -> bool + Copy,
254 ) -> impl Future<Output = K::Value>
255 where
256 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
257 {
258 let db = self.db.clone();
259 async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
260 }
261
262 pub async fn commit_tx_result(self, path: &'static str) -> Result<(), ApiError> {
264 self.dbtx.commit_tx_result().await.map_err(|err| {
265 tracing::warn!(
266 target: fedimint_logging::LOG_NET_API,
267 path,
268 "API server error when writing to database: {:?}",
269 err
270 );
271 ApiError {
272 code: 500,
273 message: "API server error when writing to database".to_string(),
274 }
275 })
276 }
277}
278
279#[apply(async_trait_maybe_send!)]
280pub trait TypedApiEndpoint {
281 type State: Sync;
282
283 const PATH: &'static str;
285
286 type Param: serde::de::DeserializeOwned + Send;
287 type Response: serde::Serialize;
288
289 async fn handle<'state, 'context, 'dbtx>(
290 state: &'state Self::State,
291 context: &'context mut ApiEndpointContext<'dbtx>,
292 request: Self::Param,
293 ) -> Result<Self::Response, ApiError>
294 where
295 'dbtx: 'context;
296}
297
298pub use serde_json;
299
300#[macro_export]
316macro_rules! __api_endpoint {
317 (
318 $path:expr_2021,
319 $version_introduced:expr_2021,
322 async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
323 ) => {{
324 struct Endpoint;
325
326 #[$crate::apply($crate::async_trait_maybe_send!)]
327 impl $crate::module::TypedApiEndpoint for Endpoint {
328 #[allow(deprecated)]
329 const PATH: &'static str = $path;
330 type State = $state_ty;
331 type Param = $param_ty;
332 type Response = $resp_ty;
333
334 async fn handle<'state, 'context, 'dbtx>(
335 $state: &'state Self::State,
336 $context: &'context mut $crate::module::ApiEndpointContext<'dbtx>,
337 $param: Self::Param,
338 ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
339 {
340 const __API_VERSION: $crate::module::ApiVersion = $version_introduced;
342 }
343 $body
344 }
345 }
346
347 $crate::module::ApiEndpoint::from_typed::<Endpoint>()
348 }};
349}
350
351pub use __api_endpoint as api_endpoint;
352use fedimint_core::NumPeers;
353
354use self::registry::ModuleDecoderRegistry;
355
356type HandlerFnReturn<'a> =
357 Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
358type HandlerFn<M> = Box<
359 maybe_add_send_sync!(
360 dyn for<'a> Fn(&'a M, ApiEndpointContext<'a>, ApiRequestErased) -> HandlerFnReturn<'a>
361 ),
362>;
363
364pub struct ApiEndpoint<M> {
366 pub path: &'static str,
371 pub handler: HandlerFn<M>,
375}
376
377static REQ_ID: AtomicU64 = AtomicU64::new(0);
379
380impl ApiEndpoint<()> {
382 pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
383 where
384 <E as TypedApiEndpoint>::Response: MaybeSend,
385 E::Param: Debug,
386 E::Response: Debug,
387 {
388 async fn handle_request<'state, 'context, 'dbtx, E>(
389 state: &'state E::State,
390 context: &'context mut ApiEndpointContext<'dbtx>,
391 request: ApiRequest<E::Param>,
392 ) -> Result<E::Response, ApiError>
393 where
394 'dbtx: 'context,
395 E: TypedApiEndpoint,
396 E::Param: Debug,
397 E::Response: Debug,
398 {
399 tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
400 let result = E::handle(state, context, request.params).await;
401 match &result {
402 Err(error) => {
403 tracing::warn!(target: LOG_NET_API, path = E::PATH, ?error, "api request error");
404 }
405 _ => {
406 tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
407 }
408 }
409 result
410 }
411
412 ApiEndpoint {
413 path: E::PATH,
414 handler: Box::new(|m, mut context, request| {
415 Box::pin(async {
416 let request = request
417 .to_typed()
418 .map_err(|e| ApiError::bad_request(e.to_string()))?;
419
420 let span = tracing::info_span!(
421 target: LOG_NET_API,
422 "api_req",
423 id = REQ_ID.fetch_add(1, Ordering::SeqCst),
424 method = E::PATH,
425 );
426 let ret = handle_request::<E>(m, &mut context, request)
427 .instrument(span)
428 .await?;
429
430 context.commit_tx_result(E::PATH).await?;
431
432 Ok(serde_json::to_value(ret).expect("encoding error"))
433 })
434 }),
435 }
436 }
437}
438
439#[apply(async_trait_maybe_send!)]
446pub trait IDynCommonModuleInit: Debug {
447 fn decoder(&self) -> Decoder;
448
449 fn module_kind(&self) -> ModuleKind;
450
451 fn to_dyn_common(&self) -> DynCommonModuleInit;
452
453 async fn dump_database(
454 &self,
455 dbtx: &mut DatabaseTransaction<'_>,
456 prefix_names: Vec<String>,
457 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
458}
459
460pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
462 type Common: CommonModuleInit;
463
464 fn dump_database(
465 &self,
466 dbtx: &mut DatabaseTransaction<'_>,
467 prefix_names: Vec<String>,
468 ) -> maybe_add_send!(
469 impl Future<
470 Output = Box<
471 dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
472 >,
473 >
474 );
475}
476
477#[apply(async_trait_maybe_send!)]
478impl<T> IDynCommonModuleInit for T
479where
480 T: ModuleInit,
481{
482 fn decoder(&self) -> Decoder {
483 T::Common::decoder()
484 }
485
486 fn module_kind(&self) -> ModuleKind {
487 T::Common::KIND
488 }
489
490 fn to_dyn_common(&self) -> DynCommonModuleInit {
491 DynCommonModuleInit::from_inner(Arc::new(self.clone()))
492 }
493
494 async fn dump_database(
495 &self,
496 dbtx: &mut DatabaseTransaction<'_>,
497 prefix_names: Vec<String>,
498 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
499 <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
500 }
501}
502
503dyn_newtype_define!(
504 #[derive(Clone)]
505 pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
506);
507
508impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
509 fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
510 self.inner.as_ref()
511 }
512}
513
514impl DynCommonModuleInit {
515 pub fn from_inner(
516 inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
517 ) -> Self {
518 Self { inner }
519 }
520}
521
522#[apply(async_trait_maybe_send!)]
524pub trait CommonModuleInit: Debug + Sized {
525 const CONSENSUS_VERSION: ModuleConsensusVersion;
526 const KIND: ModuleKind;
527
528 type ClientConfig: ClientConfig;
529
530 fn decoder() -> Decoder;
531}
532
533pub trait ModuleCommon {
535 type ClientConfig: ClientConfig;
536 type Input: Input;
537 type Output: Output;
538 type OutputOutcome: OutputOutcome;
539 type ConsensusItem: ModuleConsensusItem;
540 type InputError: InputError;
541 type OutputError: OutputError;
542
543 fn decoder_builder() -> DecoderBuilder {
544 let mut decoder_builder = Decoder::builder();
545 decoder_builder.with_decodable_type::<Self::ClientConfig>();
546 decoder_builder.with_decodable_type::<Self::Input>();
547 decoder_builder.with_decodable_type::<Self::Output>();
548 decoder_builder.with_decodable_type::<Self::OutputOutcome>();
549 decoder_builder.with_decodable_type::<Self::ConsensusItem>();
550 decoder_builder.with_decodable_type::<Self::InputError>();
551 decoder_builder.with_decodable_type::<Self::OutputError>();
552
553 decoder_builder
554 }
555
556 fn decoder() -> Decoder {
557 Self::decoder_builder().build()
558 }
559}
560
561#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
566pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
567 #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
568 #[serde(skip)] PhantomData<T>,
569);
570
571#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
573pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
574 #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
575 #[serde(skip)] PhantomData<T>,
576);
577
578impl<T> fmt::Debug for SerdeModuleEncoding<T>
579where
580 T: Encodable + Decodable,
581{
582 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
583 f.write_str("SerdeModuleEncoding(")?;
584 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
585 f.write_str(")")?;
586 Ok(())
587 }
588}
589
590impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
591 fn from(value: &T) -> Self {
592 let mut bytes = vec![];
593 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
594 .expect("Writing to buffer can never fail");
595 Self(bytes, PhantomData)
596 }
597}
598
599impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
600 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
601 Decodable::consensus_decode_whole(&self.0, modules)
602 }
603
604 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
613 let mut reader = std::io::Cursor::new(&self.0);
614 let module_instance = ModuleInstanceId::consensus_decode_partial(
615 &mut reader,
616 &ModuleDecoderRegistry::default(),
617 )?;
618
619 let total_len =
620 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
621
622 decoder.decode_complete(
625 &mut reader,
626 total_len,
627 module_instance,
628 &ModuleRegistry::default(),
629 )
630 }
631}
632
633impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
634where
635 T: Encodable + Decodable,
636{
637 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
638 f.write_str("SerdeModuleEncoding2(")?;
639 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
640 f.write_str(")")?;
641 Ok(())
642 }
643}
644
645impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
646 fn from(value: &T) -> Self {
647 let mut bytes = vec![];
648 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
649 .expect("Writing to buffer can never fail");
650 Self(bytes, PhantomData)
651 }
652}
653
654impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
655 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
656 Decodable::consensus_decode_whole(&self.0, modules)
657 }
658
659 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
668 let mut reader = std::io::Cursor::new(&self.0);
669 let module_instance = ModuleInstanceId::consensus_decode_partial(
670 &mut reader,
671 &ModuleDecoderRegistry::default(),
672 )?;
673
674 let total_len =
675 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
676
677 decoder.decode_complete(
680 &mut reader,
681 total_len,
682 module_instance,
683 &ModuleRegistry::default(),
684 )
685 }
686}
687
688#[non_exhaustive]
694pub struct PeerHandle<'a> {
695 #[doc(hidden)]
698 pub num_peers: NumPeers,
699 #[doc(hidden)]
700 pub identity: PeerId,
701 #[doc(hidden)]
702 pub connections: &'a DynP2PConnections<P2PMessage>,
703}
704
705impl<'a> PeerHandle<'a> {
706 pub fn new(
707 num_peers: NumPeers,
708 identity: PeerId,
709 connections: &'a DynP2PConnections<P2PMessage>,
710 ) -> Self {
711 Self {
712 num_peers,
713 identity,
714 connections,
715 }
716 }
717
718 pub fn num_peers(&self) -> NumPeers {
719 self.num_peers
720 }
721}