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 futures::Future;
33use jsonrpsee_core::JsonValue;
34use registry::ModuleRegistry;
35use serde::{Deserialize, Serialize};
36use tracing::Instrument;
37
38mod version;
40pub use self::version::*;
41use crate::core::{
42 ClientConfig, Decoder, DecoderBuilder, Input, InputError, ModuleConsensusItem,
43 ModuleInstanceId, ModuleKind, Output, OutputError, OutputOutcome,
44};
45use crate::db::{
46 Database, DatabaseError, DatabaseKey, DatabaseKeyWithNotify, DatabaseRecord,
47 DatabaseTransaction,
48};
49use crate::encoding::{Decodable, DecodeError, Encodable};
50use crate::fmt_utils::AbbreviateHexBytes;
51use crate::task::MaybeSend;
52use crate::util::FmtCompact;
53use crate::{Amount, apply, async_trait_maybe_send, maybe_add_send, maybe_add_send_sync};
54
55#[derive(Debug, PartialEq, Eq)]
56pub struct InputMeta {
57 pub amount: TransactionItemAmounts,
58 pub pub_key: secp256k1::PublicKey,
59}
60
61#[derive(
63 Debug,
64 Clone,
65 Copy,
66 Eq,
67 PartialEq,
68 Hash,
69 PartialOrd,
70 Ord,
71 Deserialize,
72 Serialize,
73 Encodable,
74 Decodable,
75 Default,
76)]
77pub struct AmountUnit(u64);
78
79#[cfg(feature = "uniffi")]
80uniffi::custom_newtype!(AmountUnit, u64);
81
82impl AmountUnit {
83 pub const BITCOIN: Self = Self(0);
87
88 pub fn is_bitcoin(self) -> bool {
89 self == Self::BITCOIN
90 }
91
92 pub fn new_custom(unit: u64) -> Self {
93 Self(unit)
94 }
95
96 pub const fn bitcoin() -> Self {
97 Self::BITCOIN
98 }
99}
100
101impl fmt::Display for AmountUnit {
102 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
103 if self.is_bitcoin() {
104 f.write_str("bitcoin")
105 } else {
106 write!(f, "{}", self.0)
107 }
108 }
109}
110
111#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
112pub struct AmountWithUnit {
113 amounts: Amount,
114 unit: AmountUnit,
115}
116
117#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
124pub struct Amounts(BTreeMap<AmountUnit, Amount>);
125
126impl ops::Deref for Amounts {
129 type Target = BTreeMap<AmountUnit, Amount>;
130
131 fn deref(&self) -> &Self::Target {
132 &self.0
133 }
134}
135
136impl Amounts {
137 pub const ZERO: Self = Self(BTreeMap::new());
138
139 pub fn new_bitcoin(amount: Amount) -> Self {
140 if amount == Amount::ZERO {
141 Self(BTreeMap::from([]))
142 } else {
143 Self(BTreeMap::from([(AmountUnit::BITCOIN, amount)]))
144 }
145 }
146
147 pub fn new_bitcoin_msats(msats: u64) -> Self {
148 Self::new_bitcoin(Amount::from_msats(msats))
149 }
150
151 pub fn new_custom(unit: AmountUnit, amount: Amount) -> Self {
152 if amount == Amount::ZERO {
153 Self(BTreeMap::from([]))
154 } else {
155 Self(BTreeMap::from([(unit, amount)]))
156 }
157 }
158
159 pub fn checked_add(mut self, rhs: &Self) -> Option<Self> {
160 self.checked_add_mut(rhs)?;
161
162 Some(self)
163 }
164
165 pub fn checked_add_mut(&mut self, rhs: &Self) -> Option<&mut Self> {
166 for (unit, amount) in &rhs.0 {
167 debug_assert!(
168 *amount != Amount::ZERO,
169 "`Amounts` must not add (/remove) zero-amount entries"
170 );
171 let prev = self.0.entry(*unit).or_default();
172
173 *prev = prev.checked_add(*amount)?;
174 }
175
176 Some(self)
177 }
178
179 pub fn checked_add_bitcoin(self, amount: Amount) -> Option<Self> {
180 self.checked_add_unit(amount, AmountUnit::BITCOIN)
181 }
182
183 pub fn checked_add_unit(mut self, amount: Amount, unit: AmountUnit) -> Option<Self> {
184 if amount == Amount::ZERO {
185 return Some(self);
186 }
187
188 let prev = self.0.entry(unit).or_default();
189
190 *prev = prev.checked_add(amount)?;
191
192 Some(self)
193 }
194
195 pub fn checked_sub(&self, other: &Self) -> Option<Self> {
196 let mut result = self.clone();
197
198 for (unit, amount) in &other.0 {
199 if *amount == Amount::ZERO {
200 continue;
201 }
202
203 let prev = result.0.entry(*unit).or_default();
204
205 *prev = prev.checked_sub(*amount)?;
206
207 if *prev == Amount::ZERO {
208 result.0.remove(unit);
209 }
210 }
211
212 Some(result)
213 }
214
215 pub fn remove(&mut self, unit: &AmountUnit) -> Option<Amount> {
216 self.0.remove(unit)
217 }
218
219 pub fn get_bitcoin(&self) -> Amount {
220 self.get(&AmountUnit::BITCOIN).copied().unwrap_or_default()
221 }
222
223 pub fn expect_only_bitcoin(&self) -> Amount {
224 #[allow(clippy::option_if_let_else)] match self.get(&AmountUnit::BITCOIN) {
226 Some(amount) => {
227 assert!(
228 self.len() == 1,
229 "Amounts expected to contain only bitcoin and no other currencies"
230 );
231 *amount
232 }
233 None => Amount::ZERO,
234 }
235 }
236
237 pub fn iter_units(&self) -> impl Iterator<Item = AmountUnit> {
238 self.0.keys().copied()
239 }
240
241 pub fn units(&self) -> BTreeSet<AmountUnit> {
242 self.0.keys().copied().collect()
243 }
244}
245
246impl IntoIterator for Amounts {
247 type Item = (AmountUnit, Amount);
248
249 type IntoIter = <BTreeMap<AmountUnit, Amount> as IntoIterator>::IntoIter;
250
251 fn into_iter(self) -> Self::IntoIter {
252 self.0.into_iter()
253 }
254}
255
256#[derive(Debug, Clone, Eq, PartialEq, Hash)]
262pub struct TransactionItemAmounts {
263 pub amounts: Amounts,
264 pub fees: Amounts,
265}
266
267impl TransactionItemAmounts {
268 pub fn checked_add(self, rhs: &Self) -> Option<Self> {
269 Some(Self {
270 amounts: self.amounts.checked_add(&rhs.amounts)?,
271 fees: self.fees.checked_add(&rhs.fees)?,
272 })
273 }
274}
275
276impl TransactionItemAmounts {
277 pub const ZERO: Self = Self {
278 amounts: Amounts::ZERO,
279 fees: Amounts::ZERO,
280 };
281}
282
283#[derive(Debug, Serialize, Deserialize, Clone)]
285pub struct ApiRequest<T> {
286 pub auth: Option<ApiAuth>,
288 pub params: T,
290}
291
292pub type ApiRequestErased = ApiRequest<JsonValue>;
293
294impl Default for ApiRequestErased {
295 fn default() -> Self {
296 Self {
297 auth: None,
298 params: JsonValue::Null,
299 }
300 }
301}
302
303impl ApiRequestErased {
304 pub fn new<T: Serialize>(params: T) -> Self {
305 Self {
306 auth: None,
307 params: serde_json::to_value(params)
308 .expect("parameter serialization error - this should not happen"),
309 }
310 }
311
312 pub fn to_json(&self) -> JsonValue {
313 serde_json::to_value(self).expect("parameter serialization error - this should not happen")
314 }
315
316 pub fn with_auth(self, auth: ApiAuth) -> Self {
317 Self {
318 auth: Some(auth),
319 params: self.params,
320 }
321 }
322
323 pub fn to_typed<T: serde::de::DeserializeOwned>(
324 self,
325 ) -> Result<ApiRequest<T>, serde_json::Error> {
326 Ok(ApiRequest {
327 auth: self.auth,
328 params: serde_json::from_value::<T>(self.params)?,
329 })
330 }
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub enum ApiMethod {
335 Core(String),
336 Module(ModuleInstanceId, String),
337}
338
339impl fmt::Display for ApiMethod {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 match self {
342 Self::Core(s) => f.write_str(s),
343 Self::Module(module_id, s) => f.write_fmt(format_args!("{module_id}-{s}")),
344 }
345 }
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct IrohApiRequest {
350 pub method: ApiMethod,
351 pub request: ApiRequestErased,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct IrohGatewayRequest {
356 pub route: String,
358
359 pub params: Option<serde_json::Value>,
361
362 pub password: Option<String>,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct IrohGatewayResponse {
368 pub status: u16,
369 pub body: serde_json::Value,
370}
371
372pub const FEDIMINT_API_ALPN: &[u8] = b"FEDIMINT_API_ALPN";
373pub const FEDIMINT_GATEWAY_ALPN: &[u8] = b"FEDIMINT_GATEWAY_ALPN";
374
375#[derive(Clone, Serialize, Deserialize)]
380pub struct ApiAuth(String);
381
382impl ApiAuth {
383 pub fn new(s: String) -> Self {
384 Self(s)
385 }
386
387 pub fn as_str(&self) -> &str {
388 &self.0
389 }
390
391 pub fn verify(&self, password: &str) -> bool {
392 use subtle::ConstantTimeEq as _;
393 bool::from(self.0.as_bytes().ct_eq(password.as_bytes()))
394 }
395}
396
397impl Debug for ApiAuth {
398 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
399 write!(f, "ApiAuth(****)")
400 }
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct ApiError {
405 pub code: i32,
406 pub message: String,
407}
408
409impl Error for ApiError {}
410
411impl fmt::Display for ApiError {
412 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
413 f.write_fmt(format_args!("{} {}", self.code, self.message))
414 }
415}
416
417pub type ApiResult<T> = Result<T, ApiError>;
418
419impl ApiError {
420 pub fn new(code: i32, message: String) -> Self {
421 Self { code, message }
422 }
423
424 pub fn not_found(message: String) -> Self {
425 Self::new(404, message)
426 }
427
428 pub fn bad_request(message: String) -> Self {
429 Self::new(400, message)
430 }
431
432 pub fn unauthorized() -> Self {
433 Self::new(401, "Invalid authorization".to_string())
434 }
435
436 pub fn server_error(message: String) -> Self {
437 Self::new(500, message)
438 }
439}
440
441impl From<DatabaseError> for ApiError {
442 fn from(err: DatabaseError) -> Self {
443 Self {
444 code: 500,
445 message: format!("API server error when writing to database: {err}"),
446 }
447 }
448}
449
450pub struct ApiEndpointContext {
452 db: Database,
453 has_auth: bool,
454}
455
456impl ApiEndpointContext {
457 pub fn new(db: Database, has_auth: bool) -> Self {
459 Self { db, has_auth }
460 }
461
462 pub fn has_auth(&self) -> bool {
465 self.has_auth
466 }
467
468 pub fn db(&self) -> Database {
469 self.db.clone()
470 }
471
472 pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
474 where
475 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
476 {
477 let db = self.db.clone();
478 async move { db.wait_key_exists(&key).await }
481 }
482
483 pub fn wait_value_matches<K>(
485 &self,
486 key: K,
487 matcher: impl Fn(&K::Value) -> bool + Copy,
488 ) -> impl Future<Output = K::Value>
489 where
490 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
491 {
492 let db = self.db.clone();
493 async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
494 }
495}
496
497#[apply(async_trait_maybe_send!)]
498pub trait TypedApiEndpoint {
499 type State: Sync;
500
501 const PATH: &'static str;
503 const VERSION: ApiVersion;
504 type Param: serde::de::DeserializeOwned + Send;
505 type Response: serde::Serialize;
506
507 async fn handle<'state, 'context>(
508 state: &'state Self::State,
509 context: &'context mut ApiEndpointContext,
510 request: Self::Param,
511 ) -> Result<Self::Response, ApiError>;
512}
513
514pub use serde_json;
515
516#[macro_export]
534macro_rules! __public_api_endpoint {
535 (
536 $path:expr_2021,
537 $version_introduced:expr_2021,
539 async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
540 ) => {{
541 struct Endpoint;
542
543 #[$crate::apply($crate::async_trait_maybe_send!)]
544 impl $crate::module::TypedApiEndpoint for Endpoint {
545 #[allow(deprecated)]
546 const PATH: &'static str = $path;
547 const VERSION: $crate::module::ApiVersion = $version_introduced;
548 type State = $state_ty;
549 type Param = $param_ty;
550 type Response = $resp_ty;
551
552 async fn handle<'state, 'context>(
553 $state: &'state Self::State,
554 $context: &'context mut $crate::module::ApiEndpointContext,
555 $param: Self::Param,
556 ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
557 $body
558 }
559 }
560
561 $crate::module::ApiEndpoint::from_typed::<Endpoint>()
562 }};
563}
564
565pub use __public_api_endpoint as public_api_endpoint;
566
567#[macro_export]
575macro_rules! __admin_api_endpoint {
576 (
577 $path:expr_2021,
578 $version_introduced:expr_2021,
579 async |$state:ident: &$state_ty:ty, $context:ident, $auth:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
580 ) => {{
581 $crate::module::public_api_endpoint! {
582 $path,
583 $version_introduced,
584 async |$state: &$state_ty, $context, $param: $param_ty| -> $resp_ty {
585 #[allow(unused_variables)]
588 let _ = |$state: (), $context: (), $auth: (), $param: ()| {};
589 let $auth = $crate::net::auth::check_auth($context)?;
590 $body
591 }
592 }
593 }};
594 (
595 $path:expr_2021,
596 $version_introduced:expr_2021,
597 async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
598 ) => {{
599 $crate::module::public_api_endpoint! {
600 $path,
601 $version_introduced,
602 async |$state: &$state_ty, $context, $param: $param_ty| -> $resp_ty {
603 $crate::net::auth::check_auth($context)?;
604 $body
605 }
606 }
607 }};
608}
609
610pub use __admin_api_endpoint as admin_api_endpoint;
611
612use self::registry::ModuleDecoderRegistry;
613
614type HandlerFnReturn<'a> =
615 Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
616type HandlerFn<M> = Box<
617 maybe_add_send_sync!(
618 dyn for<'a> Fn(&'a M, ApiEndpointContext, ApiRequestErased) -> HandlerFnReturn<'a>
619 ),
620>;
621
622pub struct ApiEndpoint<M> {
624 pub path: &'static str,
629 pub handler: HandlerFn<M>,
633 pub version: ApiVersion,
635}
636
637static REQ_ID: AtomicU64 = AtomicU64::new(0);
639
640impl ApiEndpoint<()> {
642 pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
643 where
644 <E as TypedApiEndpoint>::Response: MaybeSend,
645 E::Param: Debug,
646 E::Response: Debug,
647 {
648 async fn handle_request<'state, 'context, E>(
649 state: &'state E::State,
650 context: &'context mut ApiEndpointContext,
651 request: ApiRequest<E::Param>,
652 ) -> Result<E::Response, ApiError>
653 where
654 E: TypedApiEndpoint,
655 E::Param: Debug,
656 E::Response: Debug,
657 {
658 tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
659 let result = E::handle(state, context, request.params).await;
660 match &result {
661 Err(err) => {
662 tracing::warn!(target: LOG_NET_API, path = E::PATH, err = %err.fmt_compact(), "api request error");
663 }
664 _ => {
665 tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
666 }
667 }
668 result
669 }
670
671 ApiEndpoint {
672 path: E::PATH,
673 version: E::VERSION,
674 handler: Box::new(|m, mut context, request| {
675 Box::pin(async move {
676 let request = request
677 .to_typed()
678 .map_err(|e| ApiError::bad_request(e.to_string()))?;
679
680 let span = tracing::info_span!(
681 target: LOG_NET_API,
682 "api_req",
683 id = REQ_ID.fetch_add(1, Ordering::SeqCst),
684 method = E::PATH,
685 );
686 let ret = handle_request::<E>(m, &mut context, request)
687 .instrument(span)
688 .await?;
689
690 Ok(serde_json::to_value(ret).expect("encoding error"))
691 })
692 }),
693 }
694 }
695}
696
697#[apply(async_trait_maybe_send!)]
704pub trait IDynCommonModuleInit: Debug {
705 fn decoder(&self) -> Decoder;
706
707 fn module_kind(&self) -> ModuleKind;
708
709 fn to_dyn_common(&self) -> DynCommonModuleInit;
710
711 async fn dump_database(
712 &self,
713 dbtx: &mut DatabaseTransaction<'_>,
714 prefix_names: Vec<String>,
715 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
716}
717
718pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
720 type Common: CommonModuleInit;
721
722 fn dump_database(
723 &self,
724 dbtx: &mut DatabaseTransaction<'_>,
725 prefix_names: Vec<String>,
726 ) -> maybe_add_send!(
727 impl Future<
728 Output = Box<
729 dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
730 >,
731 >
732 );
733}
734
735#[apply(async_trait_maybe_send!)]
736impl<T> IDynCommonModuleInit for T
737where
738 T: ModuleInit,
739{
740 fn decoder(&self) -> Decoder {
741 T::Common::decoder()
742 }
743
744 fn module_kind(&self) -> ModuleKind {
745 T::Common::KIND
746 }
747
748 fn to_dyn_common(&self) -> DynCommonModuleInit {
749 DynCommonModuleInit::from_inner(Arc::new(self.clone()))
750 }
751
752 async fn dump_database(
753 &self,
754 dbtx: &mut DatabaseTransaction<'_>,
755 prefix_names: Vec<String>,
756 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
757 <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
758 }
759}
760
761dyn_newtype_define!(
762 #[derive(Clone)]
763 pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
764);
765
766impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
767 fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
768 self.inner.as_ref()
769 }
770}
771
772impl DynCommonModuleInit {
773 pub fn from_inner(
774 inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
775 ) -> Self {
776 Self { inner }
777 }
778}
779
780#[apply(async_trait_maybe_send!)]
782pub trait CommonModuleInit: Debug + Sized {
783 const CONSENSUS_VERSION: ModuleConsensusVersion;
784 const KIND: ModuleKind;
785
786 type ClientConfig: ClientConfig;
787
788 fn decoder() -> Decoder;
789}
790
791pub trait ModuleCommon {
793 type ClientConfig: ClientConfig;
794 type Input: Input;
795 type Output: Output;
796 type OutputOutcome: OutputOutcome;
797 type ConsensusItem: ModuleConsensusItem;
798 type InputError: InputError;
799 type OutputError: OutputError;
800
801 fn decoder_builder() -> DecoderBuilder {
802 let mut decoder_builder = Decoder::builder();
803 decoder_builder.with_decodable_type::<Self::ClientConfig>();
804 decoder_builder.with_decodable_type::<Self::Input>();
805 decoder_builder.with_decodable_type::<Self::Output>();
806 decoder_builder.with_decodable_type::<Self::OutputOutcome>();
807 decoder_builder.with_decodable_type::<Self::ConsensusItem>();
808 decoder_builder.with_decodable_type::<Self::InputError>();
809 decoder_builder.with_decodable_type::<Self::OutputError>();
810
811 decoder_builder
812 }
813
814 fn decoder() -> Decoder {
815 Self::decoder_builder().build()
816 }
817}
818
819#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
824pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
825 #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
826 #[serde(skip)] PhantomData<T>,
827);
828
829#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
831pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
832 #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
833 #[serde(skip)] PhantomData<T>,
834);
835
836impl<T> fmt::Debug for SerdeModuleEncoding<T>
837where
838 T: Encodable + Decodable,
839{
840 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
841 f.write_str("SerdeModuleEncoding(")?;
842 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
843 f.write_str(")")?;
844 Ok(())
845 }
846}
847
848impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
849 fn from(value: &T) -> Self {
850 let mut bytes = vec![];
851 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
852 .expect("Writing to buffer can never fail");
853 Self(bytes, PhantomData)
854 }
855}
856
857impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
858 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
859 Decodable::consensus_decode_whole(&self.0, modules)
860 }
861
862 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
871 let mut reader = std::io::Cursor::new(&self.0);
872 let module_instance = ModuleInstanceId::consensus_decode_partial(
873 &mut reader,
874 &ModuleDecoderRegistry::default(),
875 )?;
876
877 let total_len =
878 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
879
880 decoder.decode_complete(
883 &mut reader,
884 total_len,
885 module_instance,
886 &ModuleRegistry::default(),
887 )
888 }
889}
890
891impl<T: Encodable + Decodable> Encodable for SerdeModuleEncoding<T> {
892 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
893 self.0.consensus_encode(writer)
894 }
895}
896
897impl<T: Encodable + Decodable> Decodable for SerdeModuleEncoding<T> {
898 fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
899 reader: &mut R,
900 modules: &ModuleDecoderRegistry,
901 ) -> Result<Self, DecodeError> {
902 Ok(Self(
903 Vec::<u8>::consensus_decode_partial_from_finite_reader(reader, modules)?,
904 PhantomData,
905 ))
906 }
907}
908
909impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
910where
911 T: Encodable + Decodable,
912{
913 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
914 f.write_str("SerdeModuleEncoding2(")?;
915 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
916 f.write_str(")")?;
917 Ok(())
918 }
919}
920
921impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
922 fn from(value: &T) -> Self {
923 let mut bytes = vec![];
924 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
925 .expect("Writing to buffer can never fail");
926 Self(bytes, PhantomData)
927 }
928}
929
930impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
931 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
932 Decodable::consensus_decode_whole(&self.0, modules)
933 }
934
935 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
944 let mut reader = std::io::Cursor::new(&self.0);
945 let module_instance = ModuleInstanceId::consensus_decode_partial(
946 &mut reader,
947 &ModuleDecoderRegistry::default(),
948 )?;
949
950 let total_len =
951 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
952
953 decoder.decode_complete(
956 &mut reader,
957 total_len,
958 module_instance,
959 &ModuleRegistry::default(),
960 )
961 }
962}