1pub mod audit;
20pub mod registry;
21
22use std::collections::{BTreeMap, BTreeSet};
23use std::error::Error;
24use std::fmt::{self, Debug, Formatter};
25use std::marker::PhantomData;
26use std::ops;
27use std::pin::Pin;
28use std::sync::Arc;
29use std::sync::atomic::{AtomicU64, Ordering};
30
31use fedimint_logging::LOG_NET_API;
32use fedimint_util_error::FmtCompactAnyhow as _;
33use futures::Future;
34use jsonrpsee_core::JsonValue;
35use registry::ModuleRegistry;
36use serde::{Deserialize, Serialize};
37use tracing::Instrument;
38
39mod version;
41pub use self::version::*;
42use crate::core::{
43 ClientConfig, Decoder, DecoderBuilder, Input, InputError, ModuleConsensusItem,
44 ModuleInstanceId, ModuleKind, Output, OutputError, OutputOutcome,
45};
46use crate::db::{
47 Committable, Database, DatabaseKey, DatabaseKeyWithNotify, DatabaseRecord, DatabaseTransaction,
48 NonCommittable,
49};
50use crate::encoding::{Decodable, DecodeError, Encodable};
51use crate::fmt_utils::AbbreviateHexBytes;
52use crate::task::MaybeSend;
53use crate::util::FmtCompact;
54use crate::{Amount, apply, async_trait_maybe_send, maybe_add_send, maybe_add_send_sync};
55
56#[derive(Debug, PartialEq, Eq)]
57pub struct InputMeta {
58 pub amount: TransactionItemAmounts,
59 pub pub_key: secp256k1::PublicKey,
60}
61
62#[derive(
64 Debug,
65 Clone,
66 Copy,
67 Eq,
68 PartialEq,
69 Hash,
70 PartialOrd,
71 Ord,
72 Deserialize,
73 Serialize,
74 Encodable,
75 Decodable,
76 Default,
77)]
78pub struct AmountUnit(u64);
79
80impl AmountUnit {
81 pub const BITCOIN: Self = Self(0);
85
86 pub fn is_bitcoin(self) -> bool {
87 self == Self::BITCOIN
88 }
89
90 pub fn new_custom(unit: u64) -> Self {
91 Self(unit)
92 }
93
94 pub const fn bitcoin() -> Self {
95 Self::BITCOIN
96 }
97}
98
99#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
100pub struct AmountWithUnit {
101 amounts: Amount,
102 unit: AmountUnit,
103}
104
105#[derive(Debug, Clone, Eq, PartialEq, Hash)]
112pub struct Amounts(BTreeMap<AmountUnit, Amount>);
113
114impl ops::Deref for Amounts {
117 type Target = BTreeMap<AmountUnit, Amount>;
118
119 fn deref(&self) -> &Self::Target {
120 &self.0
121 }
122}
123
124impl Amounts {
125 pub const ZERO: Self = Self(BTreeMap::new());
126
127 pub fn new_bitcoin(amount: Amount) -> Self {
128 if amount == Amount::ZERO {
129 Self(BTreeMap::from([]))
130 } else {
131 Self(BTreeMap::from([(AmountUnit::BITCOIN, amount)]))
132 }
133 }
134
135 pub fn new_bitcoin_msats(msats: u64) -> Self {
136 Self::new_bitcoin(Amount::from_msats(msats))
137 }
138
139 pub fn new_custom(unit: AmountUnit, amount: Amount) -> Self {
140 if amount == Amount::ZERO {
141 Self(BTreeMap::from([]))
142 } else {
143 Self(BTreeMap::from([(unit, amount)]))
144 }
145 }
146
147 pub fn checked_add(mut self, rhs: &Self) -> Option<Self> {
148 self.checked_add_mut(rhs);
149
150 Some(self)
151 }
152
153 pub fn checked_add_mut(&mut self, rhs: &Self) -> Option<&mut Self> {
154 for (unit, amount) in &rhs.0 {
155 debug_assert!(
156 *amount != Amount::ZERO,
157 "`Amounts` must not add (/remove) zero-amount entries"
158 );
159 let prev = self.0.entry(*unit).or_default();
160
161 *prev = prev.checked_add(*amount)?;
162 }
163
164 Some(self)
165 }
166
167 pub fn checked_add_bitcoin(self, amount: Amount) -> Option<Self> {
168 self.checked_add_unit(amount, AmountUnit::BITCOIN)
169 }
170
171 pub fn checked_add_unit(mut self, amount: Amount, unit: AmountUnit) -> Option<Self> {
172 if amount == Amount::ZERO {
173 return Some(self);
174 }
175
176 let prev = self.0.entry(unit).or_default();
177
178 *prev = prev.checked_add(amount)?;
179
180 Some(self)
181 }
182
183 pub fn remove(&mut self, unit: &AmountUnit) -> Option<Amount> {
184 self.0.remove(unit)
185 }
186
187 pub fn get_bitcoin(&self) -> Amount {
188 self.get(&AmountUnit::BITCOIN).copied().unwrap_or_default()
189 }
190
191 pub fn expect_only_bitcoin(&self) -> Amount {
192 #[allow(clippy::option_if_let_else)] match self.get(&AmountUnit::BITCOIN) {
194 Some(amount) => {
195 assert!(
196 self.len() == 1,
197 "Amounts expected to contain only bitcoin and no other currencies"
198 );
199 *amount
200 }
201 None => Amount::ZERO,
202 }
203 }
204
205 pub fn iter_units(&self) -> impl Iterator<Item = AmountUnit> {
206 self.0.keys().copied()
207 }
208
209 pub fn units(&self) -> BTreeSet<AmountUnit> {
210 self.0.keys().copied().collect()
211 }
212}
213
214impl IntoIterator for Amounts {
215 type Item = (AmountUnit, Amount);
216
217 type IntoIter = <BTreeMap<AmountUnit, Amount> as IntoIterator>::IntoIter;
218
219 fn into_iter(self) -> Self::IntoIter {
220 self.0.into_iter()
221 }
222}
223
224#[derive(Debug, Clone, Eq, PartialEq, Hash)]
230pub struct TransactionItemAmounts {
231 pub amounts: Amounts,
232 pub fees: Amounts,
233}
234
235impl TransactionItemAmounts {
236 pub fn checked_add(self, rhs: &Self) -> Option<Self> {
237 Some(Self {
238 amounts: self.amounts.checked_add(&rhs.amounts)?,
239 fees: self.fees.checked_add(&rhs.fees)?,
240 })
241 }
242}
243
244impl TransactionItemAmounts {
245 pub const ZERO: Self = Self {
246 amounts: Amounts::ZERO,
247 fees: Amounts::ZERO,
248 };
249}
250
251#[derive(Debug, Serialize, Deserialize, Clone)]
253pub struct ApiRequest<T> {
254 pub auth: Option<ApiAuth>,
256 pub params: T,
258}
259
260pub type ApiRequestErased = ApiRequest<JsonValue>;
261
262impl Default for ApiRequestErased {
263 fn default() -> Self {
264 Self {
265 auth: None,
266 params: JsonValue::Null,
267 }
268 }
269}
270
271impl ApiRequestErased {
272 pub fn new<T: Serialize>(params: T) -> Self {
273 Self {
274 auth: None,
275 params: serde_json::to_value(params)
276 .expect("parameter serialization error - this should not happen"),
277 }
278 }
279
280 pub fn to_json(&self) -> JsonValue {
281 serde_json::to_value(self).expect("parameter serialization error - this should not happen")
282 }
283
284 pub fn with_auth(self, auth: ApiAuth) -> Self {
285 Self {
286 auth: Some(auth),
287 params: self.params,
288 }
289 }
290
291 pub fn to_typed<T: serde::de::DeserializeOwned>(
292 self,
293 ) -> Result<ApiRequest<T>, serde_json::Error> {
294 Ok(ApiRequest {
295 auth: self.auth,
296 params: serde_json::from_value::<T>(self.params)?,
297 })
298 }
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub enum ApiMethod {
303 Core(String),
304 Module(ModuleInstanceId, String),
305}
306
307impl fmt::Display for ApiMethod {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 match self {
310 Self::Core(s) => f.write_str(s),
311 Self::Module(module_id, s) => f.write_fmt(format_args!("{module_id}-{s}")),
312 }
313 }
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct IrohApiRequest {
318 pub method: ApiMethod,
319 pub request: ApiRequestErased,
320}
321
322pub const FEDIMINT_API_ALPN: &[u8] = b"FEDIMINT_API_ALPN";
323
324#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
326pub struct ApiAuth(pub String);
327
328impl Debug for ApiAuth {
329 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
330 write!(f, "ApiAuth(****)")
331 }
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct ApiError {
336 pub code: i32,
337 pub message: String,
338}
339
340impl Error for ApiError {}
341
342impl fmt::Display for ApiError {
343 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
344 f.write_fmt(format_args!("{} {}", self.code, self.message))
345 }
346}
347
348pub type ApiResult<T> = Result<T, ApiError>;
349
350impl ApiError {
351 pub fn new(code: i32, message: String) -> Self {
352 Self { code, message }
353 }
354
355 pub fn not_found(message: String) -> Self {
356 Self::new(404, message)
357 }
358
359 pub fn bad_request(message: String) -> Self {
360 Self::new(400, message)
361 }
362
363 pub fn unauthorized() -> Self {
364 Self::new(401, "Invalid authorization".to_string())
365 }
366
367 pub fn server_error(message: String) -> Self {
368 Self::new(500, message)
369 }
370}
371
372pub struct ApiEndpointContext<'dbtx> {
374 db: Database,
375 dbtx: DatabaseTransaction<'dbtx, Committable>,
376 has_auth: bool,
377 request_auth: Option<ApiAuth>,
378}
379
380impl<'a> ApiEndpointContext<'a> {
381 pub fn new(
383 db: Database,
384 dbtx: DatabaseTransaction<'a, Committable>,
385 has_auth: bool,
386 request_auth: Option<ApiAuth>,
387 ) -> Self {
388 Self {
389 db,
390 dbtx,
391 has_auth,
392 request_auth,
393 }
394 }
395
396 pub fn dbtx<'s, 'mtx>(&'s mut self) -> DatabaseTransaction<'mtx, NonCommittable>
398 where
399 'a: 'mtx,
400 's: 'mtx,
401 {
402 self.dbtx.to_ref_nc()
404 }
405
406 pub fn request_auth(&self) -> Option<ApiAuth> {
409 self.request_auth.clone()
410 }
411
412 pub fn has_auth(&self) -> bool {
415 self.has_auth
416 }
417
418 pub fn db(&self) -> Database {
419 self.db.clone()
420 }
421
422 pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
424 where
425 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
426 {
427 let db = self.db.clone();
428 async move { db.wait_key_exists(&key).await }
431 }
432
433 pub fn wait_value_matches<K>(
435 &self,
436 key: K,
437 matcher: impl Fn(&K::Value) -> bool + Copy,
438 ) -> impl Future<Output = K::Value>
439 where
440 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
441 {
442 let db = self.db.clone();
443 async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
444 }
445
446 pub async fn commit_tx_result(self, path: &'static str) -> Result<(), ApiError> {
448 self.dbtx.commit_tx_result().await.map_err(|err| {
449 tracing::warn!(
450 target: fedimint_logging::LOG_NET_API,
451 err = %err.fmt_compact_anyhow(),
452 %path,
453 "API server error when writing to database",
454 );
455 ApiError {
456 code: 500,
457 message: "API server error when writing to database".to_string(),
458 }
459 })
460 }
461}
462
463#[apply(async_trait_maybe_send!)]
464pub trait TypedApiEndpoint {
465 type State: Sync;
466
467 const PATH: &'static str;
469
470 type Param: serde::de::DeserializeOwned + Send;
471 type Response: serde::Serialize;
472
473 async fn handle<'state, 'context, 'dbtx>(
474 state: &'state Self::State,
475 context: &'context mut ApiEndpointContext<'dbtx>,
476 request: Self::Param,
477 ) -> Result<Self::Response, ApiError>
478 where
479 'dbtx: 'context;
480}
481
482pub use serde_json;
483
484#[macro_export]
500macro_rules! __api_endpoint {
501 (
502 $path:expr_2021,
503 $version_introduced:expr_2021,
506 async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
507 ) => {{
508 struct Endpoint;
509
510 #[$crate::apply($crate::async_trait_maybe_send!)]
511 impl $crate::module::TypedApiEndpoint for Endpoint {
512 #[allow(deprecated)]
513 const PATH: &'static str = $path;
514 type State = $state_ty;
515 type Param = $param_ty;
516 type Response = $resp_ty;
517
518 async fn handle<'state, 'context, 'dbtx>(
519 $state: &'state Self::State,
520 $context: &'context mut $crate::module::ApiEndpointContext<'dbtx>,
521 $param: Self::Param,
522 ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
523 {
524 const __API_VERSION: $crate::module::ApiVersion = $version_introduced;
526 }
527 $body
528 }
529 }
530
531 $crate::module::ApiEndpoint::from_typed::<Endpoint>()
532 }};
533}
534
535pub use __api_endpoint as api_endpoint;
536
537use self::registry::ModuleDecoderRegistry;
538
539type HandlerFnReturn<'a> =
540 Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
541type HandlerFn<M> = Box<
542 maybe_add_send_sync!(
543 dyn for<'a> Fn(&'a M, ApiEndpointContext<'a>, ApiRequestErased) -> HandlerFnReturn<'a>
544 ),
545>;
546
547pub struct ApiEndpoint<M> {
549 pub path: &'static str,
554 pub handler: HandlerFn<M>,
558}
559
560static REQ_ID: AtomicU64 = AtomicU64::new(0);
562
563impl ApiEndpoint<()> {
565 pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
566 where
567 <E as TypedApiEndpoint>::Response: MaybeSend,
568 E::Param: Debug,
569 E::Response: Debug,
570 {
571 async fn handle_request<'state, 'context, 'dbtx, E>(
572 state: &'state E::State,
573 context: &'context mut ApiEndpointContext<'dbtx>,
574 request: ApiRequest<E::Param>,
575 ) -> Result<E::Response, ApiError>
576 where
577 'dbtx: 'context,
578 E: TypedApiEndpoint,
579 E::Param: Debug,
580 E::Response: Debug,
581 {
582 tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
583 let result = E::handle(state, context, request.params).await;
584 match &result {
585 Err(err) => {
586 tracing::warn!(target: LOG_NET_API, path = E::PATH, err = %err.fmt_compact(), "api request error");
587 }
588 _ => {
589 tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
590 }
591 }
592 result
593 }
594
595 ApiEndpoint {
596 path: E::PATH,
597 handler: Box::new(|m, mut context, request| {
598 Box::pin(async {
599 let request = request
600 .to_typed()
601 .map_err(|e| ApiError::bad_request(e.to_string()))?;
602
603 let span = tracing::info_span!(
604 target: LOG_NET_API,
605 "api_req",
606 id = REQ_ID.fetch_add(1, Ordering::SeqCst),
607 method = E::PATH,
608 );
609 let ret = handle_request::<E>(m, &mut context, request)
610 .instrument(span)
611 .await?;
612
613 context.commit_tx_result(E::PATH).await?;
614
615 Ok(serde_json::to_value(ret).expect("encoding error"))
616 })
617 }),
618 }
619 }
620}
621
622#[apply(async_trait_maybe_send!)]
629pub trait IDynCommonModuleInit: Debug {
630 fn decoder(&self) -> Decoder;
631
632 fn module_kind(&self) -> ModuleKind;
633
634 fn to_dyn_common(&self) -> DynCommonModuleInit;
635
636 async fn dump_database(
637 &self,
638 dbtx: &mut DatabaseTransaction<'_>,
639 prefix_names: Vec<String>,
640 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
641}
642
643pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
645 type Common: CommonModuleInit;
646
647 fn dump_database(
648 &self,
649 dbtx: &mut DatabaseTransaction<'_>,
650 prefix_names: Vec<String>,
651 ) -> maybe_add_send!(
652 impl Future<
653 Output = Box<
654 dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
655 >,
656 >
657 );
658}
659
660#[apply(async_trait_maybe_send!)]
661impl<T> IDynCommonModuleInit for T
662where
663 T: ModuleInit,
664{
665 fn decoder(&self) -> Decoder {
666 T::Common::decoder()
667 }
668
669 fn module_kind(&self) -> ModuleKind {
670 T::Common::KIND
671 }
672
673 fn to_dyn_common(&self) -> DynCommonModuleInit {
674 DynCommonModuleInit::from_inner(Arc::new(self.clone()))
675 }
676
677 async fn dump_database(
678 &self,
679 dbtx: &mut DatabaseTransaction<'_>,
680 prefix_names: Vec<String>,
681 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
682 <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
683 }
684}
685
686dyn_newtype_define!(
687 #[derive(Clone)]
688 pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
689);
690
691impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
692 fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
693 self.inner.as_ref()
694 }
695}
696
697impl DynCommonModuleInit {
698 pub fn from_inner(
699 inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
700 ) -> Self {
701 Self { inner }
702 }
703}
704
705#[apply(async_trait_maybe_send!)]
707pub trait CommonModuleInit: Debug + Sized {
708 const CONSENSUS_VERSION: ModuleConsensusVersion;
709 const KIND: ModuleKind;
710
711 type ClientConfig: ClientConfig;
712
713 fn decoder() -> Decoder;
714}
715
716pub trait ModuleCommon {
718 type ClientConfig: ClientConfig;
719 type Input: Input;
720 type Output: Output;
721 type OutputOutcome: OutputOutcome;
722 type ConsensusItem: ModuleConsensusItem;
723 type InputError: InputError;
724 type OutputError: OutputError;
725
726 fn decoder_builder() -> DecoderBuilder {
727 let mut decoder_builder = Decoder::builder();
728 decoder_builder.with_decodable_type::<Self::ClientConfig>();
729 decoder_builder.with_decodable_type::<Self::Input>();
730 decoder_builder.with_decodable_type::<Self::Output>();
731 decoder_builder.with_decodable_type::<Self::OutputOutcome>();
732 decoder_builder.with_decodable_type::<Self::ConsensusItem>();
733 decoder_builder.with_decodable_type::<Self::InputError>();
734 decoder_builder.with_decodable_type::<Self::OutputError>();
735
736 decoder_builder
737 }
738
739 fn decoder() -> Decoder {
740 Self::decoder_builder().build()
741 }
742}
743
744#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
749pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
750 #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
751 #[serde(skip)] PhantomData<T>,
752);
753
754#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
756pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
757 #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
758 #[serde(skip)] PhantomData<T>,
759);
760
761impl<T> fmt::Debug for SerdeModuleEncoding<T>
762where
763 T: Encodable + Decodable,
764{
765 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
766 f.write_str("SerdeModuleEncoding(")?;
767 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
768 f.write_str(")")?;
769 Ok(())
770 }
771}
772
773impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
774 fn from(value: &T) -> Self {
775 let mut bytes = vec![];
776 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
777 .expect("Writing to buffer can never fail");
778 Self(bytes, PhantomData)
779 }
780}
781
782impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
783 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
784 Decodable::consensus_decode_whole(&self.0, modules)
785 }
786
787 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
796 let mut reader = std::io::Cursor::new(&self.0);
797 let module_instance = ModuleInstanceId::consensus_decode_partial(
798 &mut reader,
799 &ModuleDecoderRegistry::default(),
800 )?;
801
802 let total_len =
803 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
804
805 decoder.decode_complete(
808 &mut reader,
809 total_len,
810 module_instance,
811 &ModuleRegistry::default(),
812 )
813 }
814}
815
816impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
817where
818 T: Encodable + Decodable,
819{
820 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
821 f.write_str("SerdeModuleEncoding2(")?;
822 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
823 f.write_str(")")?;
824 Ok(())
825 }
826}
827
828impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
829 fn from(value: &T) -> Self {
830 let mut bytes = vec![];
831 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
832 .expect("Writing to buffer can never fail");
833 Self(bytes, PhantomData)
834 }
835}
836
837impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
838 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
839 Decodable::consensus_decode_whole(&self.0, modules)
840 }
841
842 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
851 let mut reader = std::io::Cursor::new(&self.0);
852 let module_instance = ModuleInstanceId::consensus_decode_partial(
853 &mut reader,
854 &ModuleDecoderRegistry::default(),
855 )?;
856
857 let total_len =
858 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
859
860 decoder.decode_complete(
863 &mut reader,
864 total_len,
865 module_instance,
866 &ModuleRegistry::default(),
867 )
868 }
869}