487 lines
17 KiB
Rust
487 lines
17 KiB
Rust
use super::bytes::Bytes;
|
|
use super::hash::H256;
|
|
use super::script::ScriptType;
|
|
use serde::ser::SerializeMap;
|
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
use std::fmt;
|
|
use v1::types;
|
|
use zebra_keys::Address;
|
|
|
|
/// Hex-encoded transaction
|
|
pub type RawTransaction = Bytes;
|
|
|
|
/// Transaction input
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub struct TransactionInput {
|
|
/// Previous transaction id
|
|
pub txid: H256,
|
|
/// Previous transaction output index
|
|
pub vout: u32,
|
|
/// Sequence number
|
|
pub sequence: Option<u32>,
|
|
}
|
|
|
|
/// Transaction output of form "address": amount
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct TransactionOutputWithAddress {
|
|
/// Receiver' address
|
|
pub address: Address,
|
|
/// Amount in BTC
|
|
pub amount: f64,
|
|
}
|
|
|
|
/// Transaction output of form "data": serialized(output script data)
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct TransactionOutputWithScriptData {
|
|
/// Serialized script data
|
|
pub script_data: Bytes,
|
|
}
|
|
|
|
/// Transaction output
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum TransactionOutput {
|
|
/// Of form address: amount
|
|
Address(TransactionOutputWithAddress),
|
|
/// Of form data: script_data_bytes
|
|
ScriptData(TransactionOutputWithScriptData),
|
|
}
|
|
|
|
/// Transaction outputs, which serializes/deserializes as KV-map
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct TransactionOutputs {
|
|
/// Transaction outputs
|
|
pub outputs: Vec<TransactionOutput>,
|
|
}
|
|
|
|
/// Transaction input script
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub struct TransactionInputScript {
|
|
/// Script code
|
|
pub asm: String,
|
|
/// Script hex
|
|
pub hex: Bytes,
|
|
}
|
|
|
|
/// Transaction output script
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub struct TransactionOutputScript {
|
|
/// Script code
|
|
pub asm: String,
|
|
/// Script hex
|
|
pub hex: Bytes,
|
|
/// Number of required signatures
|
|
#[serde(rename = "reqSigs")]
|
|
pub req_sigs: u32,
|
|
/// Type of script
|
|
#[serde(rename = "type")]
|
|
pub script_type: ScriptType,
|
|
/// Array of bitcoin addresses
|
|
#[serde(with = "types::address::vec")]
|
|
pub addresses: Vec<Address>,
|
|
}
|
|
|
|
/// Signed transaction input
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub struct SignedTransactionInput {
|
|
/// Previous transaction id
|
|
pub txid: H256,
|
|
/// Previous transaction output index
|
|
pub vout: u32,
|
|
/// Input script
|
|
pub script_sig: TransactionInputScript,
|
|
/// Sequence number
|
|
pub sequence: u32,
|
|
}
|
|
|
|
/// Signed transaction output
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub struct SignedTransactionOutput {
|
|
/// Output value in BTC
|
|
pub value: f64,
|
|
/// Output index
|
|
pub n: u32,
|
|
/// Output script
|
|
#[serde(rename = "scriptPubKey")]
|
|
pub script: TransactionOutputScript,
|
|
}
|
|
|
|
/// Transaction
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
pub struct Transaction {
|
|
/// Raw transaction
|
|
pub hex: RawTransaction,
|
|
/// The transaction id (same as provided)
|
|
pub txid: H256,
|
|
/// The transaction hash
|
|
pub hash: H256,
|
|
/// The serialized transaction size
|
|
pub size: usize,
|
|
/// The version
|
|
pub version: i32,
|
|
/// The lock time
|
|
pub locktime: i32,
|
|
/// Transaction inputs
|
|
pub vin: Vec<SignedTransactionInput>,
|
|
/// Transaction outputs
|
|
pub vout: Vec<SignedTransactionOutput>,
|
|
/// Hash of the block this transaction is included in
|
|
pub blockhash: H256,
|
|
/// Number of confirmations of this transaction
|
|
pub confirmations: u32,
|
|
/// The transaction time in seconds since epoch (Jan 1 1970 GMT)
|
|
pub time: u32,
|
|
/// The block time in seconds since epoch (Jan 1 1970 GMT)
|
|
pub blocktime: u32,
|
|
}
|
|
|
|
/// Return value of `getrawtransaction` method
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum GetRawTransactionResponse {
|
|
/// Return value when asking for raw transaction
|
|
Raw(RawTransaction),
|
|
/// Return value when asking for verbose transaction
|
|
Verbose(Transaction),
|
|
}
|
|
|
|
impl Serialize for GetRawTransactionResponse {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match *self {
|
|
GetRawTransactionResponse::Raw(ref raw_transaction) => {
|
|
raw_transaction.serialize(serializer)
|
|
}
|
|
GetRawTransactionResponse::Verbose(ref verbose_transaction) => {
|
|
verbose_transaction.serialize(serializer)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TransactionOutputs {
|
|
pub fn len(&self) -> usize {
|
|
self.outputs.len()
|
|
}
|
|
}
|
|
|
|
impl Serialize for TransactionOutputs {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let mut state = serializer.serialize_map(Some(self.len()))?;
|
|
for output in &self.outputs {
|
|
match output {
|
|
&TransactionOutput::Address(ref address_output) => {
|
|
state.serialize_entry(
|
|
&address_output.address.to_string(),
|
|
&address_output.amount,
|
|
)?;
|
|
}
|
|
&TransactionOutput::ScriptData(ref script_output) => {
|
|
state.serialize_entry("data", &script_output.script_data)?;
|
|
}
|
|
}
|
|
}
|
|
state.end()
|
|
}
|
|
}
|
|
|
|
impl<'a> Deserialize<'a> for TransactionOutputs {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'a>,
|
|
{
|
|
use serde::de::{MapAccess, Visitor};
|
|
|
|
struct TransactionOutputsVisitor;
|
|
|
|
impl<'b> Visitor<'b> for TransactionOutputsVisitor {
|
|
type Value = TransactionOutputs;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("a transaction output object")
|
|
}
|
|
|
|
fn visit_map<V>(self, mut visitor: V) -> Result<TransactionOutputs, V::Error>
|
|
where
|
|
V: MapAccess<'b>,
|
|
{
|
|
let mut outputs: Vec<TransactionOutput> =
|
|
Vec::with_capacity(visitor.size_hint().unwrap_or(0));
|
|
|
|
while let Some(key) = try!(visitor.next_key::<String>()) {
|
|
if &key == "data" {
|
|
let value: Bytes = try!(visitor.next_value());
|
|
outputs.push(TransactionOutput::ScriptData(
|
|
TransactionOutputWithScriptData { script_data: value },
|
|
));
|
|
} else {
|
|
let address = types::address::AddressVisitor::default().visit_str(&key)?;
|
|
let amount: f64 = try!(visitor.next_value());
|
|
outputs.push(TransactionOutput::Address(TransactionOutputWithAddress {
|
|
address: address,
|
|
amount: amount,
|
|
}));
|
|
}
|
|
}
|
|
|
|
Ok(TransactionOutputs { outputs: outputs })
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_any(TransactionOutputsVisitor)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::super::bytes::Bytes;
|
|
use super::super::hash::H256;
|
|
use super::super::script::ScriptType;
|
|
use super::*;
|
|
use serde_json;
|
|
|
|
#[test]
|
|
fn transaction_input_serialize() {
|
|
let txinput = TransactionInput {
|
|
txid: H256::from(7),
|
|
vout: 33,
|
|
sequence: Some(88),
|
|
};
|
|
assert_eq!(serde_json::to_string(&txinput).unwrap(), r#"{"txid":"0700000000000000000000000000000000000000000000000000000000000000","vout":33,"sequence":88}"#);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_input_deserialize() {
|
|
let txinput = TransactionInput {
|
|
txid: H256::from(7),
|
|
vout: 33,
|
|
sequence: Some(88),
|
|
};
|
|
|
|
assert_eq!(
|
|
serde_json::from_str::<TransactionInput>(r#"{"txid":"0700000000000000000000000000000000000000000000000000000000000000","vout":33,"sequence":88}"#).unwrap(),
|
|
txinput);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_outputs_serialize() {
|
|
let txout = TransactionOutputs {
|
|
outputs: vec![
|
|
TransactionOutput::Address(TransactionOutputWithAddress {
|
|
address: "t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi".into(),
|
|
amount: 123.45,
|
|
}),
|
|
TransactionOutput::Address(TransactionOutputWithAddress {
|
|
address: "t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543".into(),
|
|
amount: 67.89,
|
|
}),
|
|
TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
|
script_data: Bytes::new(vec![1, 2, 3, 4]),
|
|
}),
|
|
TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
|
script_data: Bytes::new(vec![5, 6, 7, 8]),
|
|
}),
|
|
],
|
|
};
|
|
assert_eq!(serde_json::to_string(&txout).unwrap(), r#"{"t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi":123.45,"t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543":67.89,"data":"01020304","data":"05060708"}"#);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_outputs_deserialize() {
|
|
let txout = TransactionOutputs {
|
|
outputs: vec![
|
|
TransactionOutput::Address(TransactionOutputWithAddress {
|
|
address: "t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi".into(),
|
|
amount: 123.45,
|
|
}),
|
|
TransactionOutput::Address(TransactionOutputWithAddress {
|
|
address: "t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543".into(),
|
|
amount: 67.89,
|
|
}),
|
|
TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
|
script_data: Bytes::new(vec![1, 2, 3, 4]),
|
|
}),
|
|
TransactionOutput::ScriptData(TransactionOutputWithScriptData {
|
|
script_data: Bytes::new(vec![5, 6, 7, 8]),
|
|
}),
|
|
],
|
|
};
|
|
assert_eq!(
|
|
serde_json::from_str::<TransactionOutputs>(r#"{"t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi":123.45,"t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543":67.89,"data":"01020304","data":"05060708"}"#).unwrap(),
|
|
txout);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_input_script_serialize() {
|
|
let txin = TransactionInputScript {
|
|
asm: "Hello, world!!!".to_owned(),
|
|
hex: Bytes::new(vec![1, 2, 3, 4]),
|
|
};
|
|
assert_eq!(
|
|
serde_json::to_string(&txin).unwrap(),
|
|
r#"{"asm":"Hello, world!!!","hex":"01020304"}"#
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_input_script_deserialize() {
|
|
let txin = TransactionInputScript {
|
|
asm: "Hello, world!!!".to_owned(),
|
|
hex: Bytes::new(vec![1, 2, 3, 4]),
|
|
};
|
|
assert_eq!(
|
|
serde_json::from_str::<TransactionInputScript>(
|
|
r#"{"asm":"Hello, world!!!","hex":"01020304"}"#
|
|
)
|
|
.unwrap(),
|
|
txin
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_output_script_serialize() {
|
|
let txout = TransactionOutputScript {
|
|
asm: "Hello, world!!!".to_owned(),
|
|
hex: Bytes::new(vec![1, 2, 3, 4]),
|
|
req_sigs: 777,
|
|
script_type: ScriptType::Multisig,
|
|
addresses: vec![
|
|
"t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi".into(),
|
|
"t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543".into(),
|
|
],
|
|
};
|
|
assert_eq!(serde_json::to_string(&txout).unwrap(), r#"{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi","t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543"]}"#);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_output_script_deserialize() {
|
|
let txout = TransactionOutputScript {
|
|
asm: "Hello, world!!!".to_owned(),
|
|
hex: Bytes::new(vec![1, 2, 3, 4]),
|
|
req_sigs: 777,
|
|
script_type: ScriptType::Multisig,
|
|
addresses: vec![
|
|
"t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi".into(),
|
|
"t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543".into(),
|
|
],
|
|
};
|
|
|
|
assert_eq!(
|
|
serde_json::from_str::<TransactionOutputScript>(r#"{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi","t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543"]}"#).unwrap(),
|
|
txout);
|
|
}
|
|
|
|
#[test]
|
|
fn signed_transaction_input_serialize() {
|
|
let txin = SignedTransactionInput {
|
|
txid: H256::from(77),
|
|
vout: 13,
|
|
script_sig: TransactionInputScript {
|
|
asm: "Hello, world!!!".to_owned(),
|
|
hex: Bytes::new(vec![1, 2, 3, 4]),
|
|
},
|
|
sequence: 123,
|
|
};
|
|
assert_eq!(serde_json::to_string(&txin).unwrap(), r#"{"txid":"4d00000000000000000000000000000000000000000000000000000000000000","vout":13,"script_sig":{"asm":"Hello, world!!!","hex":"01020304"},"sequence":123}"#);
|
|
}
|
|
|
|
#[test]
|
|
fn signed_transaction_input_deserialize() {
|
|
let txin = SignedTransactionInput {
|
|
txid: H256::from(77),
|
|
vout: 13,
|
|
script_sig: TransactionInputScript {
|
|
asm: "Hello, world!!!".to_owned(),
|
|
hex: Bytes::new(vec![1, 2, 3, 4]),
|
|
},
|
|
sequence: 123,
|
|
};
|
|
assert_eq!(
|
|
serde_json::from_str::<SignedTransactionInput>(r#"{"txid":"4d00000000000000000000000000000000000000000000000000000000000000","vout":13,"script_sig":{"asm":"Hello, world!!!","hex":"01020304"},"sequence":123}"#).unwrap(),
|
|
txin);
|
|
}
|
|
|
|
#[test]
|
|
fn signed_transaction_output_serialize() {
|
|
let txout = SignedTransactionOutput {
|
|
value: 777.79,
|
|
n: 12,
|
|
script: TransactionOutputScript {
|
|
asm: "Hello, world!!!".to_owned(),
|
|
hex: Bytes::new(vec![1, 2, 3, 4]),
|
|
req_sigs: 777,
|
|
script_type: ScriptType::Multisig,
|
|
addresses: vec![
|
|
"t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi".into(),
|
|
"t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543".into(),
|
|
],
|
|
},
|
|
};
|
|
assert_eq!(serde_json::to_string(&txout).unwrap(), r#"{"value":777.79,"n":12,"scriptPubKey":{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi","t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543"]}}"#);
|
|
}
|
|
|
|
#[test]
|
|
fn signed_transaction_output_deserialize() {
|
|
let txout = SignedTransactionOutput {
|
|
value: 777.79,
|
|
n: 12,
|
|
script: TransactionOutputScript {
|
|
asm: "Hello, world!!!".to_owned(),
|
|
hex: Bytes::new(vec![1, 2, 3, 4]),
|
|
req_sigs: 777,
|
|
script_type: ScriptType::Multisig,
|
|
addresses: vec![
|
|
"t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi".into(),
|
|
"t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543".into(),
|
|
],
|
|
},
|
|
};
|
|
assert_eq!(
|
|
serde_json::from_str::<SignedTransactionOutput>(r#"{"value":777.79,"n":12,"scriptPubKey":{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi","t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543"]}}"#).unwrap(),
|
|
txout);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_serialize() {
|
|
let tx = Transaction {
|
|
hex: "DEADBEEF".into(),
|
|
txid: H256::from(4),
|
|
hash: H256::from(5),
|
|
size: 33,
|
|
version: 55,
|
|
locktime: 66,
|
|
vin: vec![],
|
|
vout: vec![],
|
|
blockhash: H256::from(6),
|
|
confirmations: 77,
|
|
time: 88,
|
|
blocktime: 99,
|
|
};
|
|
assert_eq!(serde_json::to_string(&tx).unwrap(), r#"{"hex":"deadbeef","txid":"0400000000000000000000000000000000000000000000000000000000000000","hash":"0500000000000000000000000000000000000000000000000000000000000000","size":33,"version":55,"locktime":66,"vin":[],"vout":[],"blockhash":"0600000000000000000000000000000000000000000000000000000000000000","confirmations":77,"time":88,"blocktime":99}"#);
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_deserialize() {
|
|
let tx = Transaction {
|
|
hex: "DEADBEEF".into(),
|
|
txid: H256::from(4),
|
|
hash: H256::from(5),
|
|
size: 33,
|
|
version: 55,
|
|
locktime: 66,
|
|
vin: vec![],
|
|
vout: vec![],
|
|
blockhash: H256::from(6),
|
|
confirmations: 77,
|
|
time: 88,
|
|
blocktime: 99,
|
|
};
|
|
assert_eq!(
|
|
serde_json::from_str::<Transaction>(r#"{"hex":"deadbeef","txid":"0400000000000000000000000000000000000000000000000000000000000000","hash":"0500000000000000000000000000000000000000000000000000000000000000","size":33,"version":55,"locktime":66,"vin":[],"vout":[],"blockhash":"0600000000000000000000000000000000000000000000000000000000000000","confirmations":77,"time":88,"blocktime":99}"#).unwrap(),
|
|
tx);
|
|
}
|
|
}
|