Move OutPoint to primitives

Move the `OutPoint` type and associated code over to `primitives`. Take
the opportunity to re-order the code so the file reads like a story,
things are implemented below where they are called.
This commit is contained in:
Tobin C. Harding 2024-09-19 14:23:11 +10:00
parent f5c46cd411
commit b079cbafee
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
5 changed files with 140 additions and 123 deletions

View File

@ -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]]

View File

@ -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]]

View File

@ -18,7 +18,6 @@ use hashes::sha256d;
use internals::{write_err, ToU64 as _};
use io::{BufRead, Write};
use primitives::Sequence;
use units::parse;
use super::Weight;
use crate::consensus::{encode, Decodable, Encodable};
@ -73,32 +72,6 @@ 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);
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 };
}
crate::internal_macros::define_extension_trait! {
/// Extension functionality for the [`OutPoint`] type.
pub trait OutPointExt impl for OutPoint {
@ -115,92 +88,6 @@ crate::internal_macros::define_extension_trait! {
}
}
impl fmt::Display for OutPoint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.txid, self.vout)
}
}
/// 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 <txid>:<vout> 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<u32, ParseOutPointError> {
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<Self, Self::Err> {
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.
///
@ -1390,13 +1277,6 @@ impl InputWeightPrediction {
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for OutPoint {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
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<Self> {
@ -1428,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};

View File

@ -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 }

View File

@ -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<Self, Self::Err> {
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<u32, ParseOutPointError> {
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 <txid>:<vout> 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.
@ -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<Self> {
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<Self> {