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::core::{
39 ClientConfig, Decoder, DecoderBuilder, Input, InputError, ModuleConsensusItem,
40 ModuleInstanceId, ModuleKind, Output, OutputError, OutputOutcome,
41};
42use crate::db::{
43 Committable, Database, DatabaseKey, DatabaseKeyWithNotify, DatabaseRecord, DatabaseTransaction,
44 NonCommittable,
45};
46use crate::encoding::{Decodable, DecodeError, Encodable};
47use crate::fmt_utils::AbbreviateHexBytes;
48use crate::task::MaybeSend;
49use crate::{Amount, apply, async_trait_maybe_send, maybe_add_send, maybe_add_send_sync};
50
51#[derive(Debug, PartialEq, Eq)]
52pub struct InputMeta {
53 pub amount: TransactionItemAmount,
54 pub pub_key: secp256k1::PublicKey,
55}
56
57#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
63pub struct TransactionItemAmount {
64 pub amount: Amount,
65 pub fee: Amount,
66}
67
68impl TransactionItemAmount {
69 pub const ZERO: Self = Self {
70 amount: Amount::ZERO,
71 fee: Amount::ZERO,
72 };
73}
74
75#[derive(Debug, Serialize, Deserialize, Clone)]
77pub struct ApiRequest<T> {
78 pub auth: Option<ApiAuth>,
80 pub params: T,
82}
83
84pub type ApiRequestErased = ApiRequest<JsonValue>;
85
86impl Default for ApiRequestErased {
87 fn default() -> Self {
88 Self {
89 auth: None,
90 params: JsonValue::Null,
91 }
92 }
93}
94
95impl ApiRequestErased {
96 pub fn new<T: Serialize>(params: T) -> Self {
97 Self {
98 auth: None,
99 params: serde_json::to_value(params)
100 .expect("parameter serialization error - this should not happen"),
101 }
102 }
103
104 pub fn to_json(&self) -> JsonValue {
105 serde_json::to_value(self).expect("parameter serialization error - this should not happen")
106 }
107
108 pub fn with_auth(self, auth: ApiAuth) -> Self {
109 Self {
110 auth: Some(auth),
111 params: self.params,
112 }
113 }
114
115 pub fn to_typed<T: serde::de::DeserializeOwned>(
116 self,
117 ) -> Result<ApiRequest<T>, serde_json::Error> {
118 Ok(ApiRequest {
119 auth: self.auth,
120 params: serde_json::from_value::<T>(self.params)?,
121 })
122 }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub enum ApiMethod {
127 Core(String),
128 Module(ModuleInstanceId, String),
129}
130
131impl fmt::Display for ApiMethod {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 match self {
134 Self::Core(s) => f.write_str(s),
135 Self::Module(module_id, s) => f.write_fmt(format_args!("{module_id}-{s}")),
136 }
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct IrohApiRequest {
142 pub method: ApiMethod,
143 pub request: ApiRequestErased,
144}
145
146pub const FEDIMINT_API_ALPN: &[u8] = b"FEDIMINT_API_ALPN";
147
148#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
150pub struct ApiAuth(pub String);
151
152impl Debug for ApiAuth {
153 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
154 write!(f, "ApiAuth(****)")
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct ApiError {
160 pub code: i32,
161 pub message: String,
162}
163
164pub type ApiResult<T> = Result<T, ApiError>;
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;
352
353use self::registry::ModuleDecoderRegistry;
354
355type HandlerFnReturn<'a> =
356 Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
357type HandlerFn<M> = Box<
358 maybe_add_send_sync!(
359 dyn for<'a> Fn(&'a M, ApiEndpointContext<'a>, ApiRequestErased) -> HandlerFnReturn<'a>
360 ),
361>;
362
363pub struct ApiEndpoint<M> {
365 pub path: &'static str,
370 pub handler: HandlerFn<M>,
374}
375
376static REQ_ID: AtomicU64 = AtomicU64::new(0);
378
379impl ApiEndpoint<()> {
381 pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
382 where
383 <E as TypedApiEndpoint>::Response: MaybeSend,
384 E::Param: Debug,
385 E::Response: Debug,
386 {
387 async fn handle_request<'state, 'context, 'dbtx, E>(
388 state: &'state E::State,
389 context: &'context mut ApiEndpointContext<'dbtx>,
390 request: ApiRequest<E::Param>,
391 ) -> Result<E::Response, ApiError>
392 where
393 'dbtx: 'context,
394 E: TypedApiEndpoint,
395 E::Param: Debug,
396 E::Response: Debug,
397 {
398 tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
399 let result = E::handle(state, context, request.params).await;
400 match &result {
401 Err(error) => {
402 tracing::warn!(target: LOG_NET_API, path = E::PATH, ?error, "api request error");
403 }
404 _ => {
405 tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
406 }
407 }
408 result
409 }
410
411 ApiEndpoint {
412 path: E::PATH,
413 handler: Box::new(|m, mut context, request| {
414 Box::pin(async {
415 let request = request
416 .to_typed()
417 .map_err(|e| ApiError::bad_request(e.to_string()))?;
418
419 let span = tracing::info_span!(
420 target: LOG_NET_API,
421 "api_req",
422 id = REQ_ID.fetch_add(1, Ordering::SeqCst),
423 method = E::PATH,
424 );
425 let ret = handle_request::<E>(m, &mut context, request)
426 .instrument(span)
427 .await?;
428
429 context.commit_tx_result(E::PATH).await?;
430
431 Ok(serde_json::to_value(ret).expect("encoding error"))
432 })
433 }),
434 }
435 }
436}
437
438#[apply(async_trait_maybe_send!)]
445pub trait IDynCommonModuleInit: Debug {
446 fn decoder(&self) -> Decoder;
447
448 fn module_kind(&self) -> ModuleKind;
449
450 fn to_dyn_common(&self) -> DynCommonModuleInit;
451
452 async fn dump_database(
453 &self,
454 dbtx: &mut DatabaseTransaction<'_>,
455 prefix_names: Vec<String>,
456 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
457}
458
459pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
461 type Common: CommonModuleInit;
462
463 fn dump_database(
464 &self,
465 dbtx: &mut DatabaseTransaction<'_>,
466 prefix_names: Vec<String>,
467 ) -> maybe_add_send!(
468 impl Future<
469 Output = Box<
470 dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
471 >,
472 >
473 );
474}
475
476#[apply(async_trait_maybe_send!)]
477impl<T> IDynCommonModuleInit for T
478where
479 T: ModuleInit,
480{
481 fn decoder(&self) -> Decoder {
482 T::Common::decoder()
483 }
484
485 fn module_kind(&self) -> ModuleKind {
486 T::Common::KIND
487 }
488
489 fn to_dyn_common(&self) -> DynCommonModuleInit {
490 DynCommonModuleInit::from_inner(Arc::new(self.clone()))
491 }
492
493 async fn dump_database(
494 &self,
495 dbtx: &mut DatabaseTransaction<'_>,
496 prefix_names: Vec<String>,
497 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
498 <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
499 }
500}
501
502dyn_newtype_define!(
503 #[derive(Clone)]
504 pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
505);
506
507impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
508 fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
509 self.inner.as_ref()
510 }
511}
512
513impl DynCommonModuleInit {
514 pub fn from_inner(
515 inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
516 ) -> Self {
517 Self { inner }
518 }
519}
520
521#[apply(async_trait_maybe_send!)]
523pub trait CommonModuleInit: Debug + Sized {
524 const CONSENSUS_VERSION: ModuleConsensusVersion;
525 const KIND: ModuleKind;
526
527 type ClientConfig: ClientConfig;
528
529 fn decoder() -> Decoder;
530}
531
532pub trait ModuleCommon {
534 type ClientConfig: ClientConfig;
535 type Input: Input;
536 type Output: Output;
537 type OutputOutcome: OutputOutcome;
538 type ConsensusItem: ModuleConsensusItem;
539 type InputError: InputError;
540 type OutputError: OutputError;
541
542 fn decoder_builder() -> DecoderBuilder {
543 let mut decoder_builder = Decoder::builder();
544 decoder_builder.with_decodable_type::<Self::ClientConfig>();
545 decoder_builder.with_decodable_type::<Self::Input>();
546 decoder_builder.with_decodable_type::<Self::Output>();
547 decoder_builder.with_decodable_type::<Self::OutputOutcome>();
548 decoder_builder.with_decodable_type::<Self::ConsensusItem>();
549 decoder_builder.with_decodable_type::<Self::InputError>();
550 decoder_builder.with_decodable_type::<Self::OutputError>();
551
552 decoder_builder
553 }
554
555 fn decoder() -> Decoder {
556 Self::decoder_builder().build()
557 }
558}
559
560#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
565pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
566 #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
567 #[serde(skip)] PhantomData<T>,
568);
569
570#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
572pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
573 #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
574 #[serde(skip)] PhantomData<T>,
575);
576
577impl<T> fmt::Debug for SerdeModuleEncoding<T>
578where
579 T: Encodable + Decodable,
580{
581 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
582 f.write_str("SerdeModuleEncoding(")?;
583 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
584 f.write_str(")")?;
585 Ok(())
586 }
587}
588
589impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
590 fn from(value: &T) -> Self {
591 let mut bytes = vec![];
592 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
593 .expect("Writing to buffer can never fail");
594 Self(bytes, PhantomData)
595 }
596}
597
598impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
599 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
600 Decodable::consensus_decode_whole(&self.0, modules)
601 }
602
603 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
612 let mut reader = std::io::Cursor::new(&self.0);
613 let module_instance = ModuleInstanceId::consensus_decode_partial(
614 &mut reader,
615 &ModuleDecoderRegistry::default(),
616 )?;
617
618 let total_len =
619 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
620
621 decoder.decode_complete(
624 &mut reader,
625 total_len,
626 module_instance,
627 &ModuleRegistry::default(),
628 )
629 }
630}
631
632impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
633where
634 T: Encodable + Decodable,
635{
636 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
637 f.write_str("SerdeModuleEncoding2(")?;
638 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
639 f.write_str(")")?;
640 Ok(())
641 }
642}
643
644impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
645 fn from(value: &T) -> Self {
646 let mut bytes = vec![];
647 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
648 .expect("Writing to buffer can never fail");
649 Self(bytes, PhantomData)
650 }
651}
652
653impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
654 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
655 Decodable::consensus_decode_whole(&self.0, modules)
656 }
657
658 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
667 let mut reader = std::io::Cursor::new(&self.0);
668 let module_instance = ModuleInstanceId::consensus_decode_partial(
669 &mut reader,
670 &ModuleDecoderRegistry::default(),
671 )?;
672
673 let total_len =
674 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
675
676 decoder.decode_complete(
679 &mut reader,
680 total_len,
681 module_instance,
682 &ModuleRegistry::default(),
683 )
684 }
685}