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
79impl AmountUnit {
80 pub const BITCOIN: Self = Self(0);
84
85 pub fn is_bitcoin(self) -> bool {
86 self == Self::BITCOIN
87 }
88
89 pub fn new_custom(unit: u64) -> Self {
90 Self(unit)
91 }
92
93 pub const fn bitcoin() -> Self {
94 Self::BITCOIN
95 }
96}
97
98#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
99pub struct AmountWithUnit {
100 amounts: Amount,
101 unit: AmountUnit,
102}
103
104#[derive(Debug, Clone, Eq, PartialEq, Hash)]
111pub struct Amounts(BTreeMap<AmountUnit, Amount>);
112
113impl ops::Deref for Amounts {
116 type Target = BTreeMap<AmountUnit, Amount>;
117
118 fn deref(&self) -> &Self::Target {
119 &self.0
120 }
121}
122
123impl Amounts {
124 pub const ZERO: Self = Self(BTreeMap::new());
125
126 pub fn new_bitcoin(amount: Amount) -> Self {
127 if amount == Amount::ZERO {
128 Self(BTreeMap::from([]))
129 } else {
130 Self(BTreeMap::from([(AmountUnit::BITCOIN, amount)]))
131 }
132 }
133
134 pub fn new_bitcoin_msats(msats: u64) -> Self {
135 Self::new_bitcoin(Amount::from_msats(msats))
136 }
137
138 pub fn new_custom(unit: AmountUnit, amount: Amount) -> Self {
139 if amount == Amount::ZERO {
140 Self(BTreeMap::from([]))
141 } else {
142 Self(BTreeMap::from([(unit, amount)]))
143 }
144 }
145
146 pub fn checked_add(mut self, rhs: &Self) -> Option<Self> {
147 self.checked_add_mut(rhs);
148
149 Some(self)
150 }
151
152 pub fn checked_add_mut(&mut self, rhs: &Self) -> Option<&mut Self> {
153 for (unit, amount) in &rhs.0 {
154 debug_assert!(
155 *amount != Amount::ZERO,
156 "`Amounts` must not add (/remove) zero-amount entries"
157 );
158 let prev = self.0.entry(*unit).or_default();
159
160 *prev = prev.checked_add(*amount)?;
161 }
162
163 Some(self)
164 }
165
166 pub fn checked_add_bitcoin(self, amount: Amount) -> Option<Self> {
167 self.checked_add_unit(amount, AmountUnit::BITCOIN)
168 }
169
170 pub fn checked_add_unit(mut self, amount: Amount, unit: AmountUnit) -> Option<Self> {
171 if amount == Amount::ZERO {
172 return Some(self);
173 }
174
175 let prev = self.0.entry(unit).or_default();
176
177 *prev = prev.checked_add(amount)?;
178
179 Some(self)
180 }
181
182 pub fn remove(&mut self, unit: &AmountUnit) -> Option<Amount> {
183 self.0.remove(unit)
184 }
185
186 pub fn get_bitcoin(&self) -> Amount {
187 self.get(&AmountUnit::BITCOIN).copied().unwrap_or_default()
188 }
189
190 pub fn expect_only_bitcoin(&self) -> Amount {
191 #[allow(clippy::option_if_let_else)] match self.get(&AmountUnit::BITCOIN) {
193 Some(amount) => {
194 assert!(
195 self.len() == 1,
196 "Amounts expected to contain only bitcoin and no other currencies"
197 );
198 *amount
199 }
200 None => Amount::ZERO,
201 }
202 }
203
204 pub fn iter_units(&self) -> impl Iterator<Item = AmountUnit> {
205 self.0.keys().copied()
206 }
207
208 pub fn units(&self) -> BTreeSet<AmountUnit> {
209 self.0.keys().copied().collect()
210 }
211}
212
213impl IntoIterator for Amounts {
214 type Item = (AmountUnit, Amount);
215
216 type IntoIter = <BTreeMap<AmountUnit, Amount> as IntoIterator>::IntoIter;
217
218 fn into_iter(self) -> Self::IntoIter {
219 self.0.into_iter()
220 }
221}
222
223#[derive(Debug, Clone, Eq, PartialEq, Hash)]
229pub struct TransactionItemAmounts {
230 pub amounts: Amounts,
231 pub fees: Amounts,
232}
233
234impl TransactionItemAmounts {
235 pub fn checked_add(self, rhs: &Self) -> Option<Self> {
236 Some(Self {
237 amounts: self.amounts.checked_add(&rhs.amounts)?,
238 fees: self.fees.checked_add(&rhs.fees)?,
239 })
240 }
241}
242
243impl TransactionItemAmounts {
244 pub const ZERO: Self = Self {
245 amounts: Amounts::ZERO,
246 fees: Amounts::ZERO,
247 };
248}
249
250#[derive(Debug, Serialize, Deserialize, Clone)]
252pub struct ApiRequest<T> {
253 pub auth: Option<ApiAuth>,
255 pub params: T,
257}
258
259pub type ApiRequestErased = ApiRequest<JsonValue>;
260
261impl Default for ApiRequestErased {
262 fn default() -> Self {
263 Self {
264 auth: None,
265 params: JsonValue::Null,
266 }
267 }
268}
269
270impl ApiRequestErased {
271 pub fn new<T: Serialize>(params: T) -> Self {
272 Self {
273 auth: None,
274 params: serde_json::to_value(params)
275 .expect("parameter serialization error - this should not happen"),
276 }
277 }
278
279 pub fn to_json(&self) -> JsonValue {
280 serde_json::to_value(self).expect("parameter serialization error - this should not happen")
281 }
282
283 pub fn with_auth(self, auth: ApiAuth) -> Self {
284 Self {
285 auth: Some(auth),
286 params: self.params,
287 }
288 }
289
290 pub fn to_typed<T: serde::de::DeserializeOwned>(
291 self,
292 ) -> Result<ApiRequest<T>, serde_json::Error> {
293 Ok(ApiRequest {
294 auth: self.auth,
295 params: serde_json::from_value::<T>(self.params)?,
296 })
297 }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub enum ApiMethod {
302 Core(String),
303 Module(ModuleInstanceId, String),
304}
305
306impl fmt::Display for ApiMethod {
307 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308 match self {
309 Self::Core(s) => f.write_str(s),
310 Self::Module(module_id, s) => f.write_fmt(format_args!("{module_id}-{s}")),
311 }
312 }
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct IrohApiRequest {
317 pub method: ApiMethod,
318 pub request: ApiRequestErased,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct IrohGatewayRequest {
323 pub route: String,
325
326 pub params: Option<serde_json::Value>,
328
329 pub password: Option<String>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct IrohGatewayResponse {
335 pub status: u16,
336 pub body: serde_json::Value,
337}
338
339pub const FEDIMINT_API_ALPN: &[u8] = b"FEDIMINT_API_ALPN";
340pub const FEDIMINT_GATEWAY_ALPN: &[u8] = b"FEDIMINT_GATEWAY_ALPN";
341
342#[derive(Clone, Serialize, Deserialize)]
351pub struct ApiAuth(String);
352
353impl ApiAuth {
354 pub fn new(s: String) -> Self {
355 Self(s)
356 }
357
358 pub fn as_str(&self) -> &str {
359 &self.0
360 }
361
362 pub fn verify(&self, password: &str) -> bool {
363 use subtle::ConstantTimeEq as _;
364 bool::from(self.0.as_bytes().ct_eq(password.as_bytes()))
365 }
366}
367
368impl Debug for ApiAuth {
369 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
370 write!(f, "ApiAuth(****)")
371 }
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct ApiError {
376 pub code: i32,
377 pub message: String,
378}
379
380impl Error for ApiError {}
381
382impl fmt::Display for ApiError {
383 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
384 f.write_fmt(format_args!("{} {}", self.code, self.message))
385 }
386}
387
388pub type ApiResult<T> = Result<T, ApiError>;
389
390impl ApiError {
391 pub fn new(code: i32, message: String) -> Self {
392 Self { code, message }
393 }
394
395 pub fn not_found(message: String) -> Self {
396 Self::new(404, message)
397 }
398
399 pub fn bad_request(message: String) -> Self {
400 Self::new(400, message)
401 }
402
403 pub fn unauthorized() -> Self {
404 Self::new(401, "Invalid authorization".to_string())
405 }
406
407 pub fn server_error(message: String) -> Self {
408 Self::new(500, message)
409 }
410}
411
412impl From<DatabaseError> for ApiError {
413 fn from(err: DatabaseError) -> Self {
414 Self {
415 code: 500,
416 message: format!("API server error when writing to database: {err}"),
417 }
418 }
419}
420
421pub struct ApiEndpointContext {
423 db: Database,
424 has_auth: bool,
425 request_auth: Option<ApiAuth>,
426}
427
428impl ApiEndpointContext {
429 pub fn new(db: Database, has_auth: bool, request_auth: Option<ApiAuth>) -> Self {
431 Self {
432 db,
433 has_auth,
434 request_auth,
435 }
436 }
437
438 pub fn request_auth(&self) -> Option<ApiAuth> {
441 self.request_auth.clone()
442 }
443
444 pub fn has_auth(&self) -> bool {
447 self.has_auth
448 }
449
450 pub fn db(&self) -> Database {
451 self.db.clone()
452 }
453
454 pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
456 where
457 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
458 {
459 let db = self.db.clone();
460 async move { db.wait_key_exists(&key).await }
463 }
464
465 pub fn wait_value_matches<K>(
467 &self,
468 key: K,
469 matcher: impl Fn(&K::Value) -> bool + Copy,
470 ) -> impl Future<Output = K::Value>
471 where
472 K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
473 {
474 let db = self.db.clone();
475 async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
476 }
477}
478
479#[apply(async_trait_maybe_send!)]
480pub trait TypedApiEndpoint {
481 type State: Sync;
482
483 const PATH: &'static str;
485
486 type Param: serde::de::DeserializeOwned + Send;
487 type Response: serde::Serialize;
488
489 async fn handle<'state, 'context>(
490 state: &'state Self::State,
491 context: &'context mut ApiEndpointContext,
492 request: Self::Param,
493 ) -> Result<Self::Response, ApiError>;
494}
495
496pub use serde_json;
497
498#[macro_export]
514macro_rules! __api_endpoint {
515 (
516 $path:expr_2021,
517 $version_introduced:expr_2021,
520 async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
521 ) => {{
522 struct Endpoint;
523
524 #[$crate::apply($crate::async_trait_maybe_send!)]
525 impl $crate::module::TypedApiEndpoint for Endpoint {
526 #[allow(deprecated)]
527 const PATH: &'static str = $path;
528 type State = $state_ty;
529 type Param = $param_ty;
530 type Response = $resp_ty;
531
532 async fn handle<'state, 'context>(
533 $state: &'state Self::State,
534 $context: &'context mut $crate::module::ApiEndpointContext,
535 $param: Self::Param,
536 ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
537 {
538 const __API_VERSION: $crate::module::ApiVersion = $version_introduced;
540 }
541 $body
542 }
543 }
544
545 $crate::module::ApiEndpoint::from_typed::<Endpoint>()
546 }};
547}
548
549pub use __api_endpoint as api_endpoint;
550
551use self::registry::ModuleDecoderRegistry;
552
553type HandlerFnReturn<'a> =
554 Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
555type HandlerFn<M> = Box<
556 maybe_add_send_sync!(
557 dyn for<'a> Fn(&'a M, ApiEndpointContext, ApiRequestErased) -> HandlerFnReturn<'a>
558 ),
559>;
560
561pub struct ApiEndpoint<M> {
563 pub path: &'static str,
568 pub handler: HandlerFn<M>,
572}
573
574static REQ_ID: AtomicU64 = AtomicU64::new(0);
576
577impl ApiEndpoint<()> {
579 pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
580 where
581 <E as TypedApiEndpoint>::Response: MaybeSend,
582 E::Param: Debug,
583 E::Response: Debug,
584 {
585 async fn handle_request<'state, 'context, E>(
586 state: &'state E::State,
587 context: &'context mut ApiEndpointContext,
588 request: ApiRequest<E::Param>,
589 ) -> Result<E::Response, ApiError>
590 where
591 E: TypedApiEndpoint,
592 E::Param: Debug,
593 E::Response: Debug,
594 {
595 tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
596 let result = E::handle(state, context, request.params).await;
597 match &result {
598 Err(err) => {
599 tracing::warn!(target: LOG_NET_API, path = E::PATH, err = %err.fmt_compact(), "api request error");
600 }
601 _ => {
602 tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
603 }
604 }
605 result
606 }
607
608 ApiEndpoint {
609 path: E::PATH,
610 handler: Box::new(|m, mut context, request| {
611 Box::pin(async move {
612 let request = request
613 .to_typed()
614 .map_err(|e| ApiError::bad_request(e.to_string()))?;
615
616 let span = tracing::info_span!(
617 target: LOG_NET_API,
618 "api_req",
619 id = REQ_ID.fetch_add(1, Ordering::SeqCst),
620 method = E::PATH,
621 );
622 let ret = handle_request::<E>(m, &mut context, request)
623 .instrument(span)
624 .await?;
625
626 Ok(serde_json::to_value(ret).expect("encoding error"))
627 })
628 }),
629 }
630 }
631}
632
633#[apply(async_trait_maybe_send!)]
640pub trait IDynCommonModuleInit: Debug {
641 fn decoder(&self) -> Decoder;
642
643 fn module_kind(&self) -> ModuleKind;
644
645 fn to_dyn_common(&self) -> DynCommonModuleInit;
646
647 async fn dump_database(
648 &self,
649 dbtx: &mut DatabaseTransaction<'_>,
650 prefix_names: Vec<String>,
651 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
652}
653
654pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
656 type Common: CommonModuleInit;
657
658 fn dump_database(
659 &self,
660 dbtx: &mut DatabaseTransaction<'_>,
661 prefix_names: Vec<String>,
662 ) -> maybe_add_send!(
663 impl Future<
664 Output = Box<
665 dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
666 >,
667 >
668 );
669}
670
671#[apply(async_trait_maybe_send!)]
672impl<T> IDynCommonModuleInit for T
673where
674 T: ModuleInit,
675{
676 fn decoder(&self) -> Decoder {
677 T::Common::decoder()
678 }
679
680 fn module_kind(&self) -> ModuleKind {
681 T::Common::KIND
682 }
683
684 fn to_dyn_common(&self) -> DynCommonModuleInit {
685 DynCommonModuleInit::from_inner(Arc::new(self.clone()))
686 }
687
688 async fn dump_database(
689 &self,
690 dbtx: &mut DatabaseTransaction<'_>,
691 prefix_names: Vec<String>,
692 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
693 <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
694 }
695}
696
697dyn_newtype_define!(
698 #[derive(Clone)]
699 pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
700);
701
702impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
703 fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
704 self.inner.as_ref()
705 }
706}
707
708impl DynCommonModuleInit {
709 pub fn from_inner(
710 inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
711 ) -> Self {
712 Self { inner }
713 }
714}
715
716#[apply(async_trait_maybe_send!)]
718pub trait CommonModuleInit: Debug + Sized {
719 const CONSENSUS_VERSION: ModuleConsensusVersion;
720 const KIND: ModuleKind;
721
722 type ClientConfig: ClientConfig;
723
724 fn decoder() -> Decoder;
725}
726
727pub trait ModuleCommon {
729 type ClientConfig: ClientConfig;
730 type Input: Input;
731 type Output: Output;
732 type OutputOutcome: OutputOutcome;
733 type ConsensusItem: ModuleConsensusItem;
734 type InputError: InputError;
735 type OutputError: OutputError;
736
737 fn decoder_builder() -> DecoderBuilder {
738 let mut decoder_builder = Decoder::builder();
739 decoder_builder.with_decodable_type::<Self::ClientConfig>();
740 decoder_builder.with_decodable_type::<Self::Input>();
741 decoder_builder.with_decodable_type::<Self::Output>();
742 decoder_builder.with_decodable_type::<Self::OutputOutcome>();
743 decoder_builder.with_decodable_type::<Self::ConsensusItem>();
744 decoder_builder.with_decodable_type::<Self::InputError>();
745 decoder_builder.with_decodable_type::<Self::OutputError>();
746
747 decoder_builder
748 }
749
750 fn decoder() -> Decoder {
751 Self::decoder_builder().build()
752 }
753}
754
755#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
760pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
761 #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
762 #[serde(skip)] PhantomData<T>,
763);
764
765#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
767pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
768 #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
769 #[serde(skip)] PhantomData<T>,
770);
771
772impl<T> fmt::Debug for SerdeModuleEncoding<T>
773where
774 T: Encodable + Decodable,
775{
776 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
777 f.write_str("SerdeModuleEncoding(")?;
778 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
779 f.write_str(")")?;
780 Ok(())
781 }
782}
783
784impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
785 fn from(value: &T) -> Self {
786 let mut bytes = vec![];
787 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
788 .expect("Writing to buffer can never fail");
789 Self(bytes, PhantomData)
790 }
791}
792
793impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
794 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
795 Decodable::consensus_decode_whole(&self.0, modules)
796 }
797
798 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
807 let mut reader = std::io::Cursor::new(&self.0);
808 let module_instance = ModuleInstanceId::consensus_decode_partial(
809 &mut reader,
810 &ModuleDecoderRegistry::default(),
811 )?;
812
813 let total_len =
814 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
815
816 decoder.decode_complete(
819 &mut reader,
820 total_len,
821 module_instance,
822 &ModuleRegistry::default(),
823 )
824 }
825}
826
827impl<T: Encodable + Decodable> Encodable for SerdeModuleEncoding<T> {
828 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
829 self.0.consensus_encode(writer)
830 }
831}
832
833impl<T: Encodable + Decodable> Decodable for SerdeModuleEncoding<T> {
834 fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
835 reader: &mut R,
836 modules: &ModuleDecoderRegistry,
837 ) -> Result<Self, DecodeError> {
838 Ok(Self(
839 Vec::<u8>::consensus_decode_partial_from_finite_reader(reader, modules)?,
840 PhantomData,
841 ))
842 }
843}
844
845impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
846where
847 T: Encodable + Decodable,
848{
849 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
850 f.write_str("SerdeModuleEncoding2(")?;
851 fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
852 f.write_str(")")?;
853 Ok(())
854 }
855}
856
857impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
858 fn from(value: &T) -> Self {
859 let mut bytes = vec![];
860 fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
861 .expect("Writing to buffer can never fail");
862 Self(bytes, PhantomData)
863 }
864}
865
866impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
867 pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
868 Decodable::consensus_decode_whole(&self.0, modules)
869 }
870
871 pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
880 let mut reader = std::io::Cursor::new(&self.0);
881 let module_instance = ModuleInstanceId::consensus_decode_partial(
882 &mut reader,
883 &ModuleDecoderRegistry::default(),
884 )?;
885
886 let total_len =
887 u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
888
889 decoder.decode_complete(
892 &mut reader,
893 total_len,
894 module_instance,
895 &ModuleRegistry::default(),
896 )
897 }
898}