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
322#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct IrohGatewayRequest {
324 pub route: String,
326
327 pub params: Option<serde_json::Value>,
329
330 pub password: Option<String>,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct IrohGatewayResponse {
336 pub status: u16,
337 pub body: serde_json::Value,
338}
339
340pub const FEDIMINT_API_ALPN: &[u8] = b"FEDIMINT_API_ALPN";
341pub const FEDIMINT_GATEWAY_ALPN: &[u8] = b"FEDIMINT_GATEWAY_ALPN";
342
343#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
347pub struct ApiAuth(pub String);
348
349impl Debug for ApiAuth {
350 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
351 write!(f, "ApiAuth(****)")
352 }
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct ApiError {
357 pub code: i32,
358 pub message: String,
359}
360
361impl Error for ApiError {}
362
363impl fmt::Display for ApiError {
364 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
365 f.write_fmt(format_args!("{} {}", self.code, self.message))
366 }
367}
368
369pub type ApiResult<T> = Result<T, ApiError>;
370
371impl ApiError {
372 pub fn new(code: i32, message: String) -> Self {
373 Self { code, message }
374 }
375
376 pub fn not_found(message: String) -> Self {
377 Self::new(404, message)
378 }
379
380 pub fn bad_request(message: String) -> Self {
381 Self::new(400, message)
382 }
383
384 pub fn unauthorized() -> Self {
385 Self::new(401, "Invalid authorization".to_string())
386 }
387
388 pub fn server_error(message: String) -> Self {
389 Self::new(500, message)
390 }
391}
392
393pub struct ApiEndpointContext<'dbtx> {
395 db: Database,
396 dbtx: DatabaseTransaction<'dbtx, Committable>,
397 has_auth: bool,
398 request_auth: Option<ApiAuth>,
399}
400
401impl<'a> ApiEndpointContext<'a> {
402 pub fn new(
404 db: Database,
405 dbtx: DatabaseTransaction<'a, Committable>,
406 has_auth: bool,
407 request_auth: Option<ApiAuth>,
408 ) -> Self {
409 Self {
410 db,
411 dbtx,
412 has_auth,
413 request_auth,
414 }
415 }
416
417 pub fn dbtx<'s, 'mtx>(&'s mut self) -> DatabaseTransaction<'mtx, NonCommittable>
419 where
420 'a: 'mtx,
421 's: 'mtx,
422 {
423 self.dbtx.to_ref_nc()
425 }
426
427 pub fn request_auth(&self) -> Option<ApiAuth> {
430 self.request_auth.clone()
431 }
432
433 pub fn has_auth(&self) -> bool {
436 self.has_auth
437 }
438
439 pub fn db(&self) -> Database {
440 self.db.clone()
441 }
442
443 pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
445 where
446 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
447 {
448 let db = self.db.clone();
449 async move { db.wait_key_exists(&key).await }
452 }
453
454 pub fn wait_value_matches<K>(
456 &self,
457 key: K,
458 matcher: impl Fn(&K::Value) -> bool + Copy,
459 ) -> impl Future<Output = K::Value>
460 where
461 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
462 {
463 let db = self.db.clone();
464 async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
465 }
466
467 pub async fn commit_tx_result(self, path: &'static str) -> Result<(), ApiError> {
469 self.dbtx.commit_tx_result().await.map_err(|err| {
470 tracing::warn!(
471 target: fedimint_logging::LOG_NET_API,
472 err = %err.fmt_compact_anyhow(),
473 %path,
474 "API server error when writing to database",
475 );
476 ApiError {
477 code: 500,
478 message: "API server error when writing to database".to_string(),
479 }
480 })
481 }
482}
483
484#[apply(async_trait_maybe_send!)]
485pub trait TypedApiEndpoint {
486 type State: Sync;
487
488 const PATH: &'static str;
490
491 type Param: serde::de::DeserializeOwned + Send;
492 type Response: serde::Serialize;
493
494 async fn handle<'state, 'context, 'dbtx>(
495 state: &'state Self::State,
496 context: &'context mut ApiEndpointContext<'dbtx>,
497 request: Self::Param,
498 ) -> Result<Self::Response, ApiError>
499 where
500 'dbtx: 'context;
501}
502
503pub use serde_json;
504
505#[macro_export]
521macro_rules! __api_endpoint {
522 (
523 $path:expr_2021,
524 $version_introduced:expr_2021,
527 async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
528 ) => {{
529 struct Endpoint;
530
531 #[$crate::apply($crate::async_trait_maybe_send!)]
532 impl $crate::module::TypedApiEndpoint for Endpoint {
533 #[allow(deprecated)]
534 const PATH: &'static str = $path;
535 type State = $state_ty;
536 type Param = $param_ty;
537 type Response = $resp_ty;
538
539 async fn handle<'state, 'context, 'dbtx>(
540 $state: &'state Self::State,
541 $context: &'context mut $crate::module::ApiEndpointContext<'dbtx>,
542 $param: Self::Param,
543 ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
544 {
545 const __API_VERSION: $crate::module::ApiVersion = $version_introduced;
547 }
548 $body
549 }
550 }
551
552 $crate::module::ApiEndpoint::from_typed::<Endpoint>()
553 }};
554}
555
556pub use __api_endpoint as api_endpoint;
557
558use self::registry::ModuleDecoderRegistry;
559
560type HandlerFnReturn<'a> =
561 Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
562type HandlerFn<M> = Box<
563 maybe_add_send_sync!(
564 dyn for<'a> Fn(&'a M, ApiEndpointContext<'a>, ApiRequestErased) -> HandlerFnReturn<'a>
565 ),
566>;
567
568pub struct ApiEndpoint<M> {
570 pub path: &'static str,
575 pub handler: HandlerFn<M>,
579}
580
581static REQ_ID: AtomicU64 = AtomicU64::new(0);
583
584impl ApiEndpoint<()> {
586 pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
587 where
588 <E as TypedApiEndpoint>::Response: MaybeSend,
589 E::Param: Debug,
590 E::Response: Debug,
591 {
592 async fn handle_request<'state, 'context, 'dbtx, E>(
593 state: &'state E::State,
594 context: &'context mut ApiEndpointContext<'dbtx>,
595 request: ApiRequest<E::Param>,
596 ) -> Result<E::Response, ApiError>
597 where
598 'dbtx: 'context,
599 E: TypedApiEndpoint,
600 E::Param: Debug,
601 E::Response: Debug,
602 {
603 tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
604 let result = E::handle(state, context, request.params).await;
605 match &result {
606 Err(err) => {
607 tracing::warn!(target: LOG_NET_API, path = E::PATH, err = %err.fmt_compact(), "api request error");
608 }
609 _ => {
610 tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
611 }
612 }
613 result
614 }
615
616 ApiEndpoint {
617 path: E::PATH,
618 handler: Box::new(|m, mut context, request| {
619 Box::pin(async {
620 let request = request
621 .to_typed()
622 .map_err(|e| ApiError::bad_request(e.to_string()))?;
623
624 let span = tracing::info_span!(
625 target: LOG_NET_API,
626 "api_req",
627 id = REQ_ID.fetch_add(1, Ordering::SeqCst),
628 method = E::PATH,
629 );
630 let ret = handle_request::<E>(m, &mut context, request)
631 .instrument(span)
632 .await?;
633
634 context.commit_tx_result(E::PATH).await?;
635
636 Ok(serde_json::to_value(ret).expect("encoding error"))
637 })
638 }),
639 }
640 }
641}
642
643#[apply(async_trait_maybe_send!)]
650pub trait IDynCommonModuleInit: Debug {
651 fn decoder(&self) -> Decoder;
652
653 fn module_kind(&self) -> ModuleKind;
654
655 fn to_dyn_common(&self) -> DynCommonModuleInit;
656
657 async fn dump_database(
658 &self,
659 dbtx: &mut DatabaseTransaction<'_>,
660 prefix_names: Vec<String>,
661 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
662}
663
664pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
666 type Common: CommonModuleInit;
667
668 fn dump_database(
669 &self,
670 dbtx: &mut DatabaseTransaction<'_>,
671 prefix_names: Vec<String>,
672 ) -> maybe_add_send!(
673 impl Future<
674 Output = Box<
675 dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
676 >,
677 >
678 );
679}
680
681#[apply(async_trait_maybe_send!)]
682impl<T> IDynCommonModuleInit for T
683where
684 T: ModuleInit,
685{
686 fn decoder(&self) -> Decoder {
687 T::Common::decoder()
688 }
689
690 fn module_kind(&self) -> ModuleKind {
691 T::Common::KIND
692 }
693
694 fn to_dyn_common(&self) -> DynCommonModuleInit {
695 DynCommonModuleInit::from_inner(Arc::new(self.clone()))
696 }
697
698 async fn dump_database(
699 &self,
700 dbtx: &mut DatabaseTransaction<'_>,
701 prefix_names: Vec<String>,
702 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
703 <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
704 }
705}
706
707dyn_newtype_define!(
708 #[derive(Clone)]
709 pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
710);
711
712impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
713 fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
714 self.inner.as_ref()
715 }
716}
717
718impl DynCommonModuleInit {
719 pub fn from_inner(
720 inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
721 ) -> Self {
722 Self { inner }
723 }
724}
725
726#[apply(async_trait_maybe_send!)]
728pub trait CommonModuleInit: Debug + Sized {
729 const CONSENSUS_VERSION: ModuleConsensusVersion;
730 const KIND: ModuleKind;
731
732 type ClientConfig: ClientConfig;
733
734 fn decoder() -> Decoder;
735}
736
737pub trait ModuleCommon {
739 type ClientConfig: ClientConfig;
740 type Input: Input;
741 type Output: Output;
742 type OutputOutcome: OutputOutcome;
743 type ConsensusItem: ModuleConsensusItem;
744 type InputError: InputError;
745 type OutputError: OutputError;
746
747 fn decoder_builder() -> DecoderBuilder {
748 let mut decoder_builder = Decoder::builder();
749 decoder_builder.with_decodable_type::<Self::ClientConfig>();
750 decoder_builder.with_decodable_type::<Self::Input>();
751 decoder_builder.with_decodable_type::<Self::Output>();
752 decoder_builder.with_decodable_type::<Self::OutputOutcome>();
753 decoder_builder.with_decodable_type::<Self::ConsensusItem>();
754 decoder_builder.with_decodable_type::<Self::InputError>();
755 decoder_builder.with_decodable_type::<Self::OutputError>();
756
757 decoder_builder
758 }
759
760 fn decoder() -> Decoder {
761 Self::decoder_builder().build()
762 }
763}
764
765#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
770pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
771 #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
772 #[serde(skip)] PhantomData<T>,
773);
774
775#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
777pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
778 #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
779 #[serde(skip)] PhantomData<T>,
780);
781
782impl<T> fmt::Debug for SerdeModuleEncoding<T>
783where
784 T: Encodable + Decodable,
785{
786 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
787 f.write_str("SerdeModuleEncoding(")?;
788 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
789 f.write_str(")")?;
790 Ok(())
791 }
792}
793
794impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
795 fn from(value: &T) -> Self {
796 let mut bytes = vec![];
797 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
798 .expect("Writing to buffer can never fail");
799 Self(bytes, PhantomData)
800 }
801}
802
803impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
804 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
805 Decodable::consensus_decode_whole(&self.0, modules)
806 }
807
808 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
817 let mut reader = std::io::Cursor::new(&self.0);
818 let module_instance = ModuleInstanceId::consensus_decode_partial(
819 &mut reader,
820 &ModuleDecoderRegistry::default(),
821 )?;
822
823 let total_len =
824 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
825
826 decoder.decode_complete(
829 &mut reader,
830 total_len,
831 module_instance,
832 &ModuleRegistry::default(),
833 )
834 }
835}
836
837impl<T: Encodable + Decodable> Encodable for SerdeModuleEncoding<T> {
838 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
839 self.0.consensus_encode(writer)
840 }
841}
842
843impl<T: Encodable + Decodable> Decodable for SerdeModuleEncoding<T> {
844 fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
845 reader: &mut R,
846 modules: &ModuleDecoderRegistry,
847 ) -> Result<Self, DecodeError> {
848 Ok(Self(
849 Vec::<u8>::consensus_decode_partial_from_finite_reader(reader, modules)?,
850 PhantomData,
851 ))
852 }
853}
854
855impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
856where
857 T: Encodable + Decodable,
858{
859 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
860 f.write_str("SerdeModuleEncoding2(")?;
861 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
862 f.write_str(")")?;
863 Ok(())
864 }
865}
866
867impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
868 fn from(value: &T) -> Self {
869 let mut bytes = vec![];
870 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
871 .expect("Writing to buffer can never fail");
872 Self(bytes, PhantomData)
873 }
874}
875
876impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
877 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
878 Decodable::consensus_decode_whole(&self.0, modules)
879 }
880
881 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
890 let mut reader = std::io::Cursor::new(&self.0);
891 let module_instance = ModuleInstanceId::consensus_decode_partial(
892 &mut reader,
893 &ModuleDecoderRegistry::default(),
894 )?;
895
896 let total_len =
897 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
898
899 decoder.decode_complete(
902 &mut reader,
903 total_len,
904 module_instance,
905 &ModuleRegistry::default(),
906 )
907 }
908}