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)]
328pub struct ApiAuth(pub String);
329
330impl Debug for ApiAuth {
331 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
332 write!(f, "ApiAuth(****)")
333 }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct ApiError {
338 pub code: i32,
339 pub message: String,
340}
341
342impl Error for ApiError {}
343
344impl fmt::Display for ApiError {
345 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
346 f.write_fmt(format_args!("{} {}", self.code, self.message))
347 }
348}
349
350pub type ApiResult<T> = Result<T, ApiError>;
351
352impl ApiError {
353 pub fn new(code: i32, message: String) -> Self {
354 Self { code, message }
355 }
356
357 pub fn not_found(message: String) -> Self {
358 Self::new(404, message)
359 }
360
361 pub fn bad_request(message: String) -> Self {
362 Self::new(400, message)
363 }
364
365 pub fn unauthorized() -> Self {
366 Self::new(401, "Invalid authorization".to_string())
367 }
368
369 pub fn server_error(message: String) -> Self {
370 Self::new(500, message)
371 }
372}
373
374pub struct ApiEndpointContext<'dbtx> {
376 db: Database,
377 dbtx: DatabaseTransaction<'dbtx, Committable>,
378 has_auth: bool,
379 request_auth: Option<ApiAuth>,
380}
381
382impl<'a> ApiEndpointContext<'a> {
383 pub fn new(
385 db: Database,
386 dbtx: DatabaseTransaction<'a, Committable>,
387 has_auth: bool,
388 request_auth: Option<ApiAuth>,
389 ) -> Self {
390 Self {
391 db,
392 dbtx,
393 has_auth,
394 request_auth,
395 }
396 }
397
398 pub fn dbtx<'s, 'mtx>(&'s mut self) -> DatabaseTransaction<'mtx, NonCommittable>
400 where
401 'a: 'mtx,
402 's: 'mtx,
403 {
404 self.dbtx.to_ref_nc()
406 }
407
408 pub fn request_auth(&self) -> Option<ApiAuth> {
411 self.request_auth.clone()
412 }
413
414 pub fn has_auth(&self) -> bool {
417 self.has_auth
418 }
419
420 pub fn db(&self) -> Database {
421 self.db.clone()
422 }
423
424 pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
426 where
427 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
428 {
429 let db = self.db.clone();
430 async move { db.wait_key_exists(&key).await }
433 }
434
435 pub fn wait_value_matches<K>(
437 &self,
438 key: K,
439 matcher: impl Fn(&K::Value) -> bool + Copy,
440 ) -> impl Future<Output = K::Value>
441 where
442 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
443 {
444 let db = self.db.clone();
445 async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
446 }
447
448 pub async fn commit_tx_result(self, path: &'static str) -> Result<(), ApiError> {
450 self.dbtx.commit_tx_result().await.map_err(|err| {
451 tracing::warn!(
452 target: fedimint_logging::LOG_NET_API,
453 err = %err.fmt_compact_anyhow(),
454 %path,
455 "API server error when writing to database",
456 );
457 ApiError {
458 code: 500,
459 message: "API server error when writing to database".to_string(),
460 }
461 })
462 }
463}
464
465#[apply(async_trait_maybe_send!)]
466pub trait TypedApiEndpoint {
467 type State: Sync;
468
469 const PATH: &'static str;
471
472 type Param: serde::de::DeserializeOwned + Send;
473 type Response: serde::Serialize;
474
475 async fn handle<'state, 'context, 'dbtx>(
476 state: &'state Self::State,
477 context: &'context mut ApiEndpointContext<'dbtx>,
478 request: Self::Param,
479 ) -> Result<Self::Response, ApiError>
480 where
481 'dbtx: 'context;
482}
483
484pub use serde_json;
485
486#[macro_export]
502macro_rules! __api_endpoint {
503 (
504 $path:expr_2021,
505 $version_introduced:expr_2021,
508 async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
509 ) => {{
510 struct Endpoint;
511
512 #[$crate::apply($crate::async_trait_maybe_send!)]
513 impl $crate::module::TypedApiEndpoint for Endpoint {
514 #[allow(deprecated)]
515 const PATH: &'static str = $path;
516 type State = $state_ty;
517 type Param = $param_ty;
518 type Response = $resp_ty;
519
520 async fn handle<'state, 'context, 'dbtx>(
521 $state: &'state Self::State,
522 $context: &'context mut $crate::module::ApiEndpointContext<'dbtx>,
523 $param: Self::Param,
524 ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
525 {
526 const __API_VERSION: $crate::module::ApiVersion = $version_introduced;
528 }
529 $body
530 }
531 }
532
533 $crate::module::ApiEndpoint::from_typed::<Endpoint>()
534 }};
535}
536
537pub use __api_endpoint as api_endpoint;
538
539use self::registry::ModuleDecoderRegistry;
540
541type HandlerFnReturn<'a> =
542 Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
543type HandlerFn<M> = Box<
544 maybe_add_send_sync!(
545 dyn for<'a> Fn(&'a M, ApiEndpointContext<'a>, ApiRequestErased) -> HandlerFnReturn<'a>
546 ),
547>;
548
549pub struct ApiEndpoint<M> {
551 pub path: &'static str,
556 pub handler: HandlerFn<M>,
560}
561
562static REQ_ID: AtomicU64 = AtomicU64::new(0);
564
565impl ApiEndpoint<()> {
567 pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
568 where
569 <E as TypedApiEndpoint>::Response: MaybeSend,
570 E::Param: Debug,
571 E::Response: Debug,
572 {
573 async fn handle_request<'state, 'context, 'dbtx, E>(
574 state: &'state E::State,
575 context: &'context mut ApiEndpointContext<'dbtx>,
576 request: ApiRequest<E::Param>,
577 ) -> Result<E::Response, ApiError>
578 where
579 'dbtx: 'context,
580 E: TypedApiEndpoint,
581 E::Param: Debug,
582 E::Response: Debug,
583 {
584 tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
585 let result = E::handle(state, context, request.params).await;
586 match &result {
587 Err(err) => {
588 tracing::warn!(target: LOG_NET_API, path = E::PATH, err = %err.fmt_compact(), "api request error");
589 }
590 _ => {
591 tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
592 }
593 }
594 result
595 }
596
597 ApiEndpoint {
598 path: E::PATH,
599 handler: Box::new(|m, mut context, request| {
600 Box::pin(async {
601 let request = request
602 .to_typed()
603 .map_err(|e| ApiError::bad_request(e.to_string()))?;
604
605 let span = tracing::info_span!(
606 target: LOG_NET_API,
607 "api_req",
608 id = REQ_ID.fetch_add(1, Ordering::SeqCst),
609 method = E::PATH,
610 );
611 let ret = handle_request::<E>(m, &mut context, request)
612 .instrument(span)
613 .await?;
614
615 context.commit_tx_result(E::PATH).await?;
616
617 Ok(serde_json::to_value(ret).expect("encoding error"))
618 })
619 }),
620 }
621 }
622}
623
624#[apply(async_trait_maybe_send!)]
631pub trait IDynCommonModuleInit: Debug {
632 fn decoder(&self) -> Decoder;
633
634 fn module_kind(&self) -> ModuleKind;
635
636 fn to_dyn_common(&self) -> DynCommonModuleInit;
637
638 async fn dump_database(
639 &self,
640 dbtx: &mut DatabaseTransaction<'_>,
641 prefix_names: Vec<String>,
642 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
643}
644
645pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
647 type Common: CommonModuleInit;
648
649 fn dump_database(
650 &self,
651 dbtx: &mut DatabaseTransaction<'_>,
652 prefix_names: Vec<String>,
653 ) -> maybe_add_send!(
654 impl Future<
655 Output = Box<
656 dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
657 >,
658 >
659 );
660}
661
662#[apply(async_trait_maybe_send!)]
663impl<T> IDynCommonModuleInit for T
664where
665 T: ModuleInit,
666{
667 fn decoder(&self) -> Decoder {
668 T::Common::decoder()
669 }
670
671 fn module_kind(&self) -> ModuleKind {
672 T::Common::KIND
673 }
674
675 fn to_dyn_common(&self) -> DynCommonModuleInit {
676 DynCommonModuleInit::from_inner(Arc::new(self.clone()))
677 }
678
679 async fn dump_database(
680 &self,
681 dbtx: &mut DatabaseTransaction<'_>,
682 prefix_names: Vec<String>,
683 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
684 <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
685 }
686}
687
688dyn_newtype_define!(
689 #[derive(Clone)]
690 pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
691);
692
693impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
694 fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
695 self.inner.as_ref()
696 }
697}
698
699impl DynCommonModuleInit {
700 pub fn from_inner(
701 inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
702 ) -> Self {
703 Self { inner }
704 }
705}
706
707#[apply(async_trait_maybe_send!)]
709pub trait CommonModuleInit: Debug + Sized {
710 const CONSENSUS_VERSION: ModuleConsensusVersion;
711 const KIND: ModuleKind;
712
713 type ClientConfig: ClientConfig;
714
715 fn decoder() -> Decoder;
716}
717
718pub trait ModuleCommon {
720 type ClientConfig: ClientConfig;
721 type Input: Input;
722 type Output: Output;
723 type OutputOutcome: OutputOutcome;
724 type ConsensusItem: ModuleConsensusItem;
725 type InputError: InputError;
726 type OutputError: OutputError;
727
728 fn decoder_builder() -> DecoderBuilder {
729 let mut decoder_builder = Decoder::builder();
730 decoder_builder.with_decodable_type::<Self::ClientConfig>();
731 decoder_builder.with_decodable_type::<Self::Input>();
732 decoder_builder.with_decodable_type::<Self::Output>();
733 decoder_builder.with_decodable_type::<Self::OutputOutcome>();
734 decoder_builder.with_decodable_type::<Self::ConsensusItem>();
735 decoder_builder.with_decodable_type::<Self::InputError>();
736 decoder_builder.with_decodable_type::<Self::OutputError>();
737
738 decoder_builder
739 }
740
741 fn decoder() -> Decoder {
742 Self::decoder_builder().build()
743 }
744}
745
746#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
751pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
752 #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
753 #[serde(skip)] PhantomData<T>,
754);
755
756#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
758pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
759 #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
760 #[serde(skip)] PhantomData<T>,
761);
762
763impl<T> fmt::Debug for SerdeModuleEncoding<T>
764where
765 T: Encodable + Decodable,
766{
767 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
768 f.write_str("SerdeModuleEncoding(")?;
769 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
770 f.write_str(")")?;
771 Ok(())
772 }
773}
774
775impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
776 fn from(value: &T) -> Self {
777 let mut bytes = vec![];
778 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
779 .expect("Writing to buffer can never fail");
780 Self(bytes, PhantomData)
781 }
782}
783
784impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
785 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
786 Decodable::consensus_decode_whole(&self.0, modules)
787 }
788
789 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
798 let mut reader = std::io::Cursor::new(&self.0);
799 let module_instance = ModuleInstanceId::consensus_decode_partial(
800 &mut reader,
801 &ModuleDecoderRegistry::default(),
802 )?;
803
804 let total_len =
805 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
806
807 decoder.decode_complete(
810 &mut reader,
811 total_len,
812 module_instance,
813 &ModuleRegistry::default(),
814 )
815 }
816}
817
818impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
819where
820 T: Encodable + Decodable,
821{
822 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
823 f.write_str("SerdeModuleEncoding2(")?;
824 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
825 f.write_str(")")?;
826 Ok(())
827 }
828}
829
830impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
831 fn from(value: &T) -> Self {
832 let mut bytes = vec![];
833 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
834 .expect("Writing to buffer can never fail");
835 Self(bytes, PhantomData)
836 }
837}
838
839impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
840 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
841 Decodable::consensus_decode_whole(&self.0, modules)
842 }
843
844 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
853 let mut reader = std::io::Cursor::new(&self.0);
854 let module_instance = ModuleInstanceId::consensus_decode_partial(
855 &mut reader,
856 &ModuleDecoderRegistry::default(),
857 )?;
858
859 let total_len =
860 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
861
862 decoder.decode_complete(
865 &mut reader,
866 total_len,
867 module_instance,
868 &ModuleRegistry::default(),
869 )
870 }
871}