From a6ecc58a5e3f96076c0795c7a3f50abb8fb1846b Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Fri, 16 Sep 2022 17:27:44 +0200 Subject: [PATCH 1/2] Add `put_bytes_min` and `space_remaining` methods Pnicking on oversized slice is useful to catch errors in code that's supposed to know the exact sizes but this is undesirable in code that doesn't. These two methods help with handling the case when `buf.len()` is not known upfront. --- internals/src/hex/buf_encoder.rs | 52 +++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/internals/src/hex/buf_encoder.rs b/internals/src/hex/buf_encoder.rs index 48174bec..7a09d2e3 100644 --- a/internals/src/hex/buf_encoder.rs +++ b/internals/src/hex/buf_encoder.rs @@ -177,15 +177,25 @@ impl BufEncoder { #[inline] #[cfg_attr(rust_v_1_46, track_caller)] pub fn put_bytes(&mut self, bytes: &[u8], case: Case) { - // Panic if the result wouldn't fit address space to not waste time and give the optimizer - // more opportunities. - let double_len = bytes.len().checked_mul(2).expect("overflow"); - assert!(double_len <= self.buf.as_out_bytes().len() - self.pos); + assert!(bytes.len() <= self.space_remaining()); for byte in bytes { self.put_byte(*byte, case); } } + /// Encodes as many `bytes` as fit into the buffer as hex and return the remainder. + /// + /// This method works just like `put_bytes` but instead of panicking it returns the unwritten + /// bytes. The method returns an empty slice if all bytes were written + #[must_use = "this may write only part of the input buffer"] + #[inline] + #[cfg_attr(rust_v_1_46, track_caller)] + pub fn put_bytes_min<'a>(&mut self, bytes: &'a [u8], case: Case) -> &'a [u8] { + let to_write = self.space_remaining().min(bytes.len()); + self.put_bytes(&bytes[..to_write], case); + &bytes[to_write..] + } + /// Returns true if no more bytes can be written into the buffer. #[inline] pub fn is_full(&self) -> bool { self.pos == self.buf.as_out_bytes().len() } @@ -200,6 +210,14 @@ impl BufEncoder { /// Resets the buffer to become empty. #[inline] pub fn clear(&mut self) { self.pos = 0; } + + /// How many bytes can be written to this buffer. + /// + /// Note that this returns the number of bytes before encoding, not number of hex digits. + #[inline] + pub fn space_remaining(&self) -> usize { + (self.buf.as_out_bytes().len() - self.pos) / 2 + } } #[cfg(test)] @@ -218,13 +236,17 @@ mod tests { fn single_byte_exact_buf() { let mut buf = [0u8; 2]; let mut encoder = BufEncoder::new(&mut buf); + assert_eq!(encoder.space_remaining(), 1); encoder.put_byte(42, Case::Lower); assert_eq!(encoder.as_str(), "2a"); + assert_eq!(encoder.space_remaining(), 0); assert!(encoder.is_full()); encoder.clear(); + assert_eq!(encoder.space_remaining(), 1); assert!(!encoder.is_full()); encoder.put_byte(42, Case::Upper); assert_eq!(encoder.as_str(), "2A"); + assert_eq!(encoder.space_remaining(), 0); assert!(encoder.is_full()); } @@ -232,12 +254,16 @@ mod tests { fn single_byte_oversized_buf() { let mut buf = [0u8; 4]; let mut encoder = BufEncoder::new(&mut buf); + assert_eq!(encoder.space_remaining(), 2); encoder.put_byte(42, Case::Lower); + assert_eq!(encoder.space_remaining(), 1); assert_eq!(encoder.as_str(), "2a"); assert!(!encoder.is_full()); encoder.clear(); + assert_eq!(encoder.space_remaining(), 2); encoder.put_byte(42, Case::Upper); assert_eq!(encoder.as_str(), "2A"); + assert_eq!(encoder.space_remaining(), 1); assert!(!encoder.is_full()); } @@ -246,7 +272,9 @@ mod tests { let mut buf = [0u8; 4]; let mut encoder = BufEncoder::new(&mut buf); encoder.put_byte(42, Case::Lower); + assert_eq!(encoder.space_remaining(), 1); encoder.put_byte(255, Case::Lower); + assert_eq!(encoder.space_remaining(), 0); assert_eq!(encoder.as_str(), "2aff"); assert!(encoder.is_full()); encoder.clear(); @@ -257,6 +285,22 @@ mod tests { assert!(encoder.is_full()); } + #[test] + fn put_bytes_min() { + let mut buf = [0u8; 2]; + let mut encoder = BufEncoder::new(&mut buf); + let remainder = encoder.put_bytes_min(b"", Case::Lower); + assert_eq!(remainder, b""); + assert_eq!(encoder.as_str(), ""); + let remainder = encoder.put_bytes_min(b"*", Case::Lower); + assert_eq!(remainder, b""); + assert_eq!(encoder.as_str(), "2a"); + encoder.clear(); + let remainder = encoder.put_bytes_min(&[42, 255], Case::Lower); + assert_eq!(remainder, &[255]); + assert_eq!(encoder.as_str(), "2a"); + } + #[test] fn same_as_fmt() { use core::fmt::{self, Write}; From 1a2cf2681d5c6a77a45a60106c98131fb063a03f Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Thu, 8 Sep 2022 16:50:24 +0200 Subject: [PATCH 2/2] Implement consensus encoding adapter for serde In some protocols it is preferred to serialize consensus-encodable types using consensus encoding. E.g. serialize `Transaction` as hex-encoded string in Json in Bitcoin Core RPC protocol. This change provides adapter to make this easier. The adapter allows providing custom byte-to-string encoder for more exotic cases and provides a hex implementation which should be useful in majority of the cases. Should help with #765 --- bitcoin/Cargo.toml | 1 + bitcoin/build.rs | 31 ++ bitcoin/src/blockdata/transaction.rs | 18 + bitcoin/src/consensus/mod.rs | 4 + bitcoin/src/consensus/serde.rs | 542 +++++++++++++++++++++++++++ bitcoin/src/lib.rs | 9 + 6 files changed, 605 insertions(+) create mode 100644 bitcoin/build.rs create mode 100644 bitcoin/src/consensus/serde.rs diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index 995fbe53..294e3da6 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -49,6 +49,7 @@ hashbrown = { version = "0.8", optional = true } [dev-dependencies] serde_json = "1.0.0" serde_test = "1.0.19" +serde_derive = "1.0.103" secp256k1 = { version = "0.24.0", features = [ "recovery", "rand-std" ] } bincode = "1.3.1" diff --git a/bitcoin/build.rs b/bitcoin/build.rs new file mode 100644 index 00000000..0879f651 --- /dev/null +++ b/bitcoin/build.rs @@ -0,0 +1,31 @@ +fn main() { + let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); + let output = std::process::Command::new(rustc) + .arg("--version") + .output() + .expect("Failed to run rustc --version"); + assert!(output.status.success(), "Failed to get rust version"); + let stdout = String::from_utf8(output.stdout).expect("rustc produced non-UTF-8 output"); + let version_prefix = "rustc "; + if !stdout.starts_with(version_prefix) { + panic!("unexpected rustc output: {}", stdout); + } + + let version = &stdout[version_prefix.len()..]; + let end = version.find(&[' ', '-'] as &[_]).unwrap_or(version.len()); + let version = &version[..end]; + let mut version_components = version.split('.'); + let major = version_components.next().unwrap(); + assert_eq!(major, "1", "Unexpected Rust version"); + let minor = version_components + .next() + .unwrap_or("0") + .parse::() + .expect("invalid Rust minor version"); + + for activate_version in &[46] { + if minor >= *activate_version { + println!("cargo:rustc-cfg=rust_v_1_{}", activate_version); + } + } +} diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index a43d6b12..df3e2493 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -1192,6 +1192,24 @@ mod tests { assert_eq!(tx_without_witness.strippedsize(), expected_strippedsize); } + // We temporarily abuse `Transaction` for testing consensus serde adapter. + #[cfg(feature = "serde")] + #[test] + fn test_consensus_serde() { + use crate::consensus::serde as con_serde; + let json = "\"010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3603da1b0e00045503bd5704c7dd8a0d0ced13bb5785010800000000000a636b706f6f6c122f4e696e6a61506f6f6c2f5345475749542fffffffff02b4e5a212000000001976a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac0000000000000000266a24aa21a9edf91c46b49eb8a29089980f02ee6b57e7d63d33b18b4fddac2bcd7db2a39837040120000000000000000000000000000000000000000000000000000000000000000000000000\""; + let mut deserializer = serde_json::Deserializer::from_str(json); + let tx = con_serde::With::::deserialize::<'_, Transaction, _>(&mut deserializer) + .unwrap(); + let tx_bytes = Vec::from_hex(&json[1..(json.len() - 1)]).unwrap(); + let expected = deserialize::(&tx_bytes).unwrap(); + assert_eq!(tx, expected); + let mut bytes = Vec::new(); + let mut serializer = serde_json::Serializer::new(&mut bytes); + con_serde::With::::serialize(&tx, &mut serializer).unwrap(); + assert_eq!(bytes, json.as_bytes()) + } + #[test] fn test_transaction_version() { let tx_bytes = Vec::from_hex("ffffff7f0100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000").unwrap(); diff --git a/bitcoin/src/consensus/mod.rs b/bitcoin/src/consensus/mod.rs index 40514e9f..9ccae25f 100644 --- a/bitcoin/src/consensus/mod.rs +++ b/bitcoin/src/consensus/mod.rs @@ -12,3 +12,7 @@ pub mod params; pub use self::encode::{Encodable, Decodable, WriteExt, ReadExt}; pub use self::encode::{serialize, deserialize, deserialize_partial}; pub use self::params::Params; + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +pub mod serde; diff --git a/bitcoin/src/consensus/serde.rs b/bitcoin/src/consensus/serde.rs new file mode 100644 index 00000000..6de7ddfa --- /dev/null +++ b/bitcoin/src/consensus/serde.rs @@ -0,0 +1,542 @@ +//! Serde serialization via consensus encoding +//! +//! This provides functions for (de)serializing any type as consensus-encoded bytes. +//! For human-readable formats it serializes as a string with a consumer-supplied encoding, for +//! binary formats it serializes as a sequence of bytes (not `serialize_bytes` to avoid allocations). +//! +//! The string encoding has to be specified using a marker type implementing the encoding strategy. +//! This crate provides hex encoding via `Hex` and `Hex` + +use core::fmt; +use core::marker::PhantomData; +use crate::io; +use serde::{Serializer, Deserializer}; +use serde::de::{Visitor, SeqAccess, Unexpected}; +use serde::ser::SerializeSeq; +use super::{Encodable, Decodable}; +use super::encode::Error as ConsensusError; + +/// Hex-encoding strategy +pub struct Hex(PhantomData) where Case: hex::Case; + +impl Default for Hex { + fn default() -> Self { + Hex(Default::default()) + } +} + +impl ByteEncoder for Hex { + type Encoder = hex::Encoder; +} + +/// Implements hex encoding. +pub mod hex { + use core::fmt; + use core::marker::PhantomData; + use bitcoin_internals as internals; + use internals::hex::BufEncoder; + + /// Marker for upper/lower case type-level flags ("type-level enum"). + /// + /// You may use this trait in bounds only. + pub trait Case: sealed::Case {} + impl Case for T {} + + /// Marker for using lower-case hex encoding. + pub enum Lower {} + /// Marker for using upper-case hex encoding. + pub enum Upper {} + + mod sealed { + use bitcoin_internals as internals; + + pub trait Case { + /// Internal detail, don't depend on it!!! + const INTERNAL_CASE: internals::hex::Case; + } + + impl Case for super::Lower { + const INTERNAL_CASE: internals::hex::Case = internals::hex::Case::Lower; + } + + impl Case for super::Upper { + const INTERNAL_CASE: internals::hex::Case = internals::hex::Case::Upper; + } + } + + // TODO measure various sizes and determine the best value + const HEX_BUF_SIZE: usize = 512; + + /// Hex byte encoder. + // We wrap `BufEncoder` to not leak internal representation. + pub struct Encoder(BufEncoder<[u8; HEX_BUF_SIZE]>, PhantomData); + + impl From> for Encoder { + fn from(_: super::Hex) -> Self { + Encoder(BufEncoder::new([0; HEX_BUF_SIZE]), Default::default()) + } + } + + impl super::EncodeBytes for Encoder { + fn encode_chunk(&mut self, writer: &mut W, mut bytes: &[u8]) -> fmt::Result { + while !bytes.is_empty() { + if self.0.is_full() { + self.flush(writer)?; + } + bytes = self.0.put_bytes_min(bytes, C::INTERNAL_CASE); + } + Ok(()) + } + + fn flush(&mut self, writer: &mut W) -> fmt::Result { + writer.write_str(self.0.as_str())?; + self.0.clear(); + Ok(()) + } + } + + // Newtypes to hide internal details. + // TODO: statically prove impossible cases + + /// Error returned when a hex string decoder can't be created. + #[derive(Debug)] + pub struct DecodeInitError(bitcoin_hashes::hex::Error); + + /// Error returned when a hex string contains invalid characters. + #[derive(Debug)] + pub struct DecodeError(bitcoin_hashes::hex::Error); + + /// Hex decoder state. + pub struct Decoder<'a>(bitcoin_hashes::hex::HexIterator<'a>); + + impl<'a> Decoder<'a> { + fn new(s: &'a str) -> Result { + match bitcoin_hashes::hex::HexIterator::new(s) { + Ok(iter) => Ok(Decoder(iter)), + Err(error) => Err(DecodeInitError(error)), + } + } + } + + impl<'a> Iterator for Decoder<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + self.0.next().map(|result| result.map_err(DecodeError)) + } + } + + impl<'a, C: Case> super::ByteDecoder<'a> for super::Hex { + type InitError = DecodeInitError; + type DecodeError = DecodeError; + type Decoder = Decoder<'a>; + + fn from_str(s: &'a str) -> Result { + Decoder::new(s) + } + } + + impl super::IntoDeError for DecodeInitError { + fn into_de_error(self) -> E { + use bitcoin_hashes::hex::Error; + + match self.0 { + Error::OddLengthString(len) => E::invalid_length(len, &"an even number of ASCII-encoded hex digits"), + error => panic!("unexpected error: {:?}", error), + } + } + } + + impl super::IntoDeError for DecodeError { + fn into_de_error(self) -> E { + use bitcoin_hashes::hex::Error; + use serde::de::Unexpected; + + const EXPECTED_CHAR: &str = "an ASCII-encoded hex digit"; + + match self.0 { + Error::InvalidChar(c) if c.is_ascii() => E::invalid_value(Unexpected::Char(c as _), &EXPECTED_CHAR), + Error::InvalidChar(c) => E::invalid_value(Unexpected::Unsigned(c.into()), &EXPECTED_CHAR), + error => panic!("unexpected error: {:?}", error), + } + } + } +} + +struct DisplayWrapper<'a, T: 'a + Encodable, E>(&'a T, PhantomData); + +impl<'a, T: 'a + Encodable, E: ByteEncoder> fmt::Display for DisplayWrapper<'a, T, E> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut writer = IoWrapper::<'_, _, E::Encoder>::new(f, E::default().into()); + self.0.consensus_encode(&mut writer).map_err(|error| { + #[cfg(debug_assertions)] + { + use crate::StdError; + + if error.kind() != io::ErrorKind::Other || error.source().is_some() || !writer.writer.was_error { + panic!("{} returned an unexpected error: {:?}", core::any::type_name::(), error); + } + } + fmt::Error + })?; + let result = writer.actually_flush(); + if result.is_err() { + writer.writer.assert_was_error::(); + } + result + } +} + +struct ErrorTrackingWriter { + writer: W, + #[cfg(debug_assertions)] + was_error: bool, +} + +impl ErrorTrackingWriter { + fn new(writer: W) -> Self { + ErrorTrackingWriter { + writer, + #[cfg(debug_assertions)] + was_error: false, + } + } + + #[cfg_attr(rust_v_1_46, track_caller)] + fn assert_no_error(&self, fun: &str) { + #[cfg(debug_assertions)] + { + if self.was_error { + panic!("`{}` called on errored writer", fun); + } + } + } + + fn assert_was_error(&self) { + #[cfg(debug_assertions)] + { + if !self.was_error { + panic!("{} returned an error unexpectedly", core::any::type_name::()); + } + } + } + + fn set_error(&mut self, was: bool) { + #[cfg(debug_assertions)] + { + self.was_error |= was; + } + } + + fn check_err(&mut self, result: Result) -> Result { + self.set_error(result.is_err()); + result + } +} + +impl fmt::Write for ErrorTrackingWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.assert_no_error("write_str"); + let result = self.writer.write_str(s); + self.check_err(result) + } + + fn write_char(&mut self, c: char) -> fmt::Result { + self.assert_no_error("write_char"); + let result = self.writer.write_char(c); + self.check_err(result) + } +} + +struct IoWrapper<'a, W: fmt::Write, E: EncodeBytes> { + writer: ErrorTrackingWriter<&'a mut W>, + encoder: E, +} + +impl<'a, W: fmt::Write, E: EncodeBytes> IoWrapper<'a, W, E> { + fn new(writer: &'a mut W, encoder: E) -> Self { + IoWrapper { + writer: ErrorTrackingWriter::new(writer), + encoder, + } + } + + fn actually_flush(&mut self) -> fmt::Result { + self.encoder.flush(&mut self.writer) + } +} + +impl<'a, W: fmt::Write, E: EncodeBytes> io::Write for IoWrapper<'a, W, E> { + fn write(&mut self, bytes: &[u8]) -> io::Result { + match self.encoder.encode_chunk(&mut self.writer, bytes) { + Ok(()) => Ok(bytes.len()), + Err(fmt::Error) => { + self.writer.assert_was_error::(); + Err(io::Error::from(io::ErrorKind::Other)) + } + } + } + // we intentionally ignore flushes because we will do a single flush at the end. + fn flush(&mut self) -> io::Result<()> { Ok(()) } +} + +/// Provides an instance of byte-to-string encoder. +/// +/// This is basically a type constructor used in places where value arguments are not accepted. +/// Such as the generic `serialize`. +pub trait ByteEncoder: Default { + /// The encoder state. + type Encoder: EncodeBytes + From; +} + +/// Transforms given bytes and writes to the writer. +/// +/// The encoder is allowed to be buffered (and probably should be). +/// The design passing writer each time bypasses the need for GAT. +pub trait EncodeBytes { + /// Transform the provided slice and write to the writer. + /// + /// This is similar to the `write_all` method on `io::Write`. + fn encode_chunk(&mut self, writer: &mut W, bytes: &[u8]) -> fmt::Result; + + /// Write data in buffer (if any) to the writer. + fn flush(&mut self, writer: &mut W) -> fmt::Result; +} + +/// Provides an instance of string-to-byte decoder. +/// +/// This is basically a type constructor used in places where value arguments are not accepted. +/// Such as the generic `serialize`. +pub trait ByteDecoder<'a> { + /// Error returned when decoder can't be created. + /// + /// This is typically returned when string length is invalid. + type InitError: IntoDeError + fmt::Debug; + + /// Error returned when decoding fails. + /// + /// This is typically returned when the input string contains malformed chars. + type DecodeError: IntoDeError + fmt::Debug; + + /// The decoder state. + type Decoder: Iterator>; + + /// Constructs the decoder from string. + fn from_str(s: &'a str) -> Result; +} + +/// Converts error into a type implementing `serde::de::Error` +pub trait IntoDeError { + /// Performs the conversion. + fn into_de_error(self) -> E; +} + +struct BinWriter{ + serializer: S, + error: Option, +} + +impl io::Write for BinWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.write_all(buf).map(|_| buf.len()) + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + for byte in buf { + if let Err(error) = self.serializer.serialize_element(byte) { + self.error = Some(error); + return Err(io::ErrorKind::Other.into()); + } + } + Ok(()) + } + + fn flush(&mut self) -> io::Result<()> { Ok(()) } +} + +struct DisplayExpected(D); + +impl serde::de::Expected for DisplayExpected { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, formatter) + } +} + +enum DecodeError { + TooManyBytes, + Consensus(ConsensusError), + Other(E), +} + +// not a trait impl because we panic on some variants +fn consensus_error_into_serde(error: ConsensusError) -> E { + match error { + ConsensusError::Io(error) => panic!("unexpected IO error {:?}", error), + ConsensusError::Psbt(_) => panic!("PSBT shouldn't implement consensus encoding"), + ConsensusError::OversizedVectorAllocation { requested, max } => E::custom(format_args!("the requested allocation of {} items exceeds maximum of {}", requested, max)), + ConsensusError::InvalidChecksum { expected, actual } => E::invalid_value(Unexpected::Bytes(&actual), &DisplayExpected(format_args!("checksum {:02x}{:02x}{:02x}{:02x}", expected[0], expected[1], expected[2], expected[3]))), + ConsensusError::NonMinimalVarInt => E::custom(format_args!("compact size was not encoded minimally")), + ConsensusError::ParseFailed(msg) => E::custom(msg), + ConsensusError::UnsupportedSegwitFlag(flag) => E::invalid_value(Unexpected::Unsigned(flag.into()), &"segwit version 1 flag"), + } +} + +impl DecodeError where E: serde::de::Error { + fn unify(self) -> E { + match self { + DecodeError::Other(error) => error, + DecodeError::TooManyBytes => E::custom(format_args!("got more bytes than expected")), + DecodeError::Consensus(error) => consensus_error_into_serde(error), + } + } +} + +impl IntoDeError for DecodeError where E: IntoDeError { + fn into_de_error(self) -> DE { + match self { + DecodeError::Other(error) => error.into_de_error(), + DecodeError::TooManyBytes => DE::custom(format_args!("got more bytes than expected")), + DecodeError::Consensus(error) => consensus_error_into_serde(error), + } + } +} + +struct IterReader>> { + iterator: core::iter::Fuse, + error: Option, +} + +impl>> IterReader { + fn new(iterator: I) -> Self { + IterReader { + iterator: iterator.fuse(), + error: None, + } + } + + fn decode(mut self) -> Result> { + use crate::StdError; + + let result = T::consensus_decode(&mut self); + match (result, self.error) { + (Ok(_), None) if self.iterator.next().is_some() => { + Err(DecodeError::TooManyBytes) + }, + (Ok(value), None) => Ok(value), + (Ok(_), Some(error)) => panic!("{} silently ate the error: {:?}", core::any::type_name::(), error), + (Err(ConsensusError::Io(io_error)), Some(de_error)) if io_error.kind() == io::ErrorKind::Other && io_error.source().is_none() => Err(DecodeError::Other(de_error)), + (Err(consensus_error), None) => Err(DecodeError::Consensus(consensus_error)), + (Err(ConsensusError::Io(io_error)), de_error) => panic!("Unexpected IO error {:?} returned from {}::consensus_decode(), deserialization error: {:?}", io_error, core::any::type_name::(), de_error), + (Err(consensus_error), Some(de_error)) => panic!("{} should've returned `Other` IO error because of deserialization error {:?} but it returned consensus error {:?} instead", core::any::type_name::(), de_error, consensus_error), + } + } +} + +impl>> io::Read for IterReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut count = 0; + for (dst, src) in buf.iter_mut().zip(&mut self.iterator) { + match src { + Ok(byte) => *dst = byte, + Err(error) => { + self.error = Some(error); + return Err(io::ErrorKind::Other.into()); + } + } + // bounded by the length of buf + count += 1; + } + Ok(count) + } +} + +/// Helper for `#[serde(with = "")]`. +/// +/// To (de)serialize a field using consensus encoding you can write e.g.: +/// +/// ``` +/// use bitcoin::Transaction; +/// use bitcoin::consensus; +/// use serde_derive::{Serialize, Deserialize}; +/// +/// #[derive(Serialize, Deserialize)] +/// # #[serde(crate = "actual_serde")] +/// pub struct MyStruct { +/// #[serde(with = "consensus::serde::With::")] +/// tx: Transaction, +/// } +/// ``` +pub struct With(PhantomData); + +impl With { + /// Serializes the value as consensus-encoded + pub fn serialize(value: &T, serializer: S) -> Result where E: ByteEncoder { + if serializer.is_human_readable() { + serializer.collect_str(&DisplayWrapper::<'_, _, E>(value, Default::default())) + } else { + use crate::StdError; + + let serializer = serializer.serialize_seq(None)?; + let mut writer = BinWriter { + serializer, + error: None, + }; + + let result = value.consensus_encode(&mut writer); + match (result, writer.error) { + (Ok(_), None) => writer.serializer.end(), + (Ok(_), Some(error)) => panic!("{} silently ate an IO error: {:?}", core::any::type_name::(), error), + (Err(io_error), Some(ser_error)) if io_error.kind() == io::ErrorKind::Other && io_error.source().is_none() => Err(ser_error), + (Err(io_error), ser_error) => panic!("{} returned an unexpected IO error: {:?} serialization error: {:?}", core::any::type_name::(), io_error, ser_error), + } + } + } + + /// Deserializes the value as consensus-encoded + pub fn deserialize<'d, T: Decodable, D: Deserializer<'d>>(deserializer: D) -> Result where for<'a> E: ByteDecoder<'a> { + if deserializer.is_human_readable() { + deserializer.deserialize_str(HRVisitor::<_, E>(Default::default())) + } else { + deserializer.deserialize_seq(BinVisitor(Default::default())) + } + } +} + +struct HRVisitor ByteDecoder<'a>>(PhantomData (T, D)>); + +impl<'de, T: Decodable, D: for<'a> ByteDecoder<'a>> Visitor<'de> for HRVisitor { + type Value = T; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("bytes encoded as a hex string") + } + + fn visit_str(self, s: &str) -> Result { + let decoder = D::from_str(s).map_err(IntoDeError::into_de_error)?; + IterReader::new(decoder).decode().map_err(IntoDeError::into_de_error) + } +} + +struct BinVisitor(PhantomData T>); + +impl<'de, T: Decodable> Visitor<'de> for BinVisitor { + type Value = T; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a sequence of bytes") + } + + fn visit_seq>(self, s: S) -> Result { + IterReader::new(SeqIterator(s, Default::default())).decode().map_err(DecodeError::unify) + } +} + +struct SeqIterator<'a, S: serde::de::SeqAccess<'a>>(S, PhantomData<&'a ()>); + +impl<'a, S: serde::de::SeqAccess<'a>> Iterator for SeqIterator<'a, S> { + type Item = Result; + + fn next(&mut self) -> Option { + self.0.next_element::().transpose() + } +} diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index f6337af5..c7910da3 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -116,6 +116,15 @@ use std::io; #[cfg(not(feature = "std"))] use core2::io; +// May depend on crate features and we don't want to bother with it +#[allow(unused)] +#[cfg(feature = "std")] +use std::error::Error as StdError; + +#[allow(unused)] +#[cfg(not(feature = "std"))] +use core2::error::Error as StdError; + pub use crate::address::{Address, AddressType}; pub use crate::amount::{Amount, Denomination, SignedAmount}; pub use crate::blockdata::block::{self, Block};