Merge pull request #177 from stevenroose/outpoint-fromstr
Implement FromStr for OutPoint
This commit is contained in:
commit
0764673c38
|
@ -28,7 +28,7 @@ use std::default::Default;
|
|||
use std::fmt;
|
||||
#[cfg(feature="bitcoinconsensus")] use std::collections::HashMap;
|
||||
|
||||
use util::hash::{BitcoinHash, Sha256dHash};
|
||||
use util::hash::{BitcoinHash, Sha256dHash, HexError};
|
||||
#[cfg(feature="bitcoinconsensus")] use blockdata::script;
|
||||
use blockdata::script::Script;
|
||||
use consensus::encode::{self, serialize, Encoder, Decoder};
|
||||
|
@ -89,6 +89,87 @@ impl fmt::Display for OutPoint {
|
|||
}
|
||||
}
|
||||
|
||||
/// An error in parsing an OutPoint.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ParseOutPointError {
|
||||
/// Error in TXID part.
|
||||
Txid(HexError),
|
||||
/// Error in vout part.
|
||||
Vout(::std::num::ParseIntError),
|
||||
/// Error in general format.
|
||||
Format,
|
||||
/// Size exceeds max.
|
||||
TooLong,
|
||||
/// Vout part is not strictly numeric without leading zeroes.
|
||||
VoutNotCanonical,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseOutPointError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ParseOutPointError::Txid(ref e) => write!(f, "error parsing TXID: {}", e),
|
||||
ParseOutPointError::Vout(ref e) => write!(f, "error parsing vout: {}", e),
|
||||
ParseOutPointError::Format => write!(f, "OutPoint not in <txid>:<vout> format"),
|
||||
ParseOutPointError::TooLong => write!(f, "vout should be at most 10 digits"),
|
||||
ParseOutPointError::VoutNotCanonical => write!(f, "no leading zeroes or + allowed in vout part"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::error::Error for ParseOutPointError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
ParseOutPointError::Txid(_) => "TXID parse error",
|
||||
ParseOutPointError::Vout(_) => "vout parse error",
|
||||
ParseOutPointError::Format => "outpoint format error",
|
||||
ParseOutPointError::TooLong => "size error",
|
||||
ParseOutPointError::VoutNotCanonical => "vout canonical error",
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&::std::error::Error> {
|
||||
match *self {
|
||||
ParseOutPointError::Txid(ref e) => Some(e),
|
||||
ParseOutPointError::Vout(ref e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a string-encoded transaction index (vout).
|
||||
/// It 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().nth(0).unwrap();
|
||||
if first == '0' || first == '+' {
|
||||
return Err(ParseOutPointError::VoutNotCanonical);
|
||||
}
|
||||
}
|
||||
Ok(s.parse().map_err(ParseOutPointError::Vout)?)
|
||||
}
|
||||
|
||||
impl ::std::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 == 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: Sha256dHash::from_hex(&s[..colon]).map_err(ParseOutPointError::Txid)?,
|
||||
vout: parse_vout(&s[colon+1..])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A transaction input, which defines old coins to be consumed
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct TxIn {
|
||||
|
@ -492,8 +573,9 @@ mod tests {
|
|||
#[cfg(all(feature = "serde", feature = "strason"))]
|
||||
use strason::Json;
|
||||
|
||||
use super::{Transaction, TxIn};
|
||||
use super::{OutPoint, ParseOutPointError, Transaction, TxIn};
|
||||
|
||||
use std::str::FromStr;
|
||||
use blockdata::script::Script;
|
||||
#[cfg(all(feature = "serde", feature = "strason"))]
|
||||
use consensus::encode::serialize;
|
||||
|
@ -501,6 +583,39 @@ mod tests {
|
|||
use util::hash::{BitcoinHash, Sha256dHash};
|
||||
use util::misc::hex_bytes;
|
||||
|
||||
#[test]
|
||||
fn test_outpoint() {
|
||||
assert_eq!(OutPoint::from_str("i don't care"),
|
||||
Err(ParseOutPointError::Format));
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:1:1"),
|
||||
Err(ParseOutPointError::Format));
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:"),
|
||||
Err(ParseOutPointError::Format));
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:11111111111"),
|
||||
Err(ParseOutPointError::TooLong));
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:01"),
|
||||
Err(ParseOutPointError::VoutNotCanonical));
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:+42"),
|
||||
Err(ParseOutPointError::VoutNotCanonical));
|
||||
assert_eq!(OutPoint::from_str("i don't care:1"),
|
||||
Err(ParseOutPointError::Txid(Sha256dHash::from_hex("i don't care").unwrap_err())));
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c945X:1"),
|
||||
Err(ParseOutPointError::Txid(Sha256dHash::from_hex("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c945X").unwrap_err())));
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:lol"),
|
||||
Err(ParseOutPointError::Vout(u32::from_str("lol").unwrap_err())));
|
||||
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:42"),
|
||||
Ok(OutPoint{
|
||||
txid: Sha256dHash::from_hex("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456").unwrap(),
|
||||
vout: 42,
|
||||
}));
|
||||
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0"),
|
||||
Ok(OutPoint{
|
||||
txid: Sha256dHash::from_hex("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456").unwrap(),
|
||||
vout: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_txin() {
|
||||
let txin: Result<TxIn, _> = deserialize(&hex_bytes("a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff").unwrap());
|
||||
|
|
Loading…
Reference in New Issue