use std::default::Default;
use std::fmt;
use serde;
use util::hash::Sha256dHash;
use blockdata::script::{self, Script, ScriptTrace};
use network::serialize::BitcoinHash;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct TxOutRef {
pub txid: Sha256dHash,
pub index: usize
}
serde_struct_impl!(TxOutRef, txid, index);
impl fmt::Display for TxOutRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.txid, self.index)
}
}
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct TxIn {
pub prev_hash: Sha256dHash,
pub prev_index: u32,
pub script_sig: Script,
pub sequence: u32,
}
serde_struct_impl!(TxIn, prev_hash, prev_index, script_sig, sequence);
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct TxOut {
pub value: u64,
pub script_pubkey: Script
}
serde_struct_impl!(TxOut, value, script_pubkey);
impl Default for TxOut {
fn default() -> TxOut {
TxOut { value: 0xffffffffffffffff, script_pubkey: Script::new() }
}
}
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct Transaction {
pub version: u32,
pub lock_time: u32,
pub input: Vec<TxIn>,
pub output: Vec<TxOut>
}
serde_struct_impl!(Transaction, version, lock_time, input, output);
impl Transaction {
pub fn ntxid(&self) -> Sha256dHash {
let cloned_tx = Transaction {
version: self.version,
lock_time: self.lock_time,
input: self.input.iter().map(|txin| TxIn { script_sig: Script::new(), .. *txin }).collect(),
output: self.output.clone()
};
cloned_tx.bitcoin_hash()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Error {
InputScriptFailure(script::Error),
OutputScriptFailure(script::Error),
P2shScriptFailure(script::Error),
P2shScriptReturnedFalse,
P2shScriptReturnedEmptyStack,
ScriptReturnedFalse,
ScriptReturnedEmptyStack,
InputNotFound(Sha256dHash, u32),
}
display_from_debug!(Error);
impl serde::Serialize for Error {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: serde::Serializer,
{
serializer.visit_str(&self.to_string())
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct InputTrace {
input_txid: Sha256dHash,
input_vout: usize,
sig_trace: ScriptTrace,
pubkey_trace: Option<ScriptTrace>,
p2sh_trace: Option<ScriptTrace>,
error: Option<Error>
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct TransactionTrace {
txid: Sha256dHash,
inputs: Vec<InputTrace>
}
impl BitcoinHash for Transaction {
fn bitcoin_hash(&self) -> Sha256dHash {
use network::serialize::serialize;
Sha256dHash::from_data(&serialize(self).unwrap())
}
}
impl_consensus_encoding!(TxIn, prev_hash, prev_index, script_sig, sequence);
impl_consensus_encoding!(TxOut, value, script_pubkey);
impl_consensus_encoding!(Transaction, version, input, output, lock_time);
#[cfg(test)]
mod tests {
use strason;
use super::{Transaction, TxIn};
use blockdata::script::Script;
use network::serialize::BitcoinHash;
use network::serialize::deserialize;
use util::misc::hex_bytes;
#[test]
fn test_txin() {
let txin: Result<TxIn, _> = deserialize(&hex_bytes("a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff").unwrap());
assert!(txin.is_ok());
}
#[test]
fn test_transaction() {
let hex_tx = hex_bytes("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
let tx: Result<Transaction, _> = deserialize(&hex_tx);
assert!(tx.is_ok());
let realtx = tx.unwrap();
assert_eq!(realtx.version, 1);
assert_eq!(realtx.input.len(), 1);
assert_eq!(realtx.input[0].prev_hash.be_hex_string(),
"ce9ea9f6f5e422c6a9dbcddb3b9a14d1c78fab9ab520cb281aa2a74a09575da1".to_string());
assert_eq!(realtx.input[0].prev_index, 1);
assert_eq!(realtx.output.len(), 1);
assert_eq!(realtx.lock_time, 0);
assert_eq!(realtx.bitcoin_hash().be_hex_string(),
"a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string());
}
#[test]
fn test_ntxid() {
let hex_tx = hex_bytes("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
let mut tx: Transaction = deserialize(&hex_tx).unwrap();
let old_ntxid = tx.ntxid();
assert_eq!(old_ntxid.be_hex_string(), "c3573dbea28ce24425c59a189391937e00d255150fa973d59d61caf3a06b601d");
tx.input[0].script_sig = Script::new();
assert_eq!(old_ntxid, tx.ntxid());
tx.output[0].script_pubkey = Script::new();
assert!(old_ntxid != tx.ntxid());
}
#[test]
fn test_txn_encode_decode() {
let hex_tx = hex_bytes("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
let tx: Transaction = deserialize(&hex_tx).unwrap();
let encoded = strason::from_serialize(&tx).unwrap();
let decoded = encoded.into_deserialize().unwrap();
assert_eq!(tx, decoded);
}
}