Skip to main content

tw_chain/primitives/
transaction.rs

1#![allow(unused)]
2use crate::constants::*;
3use crate::crypto::sign_ed25519::{PublicKey, Signature};
4use crate::primitives::{
5    asset::{Asset, ItemAsset, TokenAmount},
6    druid::{DdeValues, DruidExpectation},
7};
8use crate::script::lang::Script;
9use crate::script::{OpCodes, StackEntry};
10use crate::utils::is_valid_amount;
11use bincode::serialize;
12use bytes::Bytes;
13use serde::{Deserialize, Serialize};
14use std::fmt;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub enum GenesisTxHashSpec {
18    Create,
19    Default,
20    //TODO: Eventually custom?
21}
22
23impl GenesisTxHashSpec {
24    pub fn get_genesis_hash(&self) -> Option<String> {
25        match self {
26            GenesisTxHashSpec::Create => None, /* Unique DRS transaction hash will be assigned */
27            GenesisTxHashSpec::Default => Some(ITEM_DEFAULT_DRS_TX_HASH.to_string()),
28        }
29    }
30}
31
32/// A user-friendly construction struct for a TxIn
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34pub struct TxConstructor {
35    pub previous_out: OutPoint,
36    pub signatures: Vec<Signature>,
37    pub pub_keys: Vec<PublicKey>,
38    pub address_version: Option<u64>,
39}
40
41/// An outpoint - a combination of a transaction hash and an index n into its vout
42#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
43pub struct OutPoint {
44    pub t_hash: String,
45    pub n: i32,
46}
47
48impl fmt::Display for OutPoint {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        write!(f, "t_hash:{}-n:{}", self.t_hash, self.n)
51    }
52}
53
54impl OutPoint {
55    /// Creates a new outpoint instance
56    pub fn new(t_hash: String, n: i32) -> OutPoint {
57        OutPoint { t_hash, n }
58    }
59}
60
61impl Default for OutPoint {
62    fn default() -> Self {
63        Self::new(String::new(), 0)
64    }
65}
66
67/// An input of a transaction. It contains the location of the previous
68/// transaction's output that it claims and a signature that matches the
69/// output's public key.
70#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
71pub struct TxIn {
72    pub previous_out: Option<OutPoint>,
73    pub script_signature: Script,
74}
75
76impl Default for TxIn {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl TxIn {
83    /// Creates a new TxIn instance
84    pub fn new() -> TxIn {
85        let mut script_sig = Script::new();
86        script_sig.stack.push(StackEntry::Op(OpCodes::OP_0));
87
88        TxIn {
89            previous_out: None,
90            script_signature: script_sig,
91        }
92    }
93
94    /// Creates a new TxIn instance from provided script and no previous_out
95    ///
96    /// ### Arguments
97    ///
98    /// * `script_sig`      - Script signature of the previous outpoint
99    pub fn new_from_script(script_sig: Script) -> TxIn {
100        TxIn {
101            previous_out: None,
102            script_signature: script_sig,
103        }
104    }
105
106    /// Creates a new TxIn instance from provided inputs
107    ///
108    /// ### Arguments
109    ///
110    /// * `previous_out`    - OutPoint of the previous transaction
111    /// * `script_sig`      - Script signature of the previous outpoint
112    pub fn new_from_input(previous_out: OutPoint, script_sig: Script) -> TxIn {
113        TxIn {
114            previous_out: Some(previous_out),
115            script_signature: script_sig,
116        }
117    }
118}
119
120/// An output of a transaction. It contains the public key that the next input
121/// must be able to sign with to claim it. It also contains the block hash for the
122/// potential DRS if this is a data asset transaction
123#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
124pub struct TxOut {
125    pub value: Asset,
126    pub locktime: u64,
127    pub script_public_key: Option<String>,
128}
129
130impl TxOut {
131    /// Creates a new TxOut instance
132    pub fn new() -> TxOut {
133        Default::default()
134    }
135
136    pub fn new_token_amount(
137        to_address: String,
138        amount: TokenAmount,
139        locktime: Option<u64>,
140    ) -> TxOut {
141        TxOut {
142            value: Asset::Token(amount),
143            locktime: locktime.unwrap_or(ZERO as u64),
144            script_public_key: Some(to_address),
145        }
146    }
147
148    /// Creates a new TxOut instance for a `Item` asset
149    ///
150    /// **NOTE:** Only create transactions may have `Item` assets that have a `None` `genesis_hash`
151    pub fn new_item_amount(to_address: String, item: ItemAsset, locktime: Option<u64>) -> TxOut {
152        TxOut {
153            value: Asset::Item(item),
154            locktime: locktime.unwrap_or(ZERO as u64),
155            script_public_key: Some(to_address),
156        }
157    }
158
159    //TODO: Add handling for `Data' asset variant
160    pub fn new_asset(to_address: String, asset: Asset, locktime: Option<u64>) -> TxOut {
161        match asset {
162            Asset::Token(amount) => TxOut::new_token_amount(to_address, amount, locktime),
163            Asset::Item(item) => TxOut::new_item_amount(to_address, item, locktime),
164            _ => panic!("Cannot create TxOut for asset of type {:?}", asset),
165        }
166    }
167
168    /// Returns whether current tx_out is a P2SH
169    pub fn is_p2sh_tx_out(&self) -> bool {
170        if let Some(pk) = &self.script_public_key {
171            let pk_bytes = pk.as_bytes();
172            return pk_bytes[0] == P2SH_PREPEND;
173        }
174
175        false
176    }
177}
178
179/// The basic transaction that is broadcasted on the network and contained in
180/// blocks. A transaction can contain multiple inputs and outputs.
181#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
182pub struct Transaction {
183    pub inputs: Vec<TxIn>,
184    pub outputs: Vec<TxOut>,
185    pub version: usize,
186    pub fees: Vec<TxOut>,
187    pub druid_info: Option<DdeValues>,
188}
189
190impl Default for Transaction {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196impl Transaction {
197    /// Creates a new Transaction instance
198    pub fn new() -> Transaction {
199        Transaction {
200            inputs: Vec::new(),
201            outputs: Vec::new(),
202            fees: Vec::new(),
203            version: NETWORK_VERSION as usize,
204            druid_info: None,
205        }
206    }
207
208    /// Get the total transaction size in bytes
209    pub fn get_total_size(&self) -> usize {
210        let bytes = match serialize(self) {
211            Ok(bytes) => bytes,
212            Err(_) => vec![],
213        };
214        bytes.len()
215    }
216
217    /// Gets the create asset assigned to this transaction, if it exists
218    fn get_create_asset(&self) -> Option<&Asset> {
219        let is_create = self.inputs.len() == 1
220            && self.inputs[0].previous_out.is_none()
221            && self.outputs.len() == 1;
222
223        is_create.then(|| &self.outputs[0].value)
224    }
225
226    /// Returns whether current transaction is a coinbase tx
227    pub fn is_coinbase(&self) -> bool {
228        self.get_create_asset()
229            .map(|a| a.is_token())
230            .unwrap_or_default()
231    }
232
233    /// Returns whether current transaction creates a new asset
234    pub fn is_create_tx(&self) -> bool {
235        self.get_create_asset()
236            .map(|a| !a.is_token())
237            .unwrap_or_default()
238    }
239
240    /// Returns whether current transaction is a P2SH tx
241    pub fn is_p2sh_tx(&self) -> bool {
242        if self.outputs.len() != 1 {
243            return false;
244        }
245
246        if let Some(pk) = &self.outputs[0].script_public_key {
247            let pk_bytes = pk.as_bytes();
248            return pk_bytes[0] == P2SH_PREPEND;
249        }
250
251        false
252    }
253}