1pub mod as_base64;
12pub mod as_hex;
13mod bls12_381;
14pub mod btc;
15mod collections;
16mod iroh;
17mod secp256k1;
18mod threshold_crypto;
19
20use std::borrow::Cow;
21use std::cmp;
22use std::fmt::{Debug, Display};
23use std::io::{self, Error, Read, Write};
24use std::time::{Duration, SystemTime, UNIX_EPOCH};
25
26use bitcoin::hashes::sha256;
27pub use fedimint_derive::{Decodable, Encodable};
28use hex::{FromHex, ToHex};
29use lightning::util::ser::BigSize;
30use serde::{Deserialize, Serialize};
31use thiserror::Error;
32
33use crate::core::ModuleInstanceId;
34use crate::module::registry::ModuleDecoderRegistry;
35use crate::util::SafeUrl;
36
37pub struct CountWrite<W> {
43 inner: W,
44 count: u64,
45}
46
47impl<W> CountWrite<W> {
48 pub fn count(&self) -> u64 {
50 self.count
51 }
52}
53
54impl<W> From<W> for CountWrite<W> {
55 fn from(inner: W) -> Self {
56 Self { inner, count: 0 }
57 }
58}
59
60impl<W: Write> io::Write for CountWrite<W> {
61 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
62 let written = self.inner.write(buf)?;
63 self.count += written as u64;
64 Ok(written)
65 }
66
67 fn flush(&mut self) -> io::Result<()> {
68 self.inner.flush()
69 }
70}
71
72pub trait DynEncodable {
77 fn consensus_encode_dyn(&self, writer: &mut dyn std::io::Write) -> Result<(), std::io::Error>;
78}
79
80impl Encodable for dyn DynEncodable {
81 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
82 self.consensus_encode_dyn(writer)
83 }
84}
85
86impl<T> DynEncodable for T
87where
88 T: Encodable,
89{
90 fn consensus_encode_dyn(
91 &self,
92 mut writer: &mut dyn std::io::Write,
93 ) -> Result<(), std::io::Error> {
94 <Self as Encodable>::consensus_encode(self, &mut writer)
95 }
96}
97
98impl Encodable for Box<dyn DynEncodable> {
99 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
100 (**self).consensus_encode_dyn(writer)
101 }
102}
103
104impl<T> Encodable for &T
105where
106 T: Encodable,
107{
108 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
109 (**self).consensus_encode(writer)
110 }
111}
112
113pub trait Encodable {
115 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error>;
120
121 fn consensus_encode_to_vec(&self) -> Vec<u8> {
123 let mut bytes = vec![];
124 self.consensus_encode(&mut bytes)
125 .expect("encoding to bytes can't fail for io reasons");
126 bytes
127 }
128
129 fn consensus_encode_to_hex(&self) -> String {
131 self.consensus_encode_to_vec().encode_hex()
135 }
136
137 fn consensus_encode_to_len(&self) -> u64 {
139 let mut writer = CountWrite::from(io::sink());
140 self.consensus_encode(&mut writer)
141 .expect("encoding to bytes can't fail for io reasons");
142
143 writer.count()
144 }
145
146 fn consensus_hash<H>(&self) -> H
152 where
153 H: bitcoin::hashes::Hash,
154 H::Engine: std::io::Write,
155 {
156 let mut engine = H::engine();
157 self.consensus_encode(&mut engine)
158 .expect("writing to HashEngine cannot fail");
159 H::from_engine(engine)
160 }
161
162 fn consensus_hash_sha256(&self) -> sha256::Hash {
164 self.consensus_hash()
165 }
166}
167
168pub const MAX_DECODE_SIZE: usize = 16_000_000;
171
172pub trait Decodable: Sized {
174 #[inline]
211 fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
212 r: &mut R,
213 modules: &ModuleDecoderRegistry,
214 ) -> Result<Self, DecodeError> {
215 Self::consensus_decode_partial(r, modules)
220 }
221
222 #[inline]
223 fn consensus_decode_whole(
224 slice: &[u8],
225 modules: &ModuleDecoderRegistry,
226 ) -> Result<Self, DecodeError> {
227 let total_len = slice.len() as u64;
228
229 let r = &mut &slice[..];
230 let mut r = Read::take(r, total_len);
231
232 let res = Self::consensus_decode_partial_from_finite_reader(&mut r, modules)?;
237 let left = r.limit();
238
239 if left != 0 {
240 return Err(fedimint_core::encoding::DecodeError::custom(format!(
241 "Type did not consume all bytes during decoding; expected={total_len}; \
242 left={left}; type={}",
243 std::any::type_name::<Self>(),
244 )));
245 }
246 Ok(res)
247 }
248 #[inline]
258 fn consensus_decode_partial<R: std::io::Read>(
259 r: &mut R,
260 modules: &ModuleDecoderRegistry,
261 ) -> Result<Self, DecodeError> {
262 Self::consensus_decode_partial_from_finite_reader(
263 &mut r.take(MAX_DECODE_SIZE as u64),
264 modules,
265 )
266 }
267
268 fn consensus_decode_hex(
270 hex: &str,
271 modules: &ModuleDecoderRegistry,
272 ) -> Result<Self, DecodeError> {
273 let bytes = Vec::<u8>::from_hex(hex).map_err(DecodeError::from_err)?;
274 Decodable::consensus_decode_whole(&bytes, modules)
275 }
276}
277
278pub fn encode_legacy_system_time<W: std::io::Write>(
284 time: &SystemTime,
285 writer: &mut W,
286) -> Result<(), std::io::Error> {
287 let duration = time
288 .duration_since(UNIX_EPOCH)
289 .expect("timestamps before the Unix epoch are unsupported");
290 duration.consensus_encode_dyn(writer)
291}
292
293pub fn decode_legacy_system_time_from_finite_reader<D: std::io::Read>(
298 decoder: &mut D,
299 modules: &ModuleDecoderRegistry,
300) -> Result<SystemTime, DecodeError> {
301 let duration = Duration::consensus_decode_partial_from_finite_reader(decoder, modules)?;
302 UNIX_EPOCH
307 .checked_add(duration)
308 .ok_or_else(|| DecodeError::from_str("SystemTime overflow: duration too large"))
309}
310
311pub fn encode_legacy_option_system_time<W: std::io::Write>(
316 time: &Option<SystemTime>,
317 writer: &mut W,
318) -> Result<(), std::io::Error> {
319 match time {
320 Some(time) => {
321 1u8.consensus_encode(writer)?;
322 encode_legacy_system_time(time, writer)
323 }
324 None => 0u8.consensus_encode(writer),
325 }
326}
327
328pub fn decode_legacy_option_system_time_from_finite_reader<D: std::io::Read>(
333 decoder: &mut D,
334 modules: &ModuleDecoderRegistry,
335) -> Result<Option<SystemTime>, DecodeError> {
336 match u8::consensus_decode_partial_from_finite_reader(decoder, modules)? {
337 0 => Ok(None),
338 1 => Ok(Some(decode_legacy_system_time_from_finite_reader(
339 decoder, modules,
340 )?)),
341 _ => Err(DecodeError::from_str(
342 "Invalid flag for option enum, expected 0 or 1",
343 )),
344 }
345}
346
347pub fn decode_field_from_finite_reader<T: Decodable, D: std::io::Read>(
350 decoder: &mut D,
351 modules: &ModuleDecoderRegistry,
352 context: &'static str,
353) -> Result<T, DecodeError> {
354 with_decoding_context(
355 T::consensus_decode_partial_from_finite_reader(decoder, modules),
356 context,
357 )
358}
359
360pub fn with_decoding_context<T>(
362 result: Result<T, DecodeError>,
363 context: &'static str,
364) -> Result<T, DecodeError> {
365 result.context(context)
366}
367
368#[derive(Debug, Error)]
375#[non_exhaustive]
376pub enum DecodeError {
377 #[error(transparent)]
379 Io(#[from] std::io::Error),
380 #[error("Invalid enum variant {variant} while decoding {type_name}")]
382 InvalidVariant {
383 variant: u64,
385 type_name: &'static str,
387 },
388 #[error("{context}")]
390 Context {
391 context: String,
393 #[source]
395 source: Box<Self>,
396 },
397 #[error(transparent)]
400 Invalid(Box<dyn std::error::Error + Send + Sync>),
401 #[error("{message}")]
403 Custom {
404 message: String,
406 },
407}
408
409impl DecodeError {
410 pub fn custom(message: impl Into<String>) -> Self {
412 Self::Custom {
413 message: message.into(),
414 }
415 }
416
417 #[allow(clippy::should_implement_trait)]
420 pub fn from_str(s: &'static str) -> Self {
421 Self::custom(s)
422 }
423
424 pub fn from_err<E>(e: E) -> Self
426 where
427 E: std::error::Error + Send + Sync + 'static,
428 {
429 Self::Invalid(Box::new(e))
430 }
431
432 pub fn context(self, context: impl Display) -> Self {
434 Self::Context {
435 context: context.to_string(),
436 source: Box::new(self),
437 }
438 }
439}
440
441pub trait DecodeContext<T> {
448 fn context<C>(self, context: C) -> Result<T, DecodeError>
450 where
451 C: Display;
452
453 fn with_context<C, F>(self, context: F) -> Result<T, DecodeError>
456 where
457 C: Display,
458 F: FnOnce() -> C;
459}
460
461impl<T, E> DecodeContext<T> for Result<T, E>
462where
463 E: Into<DecodeError>,
464{
465 fn context<C>(self, context: C) -> Result<T, DecodeError>
466 where
467 C: Display,
468 {
469 self.map_err(|error| error.into().context(context))
470 }
471
472 fn with_context<C, F>(self, context: F) -> Result<T, DecodeError>
473 where
474 C: Display,
475 F: FnOnce() -> C,
476 {
477 self.map_err(|error| error.into().context(context()))
478 }
479}
480
481impl Encodable for SafeUrl {
482 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
483 self.to_string().consensus_encode(writer)
484 }
485}
486
487impl Decodable for SafeUrl {
488 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
489 d: &mut D,
490 modules: &ModuleDecoderRegistry,
491 ) -> Result<Self, DecodeError> {
492 String::consensus_decode_partial_from_finite_reader(d, modules)?
493 .parse::<Self>()
494 .map_err(DecodeError::from_err)
495 }
496}
497
498macro_rules! impl_encode_decode_num_as_plain {
499 ($num_type:ty) => {
500 impl Encodable for $num_type {
501 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
502 let bytes = self.to_be_bytes();
503 writer.write_all(&bytes[..])?;
504 Ok(())
505 }
506 }
507
508 impl Decodable for $num_type {
509 fn consensus_decode_partial<D: std::io::Read>(
510 d: &mut D,
511 _modules: &ModuleDecoderRegistry,
512 ) -> Result<Self, crate::encoding::DecodeError> {
513 let mut bytes = [0u8; (<$num_type>::BITS / 8) as usize];
514 d.read_exact(&mut bytes)?;
515 Ok(<$num_type>::from_be_bytes(bytes))
516 }
517 }
518 };
519}
520
521macro_rules! impl_encode_decode_num_as_bigsize {
522 ($num_type:ty) => {
523 impl Encodable for $num_type {
524 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
525 BigSize(u64::from(*self)).consensus_encode(writer)
526 }
527 }
528
529 impl Decodable for $num_type {
530 fn consensus_decode_partial<D: std::io::Read>(
531 d: &mut D,
532 _modules: &ModuleDecoderRegistry,
533 ) -> Result<Self, crate::encoding::DecodeError> {
534 let varint = BigSize::consensus_decode_partial(d, &Default::default())?;
535 <$num_type>::try_from(varint.0).map_err(crate::encoding::DecodeError::from_err)
536 }
537 }
538 };
539}
540
541impl_encode_decode_num_as_bigsize!(u64);
542impl_encode_decode_num_as_bigsize!(u32);
543impl_encode_decode_num_as_bigsize!(u16);
544impl_encode_decode_num_as_plain!(u8);
545
546macro_rules! impl_encode_decode_tuple {
547 ($($x:ident),*) => (
548 #[allow(non_snake_case)]
549 impl <$($x: Encodable),*> Encodable for ($($x),*) {
550 fn consensus_encode<W: std::io::Write>(&self, s: &mut W) -> Result<(), std::io::Error> {
551 let &($(ref $x),*) = self;
552 $($x.consensus_encode(s)?;)*
553 Ok(())
554 }
555 }
556
557 #[allow(non_snake_case)]
558 impl<$($x: Decodable),*> Decodable for ($($x),*) {
559 fn consensus_decode_partial<D: std::io::Read>(d: &mut D, modules: &ModuleDecoderRegistry) -> Result<Self, DecodeError> {
560 Ok(($({let $x = Decodable::consensus_decode_partial(d, modules)?; $x }),*))
561 }
562 }
563 );
564}
565
566impl_encode_decode_tuple!(T1, T2);
567impl_encode_decode_tuple!(T1, T2, T3);
568impl_encode_decode_tuple!(T1, T2, T3, T4);
569impl_encode_decode_tuple!(T1, T2, T3, T4, T5);
570impl_encode_decode_tuple!(T1, T2, T3, T4, T5, T6);
571
572impl<T> Encodable for Option<T>
573where
574 T: Encodable,
575{
576 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
577 if let Some(inner) = self {
578 1u8.consensus_encode(writer)?;
579 inner.consensus_encode(writer)?;
580 } else {
581 0u8.consensus_encode(writer)?;
582 }
583 Ok(())
584 }
585}
586
587impl<T> Decodable for Option<T>
588where
589 T: Decodable,
590{
591 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
592 d: &mut D,
593 modules: &ModuleDecoderRegistry,
594 ) -> Result<Self, DecodeError> {
595 let flag = u8::consensus_decode_partial_from_finite_reader(d, modules)?;
596 match flag {
597 0 => Ok(None),
598 1 => Ok(Some(T::consensus_decode_partial_from_finite_reader(
599 d, modules,
600 )?)),
601 _ => Err(DecodeError::from_str(
602 "Invalid flag for option enum, expected 0 or 1",
603 )),
604 }
605 }
606}
607
608impl<T, E> Encodable for Result<T, E>
609where
610 T: Encodable,
611 E: Encodable,
612{
613 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
614 match self {
615 Ok(value) => {
616 1u8.consensus_encode(writer)?;
617 value.consensus_encode(writer)?;
618 }
619 Err(error) => {
620 0u8.consensus_encode(writer)?;
621 error.consensus_encode(writer)?;
622 }
623 }
624
625 Ok(())
626 }
627}
628
629impl<T, E> Decodable for Result<T, E>
630where
631 T: Decodable,
632 E: Decodable,
633{
634 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
635 d: &mut D,
636 modules: &ModuleDecoderRegistry,
637 ) -> Result<Self, DecodeError> {
638 let flag = u8::consensus_decode_partial_from_finite_reader(d, modules)?;
639 match flag {
640 0 => Ok(Err(E::consensus_decode_partial_from_finite_reader(
641 d, modules,
642 )?)),
643 1 => Ok(Ok(T::consensus_decode_partial_from_finite_reader(
644 d, modules,
645 )?)),
646 _ => Err(DecodeError::from_str(
647 "Invalid flag for option enum, expected 0 or 1",
648 )),
649 }
650 }
651}
652
653impl<T> Encodable for Box<T>
654where
655 T: Encodable,
656{
657 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
658 self.as_ref().consensus_encode(writer)
659 }
660}
661
662impl<T> Decodable for Box<T>
663where
664 T: Decodable,
665{
666 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
667 d: &mut D,
668 modules: &ModuleDecoderRegistry,
669 ) -> Result<Self, DecodeError> {
670 Ok(Self::new(T::consensus_decode_partial_from_finite_reader(
671 d, modules,
672 )?))
673 }
674}
675
676impl Encodable for () {
677 fn consensus_encode<W: std::io::Write>(&self, _writer: &mut W) -> Result<(), std::io::Error> {
678 Ok(())
679 }
680}
681
682impl Decodable for () {
683 fn consensus_decode_partial<D: std::io::Read>(
684 _d: &mut D,
685 _modules: &ModuleDecoderRegistry,
686 ) -> Result<Self, DecodeError> {
687 Ok(())
688 }
689}
690
691impl Encodable for &str {
692 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
693 self.as_bytes().consensus_encode(writer)
694 }
695}
696
697impl Encodable for String {
698 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
699 self.as_bytes().consensus_encode(writer)
700 }
701}
702
703impl Decodable for String {
704 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
705 d: &mut D,
706 modules: &ModuleDecoderRegistry,
707 ) -> Result<Self, DecodeError> {
708 Self::from_utf8(Decodable::consensus_decode_partial_from_finite_reader(
709 d, modules,
710 )?)
711 .map_err(DecodeError::from_err)
712 }
713}
714
715impl Encodable for Duration {
716 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
717 self.as_secs().consensus_encode(writer)?;
718 self.subsec_nanos().consensus_encode(writer)?;
719
720 Ok(())
721 }
722}
723
724impl Decodable for Duration {
725 fn consensus_decode_partial<D: std::io::Read>(
726 d: &mut D,
727 modules: &ModuleDecoderRegistry,
728 ) -> Result<Self, DecodeError> {
729 let secs = Decodable::consensus_decode_partial(d, modules)?;
730 let nsecs = Decodable::consensus_decode_partial(d, modules)?;
731 if 1_000_000_000 <= nsecs {
736 return Err(DecodeError::from_str("Duration nanoseconds out of range"));
737 }
738 Ok(Self::new(secs, nsecs))
739 }
740}
741
742impl Encodable for bool {
743 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
744 let bool_as_u8 = u8::from(*self);
745 writer.write_all(&[bool_as_u8])?;
746 Ok(())
747 }
748}
749
750impl Decodable for bool {
751 fn consensus_decode_partial<D: Read>(
752 d: &mut D,
753 _modules: &ModuleDecoderRegistry,
754 ) -> Result<Self, DecodeError> {
755 let mut bool_as_u8 = [0u8];
756 d.read_exact(&mut bool_as_u8)?;
757 match bool_as_u8[0] {
758 0 => Ok(false),
759 1 => Ok(true),
760 _ => Err(DecodeError::from_str("Out of range, expected 0 or 1")),
761 }
762 }
763}
764
765impl Encodable for Cow<'static, str> {
766 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
767 self.as_ref().consensus_encode(writer)
768 }
769}
770
771impl Decodable for Cow<'static, str> {
772 fn consensus_decode_partial<D: std::io::Read>(
773 d: &mut D,
774 modules: &ModuleDecoderRegistry,
775 ) -> Result<Self, DecodeError> {
776 Ok(Cow::Owned(String::consensus_decode_partial(d, modules)?))
777 }
778}
779
780#[derive(Debug, Clone, Serialize, Deserialize)]
799pub enum DynRawFallback<T> {
800 Raw {
801 module_instance_id: ModuleInstanceId,
802 #[serde(with = "::fedimint_core::encoding::as_hex")]
803 raw: Vec<u8>,
804 },
805 Decoded(T),
806}
807
808impl<T> cmp::PartialEq for DynRawFallback<T>
809where
810 T: cmp::PartialEq + Encodable,
811{
812 fn eq(&self, other: &Self) -> bool {
813 match (self, other) {
814 (
815 Self::Raw {
816 module_instance_id: mid_self,
817 raw: raw_self,
818 },
819 Self::Raw {
820 module_instance_id: mid_other,
821 raw: raw_other,
822 },
823 ) => mid_self.eq(mid_other) && raw_self.eq(raw_other),
824 (r @ Self::Raw { .. }, d @ Self::Decoded(_))
825 | (d @ Self::Decoded(_), r @ Self::Raw { .. }) => {
826 r.consensus_encode_to_vec() == d.consensus_encode_to_vec()
827 }
828 (Self::Decoded(s), Self::Decoded(o)) => s == o,
829 }
830 }
831}
832
833impl<T> cmp::Eq for DynRawFallback<T> where T: cmp::Eq + Encodable {}
834
835impl<T> DynRawFallback<T>
836where
837 T: Decodable + 'static,
838{
839 pub fn decoded(self) -> Option<T> {
841 match self {
842 Self::Raw { .. } => None,
843 Self::Decoded(v) => Some(v),
844 }
845 }
846
847 pub fn expect_decoded(self) -> T {
849 match self {
850 Self::Raw { .. } => {
851 panic!("Expected decoded value. Possibly `redecode_raw` call is missing.")
852 }
853 Self::Decoded(v) => v,
854 }
855 }
856
857 pub fn expect_decoded_ref(&self) -> &T {
859 match self {
860 Self::Raw { .. } => {
861 panic!("Expected decoded value. Possibly `redecode_raw` call is missing.")
862 }
863 Self::Decoded(v) => v,
864 }
865 }
866
867 pub fn redecode_raw(
872 self,
873 decoders: &ModuleDecoderRegistry,
874 ) -> Result<Self, crate::encoding::DecodeError> {
875 Ok(match self {
876 Self::Raw {
877 module_instance_id,
878 raw,
879 } => match decoders.get(module_instance_id) {
880 Some(decoder) => Self::Decoded(decoder.decode_complete(
881 &mut &raw[..],
882 raw.len() as u64,
883 module_instance_id,
884 decoders,
885 )?),
886 None => Self::Raw {
887 module_instance_id,
888 raw,
889 },
890 },
891 Self::Decoded(v) => Self::Decoded(v),
892 })
893 }
894}
895
896impl<T> From<T> for DynRawFallback<T> {
897 fn from(value: T) -> Self {
898 Self::Decoded(value)
899 }
900}
901
902impl<T> Decodable for DynRawFallback<T>
903where
904 T: Decodable + 'static,
905{
906 fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
907 reader: &mut R,
908 decoders: &ModuleDecoderRegistry,
909 ) -> Result<Self, crate::encoding::DecodeError> {
910 let module_instance_id =
911 fedimint_core::core::ModuleInstanceId::consensus_decode_partial_from_finite_reader(
912 reader, decoders,
913 )?;
914 Ok(match decoders.get(module_instance_id) {
915 Some(decoder) => {
916 let total_len_u64 =
917 u64::consensus_decode_partial_from_finite_reader(reader, decoders)?;
918 Self::Decoded(decoder.decode_complete(
919 reader,
920 total_len_u64,
921 module_instance_id,
922 decoders,
923 )?)
924 }
925 None => {
926 Self::Raw {
928 module_instance_id,
929 raw: Vec::consensus_decode_partial_from_finite_reader(reader, decoders)?,
930 }
931 }
932 })
933 }
934}
935
936impl<T> Encodable for DynRawFallback<T>
937where
938 T: Encodable,
939{
940 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
941 match self {
942 Self::Raw {
943 module_instance_id,
944 raw,
945 } => {
946 module_instance_id.consensus_encode(writer)?;
947 raw.consensus_encode(writer)?;
948 Ok(())
949 }
950 Self::Decoded(v) => v.consensus_encode(writer),
951 }
952 }
953}
954
955#[cfg(test)]
956mod tests;