diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index a5c5bdd58..c51105b93 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -115,6 +115,7 @@ dependencies = [ "bitcoin-io", "bitcoin-units", "bitcoin_hashes", + "hex-conservative", "mutagen", "ordered", "serde", @@ -187,6 +188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1aa273bf451e37ed35ced41c71a5e2a4e29064afb104158f2514bcd71c2c986" dependencies = [ "arrayvec", + "serde", ] [[package]] diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 821d78b31..cd6c50b43 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -114,6 +114,7 @@ dependencies = [ "bitcoin-io", "bitcoin-units", "bitcoin_hashes", + "hex-conservative", "mutagen", "ordered", "serde", @@ -180,6 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1aa273bf451e37ed35ced41c71a5e2a4e29064afb104158f2514bcd71c2c986" dependencies = [ "arrayvec", + "serde", ] [[package]] diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs index 7fad7fdeb..ff3e59c14 100644 --- a/bitcoin/src/bip152.rs +++ b/bitcoin/src/bip152.rs @@ -404,6 +404,7 @@ mod test { use crate::consensus::encode::{deserialize, serialize}; use crate::locktime::absolute; use crate::merkle_tree::TxMerkleNode; + use crate::transaction::OutPointExt; use crate::{ transaction, Amount, CompactTarget, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, Witness, diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 99d1cabd8..665f61e4f 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -18,7 +18,6 @@ use hashes::sha256d; use internals::{compact_size, write_err, ToU64}; use io::{BufRead, Write}; use primitives::Sequence; -use units::parse; use super::Weight; use crate::consensus::{encode, Decodable, Encodable}; @@ -73,146 +72,22 @@ const SEGWIT_MARKER: u8 = 0x00; /// The flag MUST be a 1-byte non-zero value. Currently, 0x01 MUST be used. (BIP-141) const SEGWIT_FLAG: u8 = 0x01; -/// A reference to a transaction output. -/// -/// ### Bitcoin Core References -/// -/// * [COutPoint definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/transaction.h#L26) -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct OutPoint { - /// The referenced transaction's txid. - pub txid: Txid, - /// The index of the referenced output in its transaction's vout. - pub vout: u32, -} -#[cfg(feature = "serde")] -internals::serde_struct_human_string_impl!(OutPoint, "an OutPoint", txid, vout); +crate::internal_macros::define_extension_trait! { + /// Extension functionality for the [`OutPoint`] type. + pub trait OutPointExt impl for OutPoint { + /// Creates a new [`OutPoint`]. + #[inline] + #[deprecated(since = "TBD", note = "use struct initialization syntax instead")] + #[allow(clippy::new-ret-no-self)] + fn new(txid: Txid, vout: u32) -> Self { OutPoint { txid, vout } } -impl OutPoint { - /// The number of bytes that an outpoint contributes to the size of a transaction. - const SIZE: usize = 32 + 4; // The serialized lengths of txid and vout. - - /// The `OutPoint` used in a coinbase prevout. - /// - /// This is used as the dummy input for coinbase transactions because they don't have any - /// previous outputs. In other words, does not point to a real transaction. - pub const COINBASE_PREVOUT: Self = Self { txid: Txid::COINBASE_PREVOUT, vout: u32::MAX }; - - /// Creates a new [`OutPoint`]. - #[inline] - #[deprecated(since = "TBD", note = "use struct initialization syntax instead")] - pub const fn new(txid: Txid, vout: u32) -> OutPoint { OutPoint { txid, vout } } - - /// Creates a "null" `OutPoint`. - #[inline] - #[deprecated(since = "TBD", note = "use OutPoint::COINBASE_PREVOUT instead")] - pub fn null() -> OutPoint { Self::COINBASE_PREVOUT } - - /// Checks if an `OutPoint` is "null". - /// - /// # Examples - /// - /// ```rust - /// use bitcoin::constants::genesis_block; - /// use bitcoin::params; - /// - /// let block = genesis_block(¶ms::MAINNET); - /// let tx = &block.txdata[0]; - /// - /// // Coinbase transactions don't have any previous output. - /// assert!(tx.input[0].previous_output.is_null()); - /// ``` - #[inline] - #[deprecated(since = "TBD", note = "use outpoint == OutPoint::COINBASE_PREVOUT instead")] - pub fn is_null(&self) -> bool { *self == OutPoint::null() } -} - -impl fmt::Display for OutPoint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}:{}", self.txid, self.vout) + /// Checks if an `OutPoint` is "null". + #[inline] + #[deprecated(since = "TBD", note = "use outpoint == OutPoint::COINBASE_PREVOUT instead")] + fn is_null(&self) -> bool { *self == OutPoint::COINBASE_PREVOUT } } } -/// An error in parsing an OutPoint. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub enum ParseOutPointError { - /// Error in TXID part. - Txid(hex::HexToArrayError), - /// Error in vout part. - Vout(parse::ParseIntError), - /// Error in general format. - Format, - /// Size exceeds max. - TooLong, - /// Vout part is not strictly numeric without leading zeroes. - VoutNotCanonical, -} - -internals::impl_from_infallible!(ParseOutPointError); - -impl fmt::Display for ParseOutPointError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ParseOutPointError::*; - - match *self { - Txid(ref e) => write_err!(f, "error parsing TXID"; e), - Vout(ref e) => write_err!(f, "error parsing vout"; e), - Format => write!(f, "OutPoint not in : format"), - TooLong => write!(f, "vout should be at most 10 digits"), - VoutNotCanonical => write!(f, "no leading zeroes or + allowed in vout part"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ParseOutPointError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use ParseOutPointError::*; - - match self { - Txid(e) => Some(e), - Vout(e) => Some(e), - Format | TooLong | VoutNotCanonical => None, - } - } -} - -/// Parses a string-encoded transaction index (vout). -/// -/// Does not permit leading zeroes or non-digit characters. -fn parse_vout(s: &str) -> Result { - if s.len() > 1 { - let first = s.chars().next().unwrap(); - if first == '0' || first == '+' { - return Err(ParseOutPointError::VoutNotCanonical); - } - } - parse::int(s).map_err(ParseOutPointError::Vout) -} - -impl core::str::FromStr for OutPoint { - type Err = ParseOutPointError; - - fn from_str(s: &str) -> Result { - if s.len() > 75 { - // 64 + 1 + 10 - return Err(ParseOutPointError::TooLong); - } - let find = s.find(':'); - if find.is_none() || find != s.rfind(':') { - return Err(ParseOutPointError::Format); - } - let colon = find.unwrap(); - if colon == 0 || colon == s.len() - 1 { - return Err(ParseOutPointError::Format); - } - Ok(OutPoint { - txid: s[..colon].parse().map_err(ParseOutPointError::Txid)?, - vout: parse_vout(&s[colon + 1..])?, - }) - } -} /// Bitcoin transaction input. /// @@ -1402,13 +1277,6 @@ impl InputWeightPrediction { } } -#[cfg(feature = "arbitrary")] -impl<'a> Arbitrary<'a> for OutPoint { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - Ok(OutPoint { txid: Txid::arbitrary(u)?, vout: u32::arbitrary(u)? }) - } -} - #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for TxIn { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { @@ -1440,6 +1308,7 @@ mod tests { use hex::{test_hex_unwrap as hex, FromHex}; #[cfg(feature = "serde")] use internals::serde_round_trip; + use units::parse; use super::*; use crate::consensus::encode::{deserialize, serialize}; diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 6349b9978..5b263b636 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -16,13 +16,14 @@ exclude = ["tests", "contrib"] [features] default = ["std"] -std = ["alloc", "hashes/std", "internals/std", "io/std", "units/std"] -alloc = ["hashes/alloc", "internals/alloc", "io/alloc", "units/alloc"] -serde = ["dep:serde", "hashes/serde", "internals/serde", "units/serde", "alloc"] +std = ["alloc", "hashes/std", "hex/std", "internals/std", "io/std", "units/std"] +alloc = ["hashes/alloc", "hex/alloc", "internals/alloc", "io/alloc", "units/alloc"] +serde = ["dep:serde", "hashes/serde", "hex/serde", "internals/serde", "units/serde", "alloc"] arbitrary = ["dep:arbitrary", "units/arbitrary"] [dependencies] hashes = { package = "bitcoin_hashes", version = "0.14.0", default-features = false, features = ["bitcoin-io"] } +hex = { package = "hex-conservative", version = "0.2.0", default-features = false } internals = { package = "bitcoin-internals", version = "0.4.0" } io = { package = "bitcoin-io", version = "0.1.1", default-features = false } units = { package = "bitcoin-units", version = "0.1.0", default-features = false } diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs index cd6897dac..ee54ac297 100644 --- a/primitives/src/transaction.rs +++ b/primitives/src/transaction.rs @@ -15,6 +15,127 @@ use core::fmt; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; use hashes::sha256d; +use internals::write_err; +#[cfg(feature = "alloc")] +use units::parse; + +/// A reference to a transaction output. +/// +/// ### Bitcoin Core References +/// +/// * [COutPoint definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/transaction.h#L26) +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct OutPoint { + /// The referenced transaction's txid. + pub txid: Txid, + /// The index of the referenced output in its transaction's vout. + pub vout: u32, +} +#[cfg(feature = "serde")] +internals::serde_struct_human_string_impl!(OutPoint, "an OutPoint", txid, vout); + +impl OutPoint { + /// The number of bytes that an outpoint contributes to the size of a transaction. + pub const SIZE: usize = 32 + 4; // The serialized lengths of txid and vout. + + /// The `OutPoint` used in a coinbase prevout. + /// + /// This is used as the dummy input for coinbase transactions because they don't have any + /// previous outputs. In other words, does not point to a real transaction. + pub const COINBASE_PREVOUT: Self = Self { txid: Txid::COINBASE_PREVOUT, vout: u32::MAX }; +} + +impl fmt::Display for OutPoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}", self.txid, self.vout) + } +} + +#[cfg(feature = "alloc")] +impl core::str::FromStr for OutPoint { + type Err = ParseOutPointError; + + fn from_str(s: &str) -> Result { + if s.len() > 75 { + // 64 + 1 + 10 + return Err(ParseOutPointError::TooLong); + } + let find = s.find(':'); + if find.is_none() || find != s.rfind(':') { + return Err(ParseOutPointError::Format); + } + let colon = find.unwrap(); + if colon == 0 || colon == s.len() - 1 { + return Err(ParseOutPointError::Format); + } + Ok(OutPoint { + txid: s[..colon].parse().map_err(ParseOutPointError::Txid)?, + vout: parse_vout(&s[colon + 1..])?, + }) + } +} + +/// Parses a string-encoded transaction index (vout). +/// +/// Does not permit leading zeroes or non-digit characters. +#[cfg(feature = "alloc")] +fn parse_vout(s: &str) -> Result { + if s.len() > 1 { + let first = s.chars().next().unwrap(); + if first == '0' || first == '+' { + return Err(ParseOutPointError::VoutNotCanonical); + } + } + parse::int(s).map_err(ParseOutPointError::Vout) +} + +/// An error in parsing an [`OutPoint`]. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +#[cfg(feature = "alloc")] +pub enum ParseOutPointError { + /// Error in TXID part. + Txid(hex::HexToArrayError), + /// Error in vout part. + Vout(parse::ParseIntError), + /// Error in general format. + Format, + /// Size exceeds max. + TooLong, + /// Vout part is not strictly numeric without leading zeroes. + VoutNotCanonical, +} + +#[cfg(feature = "alloc")] +internals::impl_from_infallible!(ParseOutPointError); + +#[cfg(feature = "alloc")] +impl fmt::Display for ParseOutPointError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ParseOutPointError::*; + + match *self { + Txid(ref e) => write_err!(f, "error parsing TXID"; e), + Vout(ref e) => write_err!(f, "error parsing vout"; e), + Format => write!(f, "OutPoint not in : format"), + TooLong => write!(f, "vout should be at most 10 digits"), + VoutNotCanonical => write!(f, "no leading zeroes or + allowed in vout part"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseOutPointError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ParseOutPointError::*; + + match self { + Txid(e) => Some(e), + Vout(e) => Some(e), + Format | TooLong | VoutNotCanonical => None, + } + } +} hashes::hash_newtype! { /// A bitcoin transaction hash/transaction ID. @@ -35,7 +156,7 @@ impl Txid { /// The `Txid` used in a coinbase prevout. /// /// This is used as the "txid" of the dummy input of a coinbase transaction. This is not a real - /// TXID and should not be used in any other contexts. See `OutPoint::COINBASE_PREVOUT`. + /// TXID and should not be used in any other contexts. See [`OutPoint::COINBASE_PREVOUT`]. pub const COINBASE_PREVOUT: Self = Self::from_byte_array([0; 32]); } @@ -73,6 +194,16 @@ impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for OutPoint { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + Ok(OutPoint{ + txid: Txid::arbitrary(u)?, + vout: u32::arbitrary(u)? + }) + } +} + #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for Version { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result {