From 32631e44addcf1669ecf98f51a6a03fd37b87ba2 Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Mon, 20 Aug 2018 11:58:12 -0400 Subject: [PATCH] Rename `TxOutRef` to `OutPoint` and use it in `TxIn`. Previously this structure was unused, it's now being used by the `TxIn` structure to simplify the code a little bit and avoid confusions. Also the rust-lightning source code has an `OutPoint` similar to this one but with the `vout` index as an `u16` to avoid unsafe conversions. I've added to new methods to `OutPoint`: - `null`: Creates a new "null" `OutPoint`. - `is_null`: Checks if the given `OutPoint` is null. Signed-off-by: Jean Pierre Dudey --- src/blockdata/constants.rs | 9 ++- src/blockdata/transaction.rs | 106 +++++++++++++++++++++++++---------- src/util/bip143.rs | 9 +-- 3 files changed, 83 insertions(+), 41 deletions(-) diff --git a/src/blockdata/constants.rs b/src/blockdata/constants.rs index c4bd3c2f..408af931 100644 --- a/src/blockdata/constants.rs +++ b/src/blockdata/constants.rs @@ -23,7 +23,7 @@ use std::default::Default; use blockdata::opcodes; use blockdata::script; -use blockdata::transaction::{Transaction, TxOut, TxIn}; +use blockdata::transaction::{OutPoint, Transaction, TxOut, TxIn}; use blockdata::block::{Block, BlockHeader}; use network::constants::Network; use util::misc::hex_bytes; @@ -69,8 +69,7 @@ fn bitcoin_genesis_tx() -> Transaction { .push_slice(b"The Times 03/Jan/2009 Chancellor on brink of second bailout for banks") .into_script(); ret.input.push(TxIn { - prev_hash: Default::default(), - prev_index: 0xFFFFFFFF, + previous_output: OutPoint::null(), script_sig: in_script, sequence: MAX_SEQUENCE, witness: vec![], @@ -154,8 +153,8 @@ mod test { assert_eq!(gen.version, 1); assert_eq!(gen.input.len(), 1); - assert_eq!(gen.input[0].prev_hash, Default::default()); - assert_eq!(gen.input[0].prev_index, 0xFFFFFFFF); + assert_eq!(gen.input[0].previous_output.txid, Default::default()); + assert_eq!(gen.input[0].previous_output.vout, 0xFFFFFFFF); assert_eq!(serialize(&gen.input[0].script_sig).ok(), Some(hex_decode("4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73").unwrap())); diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index f5d62608..fa85c407 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -36,28 +36,65 @@ use network::serialize::{serialize, BitcoinHash, SimpleEncoder, SimpleDecoder}; use network::encodable::{ConsensusEncodable, ConsensusDecodable, VarInt}; /// A reference to a transaction output -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct TxOutRef { +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct OutPoint { /// The referenced transaction's txid pub txid: Sha256dHash, /// The index of the referenced output in its transaction's vout - pub index: usize + pub vout: u32, } -serde_struct_impl!(TxOutRef, txid, index); +serde_struct_impl!(OutPoint, txid, vout); -impl fmt::Display for TxOutRef { +impl OutPoint { + /// Creates a "null" `OutPoint`. + /// + /// This value is used for coinbase transactions because they don't have + /// any previous outputs. + #[inline] + pub fn null() -> OutPoint { + OutPoint { + txid: Default::default(), + vout: u32::max_value(), + } + } + + /// Checks if an `OutPoint` is "null". + /// + /// # Examples + /// + /// ```rust + /// use bitcoin::blockdata::constants::genesis_block; + /// use bitcoin::network::constants::Network; + /// + /// let block = genesis_block(Network::Bitcoin); + /// let tx = &block.txdata[0]; + /// + /// // Coinbase transactions don't have any previous output. + /// assert_eq!(tx.input[0].previous_output.is_null(), true); + /// ``` + #[inline] + pub fn is_null(&self) -> bool { + *self == OutPoint::null() + } +} + +impl Default for OutPoint { + fn default() -> Self { + OutPoint::null() + } +} + +impl fmt::Display for OutPoint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}:{}", self.txid, self.index) + write!(f, "{}:{}", self.txid, self.vout) } } /// A transaction input, which defines old coins to be consumed #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct TxIn { - /// The hash of the transaction whose output is being used an an input - pub prev_hash: Sha256dHash, - /// The index of the output in the previous transaction, which may have several - pub prev_index: u32, + /// The reference to the previous output that is being used an an input + pub previous_output: OutPoint, /// The script which pushes values on the stack which will cause /// the referenced output's script to accept pub script_sig: Script, @@ -73,7 +110,7 @@ pub struct TxIn { /// routines. pub witness: Vec> } -serde_struct_impl!(TxIn, prev_hash, prev_index, script_sig, sequence, witness); +serde_struct_impl!(TxIn, previous_output, script_sig, sequence, witness); /// A transaction output, which defines new coins to be created from old ones. #[derive(Clone, PartialEq, Eq, Debug, Hash)] @@ -172,9 +209,8 @@ impl Transaction { // Add all inputs necessary.. if anyone_can_pay { tx.input = vec![TxIn { - prev_hash: self.input[input_index].prev_hash, + previous_output: self.input[input_index].previous_output, script_sig: script_pubkey.clone(), - prev_index: self.input[input_index].prev_index, sequence: self.input[input_index].sequence, witness: vec![], }]; @@ -182,8 +218,7 @@ impl Transaction { tx.input = Vec::with_capacity(self.input.len()); for (n, input) in self.input.iter().enumerate() { tx.input.push(TxIn { - prev_hash: input.prev_hash, - prev_index: input.prev_index, + previous_output: input.previous_output, script_sig: if n == input_index { script_pubkey.clone() } else { Script::new() }, sequence: if n != input_index && (sighash == SigHashType::Single || sighash == SigHashType::None) { 0 } else { input.sequence }, witness: vec![], @@ -253,17 +288,17 @@ impl Transaction { #[cfg(feature="bitcoinconsensus")] /// Verify that this transaction is able to spend some outputs of spent transactions - pub fn verify (&self, spent : &HashMap) -> Result<(), script::Error> { + pub fn verify(&self, spent: &HashMap) -> Result<(), script::Error> { if let Ok(tx) = serialize(&*self) { for (idx, input) in self.input.iter().enumerate() { - if let Some(ref s) = spent.get(&input.prev_hash) { - if let Some(ref output) = s.output.get(input.prev_index as usize) { + if let Some(ref s) = spent.get(&input.previous_output.txid) { + if let Some(ref output) = s.output.get(input.previous_output.vout as usize) { output.script_pubkey.verify(idx, output.value, tx.as_slice())?; } else { - return Err(script::Error::WrongSpentOutputIndex(input.prev_index as usize)); + return Err(script::Error::WrongSpentOutputIndex(input.previous_output.vout as usize)); } } else { - return Err(script::Error::UnknownSpentTransaction(input.prev_hash)); + return Err(script::Error::UnknownSpentTransaction(input.previous_output.txid)); } } Ok(()) @@ -273,9 +308,9 @@ impl Transaction { } } - /// is this a coin base transaction? - pub fn is_coin_base (&self) -> bool { - self.input.len() == 1 && self.input[0].prev_index == 0xFFFFFFFF && self.input [0].prev_hash == Sha256dHash::default() + /// Is this a coin base transaction? + pub fn is_coin_base(&self) -> bool { + self.input.len() == 1 && self.input[0].previous_output.is_null() } } @@ -291,10 +326,24 @@ impl BitcoinHash for Transaction { impl_consensus_encoding!(TxOut, value, script_pubkey); +impl ConsensusEncodable for OutPoint { + fn consensus_encode(&self, s: &mut S) -> Result <(), S::Error> { + self.txid.consensus_encode(s)?; + self.vout.consensus_encode(s) + } +} +impl ConsensusDecodable for OutPoint { + fn consensus_decode(d: &mut D) -> Result { + Ok(OutPoint { + txid: ConsensusDecodable::consensus_decode(d)?, + vout: ConsensusDecodable::consensus_decode(d)?, + }) + } +} + impl ConsensusEncodable for TxIn { fn consensus_encode(&self, s: &mut S) -> Result <(), S::Error> { - self.prev_hash.consensus_encode(s)?; - self.prev_index.consensus_encode(s)?; + self.previous_output.consensus_encode(s)?; self.script_sig.consensus_encode(s)?; self.sequence.consensus_encode(s) } @@ -302,8 +351,7 @@ impl ConsensusEncodable for TxIn { impl ConsensusDecodable for TxIn { fn consensus_decode(d: &mut D) -> Result { Ok(TxIn { - prev_hash: ConsensusDecodable::consensus_decode(d)?, - prev_index: ConsensusDecodable::consensus_decode(d)?, + previous_output: ConsensusDecodable::consensus_decode(d)?, script_sig: ConsensusDecodable::consensus_decode(d)?, sequence: ConsensusDecodable::consensus_decode(d)?, witness: vec![], @@ -482,9 +530,9 @@ mod tests { assert_eq!(realtx.input.len(), 1); // In particular this one is easy to get backward -- in bitcoin hashes are encoded // as little-endian 256-bit numbers rather than as data strings. - assert_eq!(realtx.input[0].prev_hash.be_hex_string(), + assert_eq!(realtx.input[0].previous_output.txid.be_hex_string(), "ce9ea9f6f5e422c6a9dbcddb3b9a14d1c78fab9ab520cb281aa2a74a09575da1".to_string()); - assert_eq!(realtx.input[0].prev_index, 1); + assert_eq!(realtx.input[0].previous_output.vout, 1); assert_eq!(realtx.output.len(), 1); assert_eq!(realtx.lock_time, 0); diff --git a/src/util/bip143.rs b/src/util/bip143.rs index 03ae7f36..4b199d29 100644 --- a/src/util/bip143.rs +++ b/src/util/bip143.rs @@ -47,8 +47,7 @@ impl SighashComponents { let hash_prevouts = { let mut enc = Sha256dEncoder::new(); for txin in &tx.input { - txin.prev_hash.consensus_encode(&mut enc).unwrap(); - txin.prev_index.consensus_encode(&mut enc).unwrap(); + txin.previous_output.consensus_encode(&mut enc).unwrap(); } enc.into_hash() }; @@ -86,11 +85,7 @@ impl SighashComponents { self.hash_prevouts.consensus_encode(&mut enc).unwrap(); self.hash_sequence.consensus_encode(&mut enc).unwrap(); txin - .prev_hash - .consensus_encode(&mut enc) - .unwrap(); - txin - .prev_index + .previous_output .consensus_encode(&mut enc) .unwrap(); witness_script.consensus_encode(&mut enc).unwrap();