kaspa_consensus_core/
tx.rs

1//!
2//! # Transaction
3//!
4//! This module implements consensus [`Transaction`] structure and related types.
5//!
6
7#![allow(non_snake_case)]
8
9mod script_public_key;
10
11use borsh::{BorshDeserialize, BorshSerialize};
12use kaspa_utils::hex::ToHex;
13use kaspa_utils::mem_size::MemSizeEstimator;
14use kaspa_utils::{serde_bytes, serde_bytes_fixed_ref};
15pub use script_public_key::{
16    scriptvec, ScriptPublicKey, ScriptPublicKeyT, ScriptPublicKeyVersion, ScriptPublicKeys, ScriptVec, SCRIPT_VECTOR_SIZE,
17};
18use serde::{Deserialize, Serialize};
19use std::collections::HashSet;
20use std::sync::atomic::AtomicU64;
21use std::sync::atomic::Ordering::SeqCst;
22use std::{
23    fmt::Display,
24    ops::Range,
25    str::{self},
26};
27use wasm_bindgen::prelude::*;
28
29use crate::{
30    hashing,
31    subnets::{self, SubnetworkId},
32};
33
34/// COINBASE_TRANSACTION_INDEX is the index of the coinbase transaction in every block
35pub const COINBASE_TRANSACTION_INDEX: usize = 0;
36/// A 32-byte Kaspa transaction identifier.
37pub type TransactionId = kaspa_hashes::Hash;
38
39/// Holds details about an individual transaction output in a utxo
40/// set such as whether or not it was contained in a coinbase tx, the daa
41/// score of the block that accepts the tx, its public key script, and how
42/// much it pays.
43/// @category Consensus
44#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
45#[serde(rename_all = "camelCase")]
46#[wasm_bindgen(inspectable, js_name = TransactionUtxoEntry)]
47pub struct UtxoEntry {
48    pub amount: u64,
49    #[wasm_bindgen(js_name = scriptPublicKey, getter_with_clone)]
50    pub script_public_key: ScriptPublicKey,
51    #[wasm_bindgen(js_name = blockDaaScore)]
52    pub block_daa_score: u64,
53    #[wasm_bindgen(js_name = isCoinbase)]
54    pub is_coinbase: bool,
55}
56
57impl UtxoEntry {
58    pub fn new(amount: u64, script_public_key: ScriptPublicKey, block_daa_score: u64, is_coinbase: bool) -> Self {
59        Self { amount, script_public_key, block_daa_score, is_coinbase }
60    }
61}
62
63impl MemSizeEstimator for UtxoEntry {}
64
65pub type TransactionIndexType = u32;
66
67/// Represents a Kaspa transaction outpoint
68#[derive(Eq, Default, Hash, PartialEq, Debug, Copy, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct TransactionOutpoint {
71    #[serde(with = "serde_bytes_fixed_ref")]
72    pub transaction_id: TransactionId,
73    pub index: TransactionIndexType,
74}
75
76impl TransactionOutpoint {
77    pub fn new(transaction_id: TransactionId, index: u32) -> Self {
78        Self { transaction_id, index }
79    }
80}
81
82impl Display for TransactionOutpoint {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "({}, {})", self.transaction_id, self.index)
85    }
86}
87
88/// Represents a Kaspa transaction input
89#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct TransactionInput {
92    pub previous_outpoint: TransactionOutpoint,
93    #[serde(with = "serde_bytes")]
94    pub signature_script: Vec<u8>, // TODO: Consider using SmallVec
95    pub sequence: u64,
96
97    // TODO: Since this field is used for calculating mass context free, and we already commit
98    // to the mass in a dedicated field (on the tx level), it follows that this field is no longer
99    // needed, and can be removed if we ever implement a v2 transaction
100    pub sig_op_count: u8,
101}
102
103impl TransactionInput {
104    pub fn new(previous_outpoint: TransactionOutpoint, signature_script: Vec<u8>, sequence: u64, sig_op_count: u8) -> Self {
105        Self { previous_outpoint, signature_script, sequence, sig_op_count }
106    }
107}
108
109impl std::fmt::Debug for TransactionInput {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        f.debug_struct("TransactionInput")
112            .field("previous_outpoint", &self.previous_outpoint)
113            .field("signature_script", &self.signature_script.to_hex())
114            .field("sequence", &self.sequence)
115            .field("sig_op_count", &self.sig_op_count)
116            .finish()
117    }
118}
119
120/// Represents a Kaspad transaction output
121#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct TransactionOutput {
124    pub value: u64,
125    pub script_public_key: ScriptPublicKey,
126}
127
128impl TransactionOutput {
129    pub fn new(value: u64, script_public_key: ScriptPublicKey) -> Self {
130        Self { value, script_public_key }
131    }
132}
133
134#[derive(Debug, Default, Serialize, Deserialize)]
135pub struct TransactionMass(AtomicU64); // TODO: using atomic as a temp solution for mutating this field through the mempool
136
137impl Eq for TransactionMass {}
138
139impl PartialEq for TransactionMass {
140    fn eq(&self, other: &Self) -> bool {
141        self.0.load(SeqCst) == other.0.load(SeqCst)
142    }
143}
144
145impl Clone for TransactionMass {
146    fn clone(&self) -> Self {
147        Self(AtomicU64::new(self.0.load(SeqCst)))
148    }
149}
150
151impl BorshDeserialize for TransactionMass {
152    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
153        let mass: u64 = borsh::BorshDeserialize::deserialize_reader(reader)?;
154        Ok(Self(AtomicU64::new(mass)))
155    }
156}
157
158impl BorshSerialize for TransactionMass {
159    fn serialize<W: std::io::prelude::Write>(&self, writer: &mut W) -> std::io::Result<()> {
160        borsh::BorshSerialize::serialize(&self.0.load(SeqCst), writer)
161    }
162}
163
164/// Represents a Kaspa transaction
165#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct Transaction {
168    pub version: u16,
169    pub inputs: Vec<TransactionInput>,
170    pub outputs: Vec<TransactionOutput>,
171    pub lock_time: u64,
172    pub subnetwork_id: SubnetworkId,
173    pub gas: u64,
174    #[serde(with = "serde_bytes")]
175    pub payload: Vec<u8>,
176
177    #[serde(default)]
178    mass: TransactionMass,
179
180    // A field that is used to cache the transaction ID.
181    // Always use the corresponding self.id() instead of accessing this field directly
182    #[serde(with = "serde_bytes_fixed_ref")]
183    id: TransactionId,
184}
185
186impl Transaction {
187    pub fn new(
188        version: u16,
189        inputs: Vec<TransactionInput>,
190        outputs: Vec<TransactionOutput>,
191        lock_time: u64,
192        subnetwork_id: SubnetworkId,
193        gas: u64,
194        payload: Vec<u8>,
195    ) -> Self {
196        let mut tx = Self::new_non_finalized(version, inputs, outputs, lock_time, subnetwork_id, gas, payload);
197        tx.finalize();
198        tx
199    }
200
201    pub fn new_non_finalized(
202        version: u16,
203        inputs: Vec<TransactionInput>,
204        outputs: Vec<TransactionOutput>,
205        lock_time: u64,
206        subnetwork_id: SubnetworkId,
207        gas: u64,
208        payload: Vec<u8>,
209    ) -> Self {
210        Self { version, inputs, outputs, lock_time, subnetwork_id, gas, payload, mass: Default::default(), id: Default::default() }
211    }
212}
213
214impl Transaction {
215    /// Determines whether or not a transaction is a coinbase transaction. A coinbase
216    /// transaction is a special transaction created by miners that distributes fees and block subsidy
217    /// to the previous blocks' miners, and specifies the script_pub_key that will be used to pay the current
218    /// miner in future blocks.
219    pub fn is_coinbase(&self) -> bool {
220        self.subnetwork_id == subnets::SUBNETWORK_ID_COINBASE
221    }
222
223    /// Recompute and finalize the tx id based on updated tx fields
224    pub fn finalize(&mut self) {
225        self.id = hashing::tx::id(self);
226    }
227
228    /// Returns the transaction ID
229    pub fn id(&self) -> TransactionId {
230        self.id
231    }
232
233    /// Set the mass field of this transaction. The mass field is expected depending on hard-forks which are currently
234    /// activated only on some testnets. The field has no effect on tx ID so no need to finalize following this call.
235    pub fn set_mass(&self, mass: u64) {
236        self.mass.0.store(mass, SeqCst)
237    }
238
239    pub fn mass(&self) -> u64 {
240        self.mass.0.load(SeqCst)
241    }
242
243    pub fn with_mass(self, mass: u64) -> Self {
244        self.set_mass(mass);
245        self
246    }
247}
248
249impl MemSizeEstimator for Transaction {
250    fn estimate_mem_bytes(&self) -> usize {
251        // Calculates mem bytes of the transaction (for cache tracking purposes)
252        size_of::<Self>()
253            + self.payload.len()
254            + self
255                .inputs
256                .iter()
257                .map(|i| i.signature_script.len() + size_of::<TransactionInput>())
258                .chain(self.outputs.iter().map(|o| {
259                    // size_of::<TransactionOutput>() already counts SCRIPT_VECTOR_SIZE bytes within, so we only add the delta
260                    o.script_public_key.script().len().saturating_sub(SCRIPT_VECTOR_SIZE) + size_of::<TransactionOutput>()
261                }))
262                .sum::<usize>()
263    }
264}
265
266/// Represents any kind of transaction which has populated UTXO entry data and can be verified/signed etc
267pub trait VerifiableTransaction {
268    fn tx(&self) -> &Transaction;
269
270    /// Returns the `i`'th populated input
271    fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry);
272
273    /// Returns an iterator over populated `(input, entry)` pairs
274    fn populated_inputs(&self) -> PopulatedInputIterator<'_, Self>
275    where
276        Self: Sized,
277    {
278        PopulatedInputIterator::new(self)
279    }
280
281    fn inputs(&self) -> &[TransactionInput] {
282        &self.tx().inputs
283    }
284
285    fn outputs(&self) -> &[TransactionOutput] {
286        &self.tx().outputs
287    }
288
289    fn is_coinbase(&self) -> bool {
290        self.tx().is_coinbase()
291    }
292
293    fn id(&self) -> TransactionId {
294        self.tx().id()
295    }
296}
297
298/// A custom iterator written only so that `populated_inputs` has a known return type and can de defined on the trait level
299pub struct PopulatedInputIterator<'a, T: VerifiableTransaction> {
300    tx: &'a T,
301    r: Range<usize>,
302}
303
304impl<'a, T: VerifiableTransaction> PopulatedInputIterator<'a, T> {
305    pub fn new(tx: &'a T) -> Self {
306        Self { tx, r: (0..tx.inputs().len()) }
307    }
308}
309
310impl<'a, T: VerifiableTransaction> Iterator for PopulatedInputIterator<'a, T> {
311    type Item = (&'a TransactionInput, &'a UtxoEntry);
312
313    fn next(&mut self) -> Option<Self::Item> {
314        self.r.next().map(|i| self.tx.populated_input(i))
315    }
316
317    fn size_hint(&self) -> (usize, Option<usize>) {
318        self.r.size_hint()
319    }
320}
321
322impl<'a, T: VerifiableTransaction> ExactSizeIterator for PopulatedInputIterator<'a, T> {}
323
324/// Represents a read-only referenced transaction along with fully populated UTXO entry data
325pub struct PopulatedTransaction<'a> {
326    pub tx: &'a Transaction,
327    pub entries: Vec<UtxoEntry>,
328}
329
330impl<'a> PopulatedTransaction<'a> {
331    pub fn new(tx: &'a Transaction, entries: Vec<UtxoEntry>) -> Self {
332        assert_eq!(tx.inputs.len(), entries.len());
333        Self { tx, entries }
334    }
335}
336
337impl<'a> VerifiableTransaction for PopulatedTransaction<'a> {
338    fn tx(&self) -> &Transaction {
339        self.tx
340    }
341
342    fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) {
343        (&self.tx.inputs[index], &self.entries[index])
344    }
345}
346
347/// Represents a validated transaction with populated UTXO entry data and a calculated fee
348pub struct ValidatedTransaction<'a> {
349    pub tx: &'a Transaction,
350    pub entries: Vec<UtxoEntry>,
351    pub calculated_fee: u64,
352}
353
354impl<'a> ValidatedTransaction<'a> {
355    pub fn new(populated_tx: PopulatedTransaction<'a>, calculated_fee: u64) -> Self {
356        Self { tx: populated_tx.tx, entries: populated_tx.entries, calculated_fee }
357    }
358
359    pub fn new_coinbase(tx: &'a Transaction) -> Self {
360        assert!(tx.is_coinbase());
361        Self { tx, entries: Vec::new(), calculated_fee: 0 }
362    }
363}
364
365impl<'a> VerifiableTransaction for ValidatedTransaction<'a> {
366    fn tx(&self) -> &Transaction {
367        self.tx
368    }
369
370    fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) {
371        (&self.tx.inputs[index], &self.entries[index])
372    }
373}
374
375impl AsRef<Transaction> for Transaction {
376    fn as_ref(&self) -> &Transaction {
377        self
378    }
379}
380
381/// Represents a generic mutable/readonly/pointer transaction type along
382/// with partially filled UTXO entry data and optional fee and mass
383#[derive(Clone, Debug, PartialEq, Eq)]
384pub struct MutableTransaction<T: AsRef<Transaction> = std::sync::Arc<Transaction>> {
385    /// The inner transaction
386    pub tx: T,
387    /// Partially filled UTXO entry data
388    pub entries: Vec<Option<UtxoEntry>>,
389    /// Populated fee
390    pub calculated_fee: Option<u64>,
391    /// Populated compute mass (does not include the storage mass)
392    pub calculated_compute_mass: Option<u64>,
393}
394
395impl<T: AsRef<Transaction>> MutableTransaction<T> {
396    pub fn new(tx: T) -> Self {
397        let num_inputs = tx.as_ref().inputs.len();
398        Self { tx, entries: vec![None; num_inputs], calculated_fee: None, calculated_compute_mass: None }
399    }
400
401    pub fn id(&self) -> TransactionId {
402        self.tx.as_ref().id()
403    }
404
405    pub fn with_entries(tx: T, entries: Vec<UtxoEntry>) -> Self {
406        assert_eq!(tx.as_ref().inputs.len(), entries.len());
407        Self { tx, entries: entries.into_iter().map(Some).collect(), calculated_fee: None, calculated_compute_mass: None }
408    }
409
410    /// Returns the tx wrapped as a [`VerifiableTransaction`]. Note that this function
411    /// must be called only once all UTXO entries are populated, otherwise it panics.
412    pub fn as_verifiable(&self) -> impl VerifiableTransaction + '_ {
413        assert!(self.is_verifiable());
414        MutableTransactionVerifiableWrapper { inner: self }
415    }
416
417    pub fn is_verifiable(&self) -> bool {
418        assert_eq!(self.entries.len(), self.tx.as_ref().inputs.len());
419        self.entries.iter().all(|e| e.is_some())
420    }
421
422    pub fn is_fully_populated(&self) -> bool {
423        self.is_verifiable() && self.calculated_fee.is_some() && self.calculated_compute_mass.is_some()
424    }
425
426    pub fn missing_outpoints(&self) -> impl Iterator<Item = TransactionOutpoint> + '_ {
427        assert_eq!(self.entries.len(), self.tx.as_ref().inputs.len());
428        self.entries.iter().enumerate().filter_map(|(i, entry)| {
429            if entry.is_none() {
430                Some(self.tx.as_ref().inputs[i].previous_outpoint)
431            } else {
432                None
433            }
434        })
435    }
436
437    pub fn clear_entries(&mut self) {
438        for entry in self.entries.iter_mut() {
439            *entry = None;
440        }
441    }
442
443    /// Returns the calculated feerate. The feerate is calculated as the amount of fee
444    /// this transactions pays per gram of the full contextual (compute & storage) mass. The
445    /// function returns a value when calculated fee exists and the contextual mass is greater
446    /// than zero, otherwise `None` is returned.
447    pub fn calculated_feerate(&self) -> Option<f64> {
448        let contextual_mass = self.tx.as_ref().mass();
449        if contextual_mass > 0 {
450            self.calculated_fee.map(|fee| fee as f64 / contextual_mass as f64)
451        } else {
452            None
453        }
454    }
455
456    /// A function for estimating the amount of memory bytes used by this transaction (dedicated to mempool usage).
457    /// We need consistency between estimation calls so only this function should be used for this purpose since
458    /// `estimate_mem_bytes` is sensitive to pointer wrappers such as Arc
459    pub fn mempool_estimated_bytes(&self) -> usize {
460        self.estimate_mem_bytes()
461    }
462
463    pub fn has_parent(&self, possible_parent: TransactionId) -> bool {
464        self.tx.as_ref().inputs.iter().any(|x| x.previous_outpoint.transaction_id == possible_parent)
465    }
466
467    pub fn has_parent_in_set(&self, possible_parents: &HashSet<TransactionId>) -> bool {
468        self.tx.as_ref().inputs.iter().any(|x| possible_parents.contains(&x.previous_outpoint.transaction_id))
469    }
470}
471
472impl<T: AsRef<Transaction>> MemSizeEstimator for MutableTransaction<T> {
473    fn estimate_mem_bytes(&self) -> usize {
474        size_of::<Self>()
475            + self
476                .entries
477                .iter()
478                .map(|op| {
479                    // size_of::<Option<UtxoEntry>>() already counts SCRIPT_VECTOR_SIZE bytes within, so we only add the delta
480                    size_of::<Option<UtxoEntry>>()
481                        + op.as_ref().map_or(0, |e| e.script_public_key.script().len().saturating_sub(SCRIPT_VECTOR_SIZE))
482                })
483                .sum::<usize>()
484            + self.tx.as_ref().estimate_mem_bytes()
485    }
486}
487
488impl<T: AsRef<Transaction>> AsRef<Transaction> for MutableTransaction<T> {
489    fn as_ref(&self) -> &Transaction {
490        self.tx.as_ref()
491    }
492}
493
494/// Private struct used to wrap a [`MutableTransaction`] as a [`VerifiableTransaction`]
495struct MutableTransactionVerifiableWrapper<'a, T: AsRef<Transaction>> {
496    inner: &'a MutableTransaction<T>,
497}
498
499impl<T: AsRef<Transaction>> VerifiableTransaction for MutableTransactionVerifiableWrapper<'_, T> {
500    fn tx(&self) -> &Transaction {
501        self.inner.tx.as_ref()
502    }
503
504    fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) {
505        (
506            &self.inner.tx.as_ref().inputs[index],
507            self.inner.entries[index].as_ref().expect("expected to be called only following full UTXO population"),
508        )
509    }
510}
511
512/// Specialized impl for `T=Arc<Transaction>`
513impl MutableTransaction {
514    pub fn from_tx(tx: Transaction) -> Self {
515        Self::new(std::sync::Arc::new(tx))
516    }
517}
518
519/// Alias for a fully mutable and owned transaction which can be populated with external data
520/// and can also be modified internally and signed etc.
521pub type SignableTransaction = MutableTransaction<Transaction>;
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526    use consensus_core::subnets::SUBNETWORK_ID_COINBASE;
527    use smallvec::smallvec;
528
529    fn test_transaction() -> Transaction {
530        let script_public_key = ScriptPublicKey::new(
531            0,
532            smallvec![
533                0x76, 0xa9, 0x21, 0x03, 0x2f, 0x7e, 0x43, 0x0a, 0xa4, 0xc9, 0xd1, 0x59, 0x43, 0x7e, 0x84, 0xb9, 0x75, 0xdc, 0x76,
534                0xd9, 0x00, 0x3b, 0xf0, 0x92, 0x2c, 0xf3, 0xaa, 0x45, 0x28, 0x46, 0x4b, 0xab, 0x78, 0x0d, 0xba, 0x5e
535            ],
536        );
537        Transaction::new(
538            1,
539            vec![
540                TransactionInput {
541                    previous_outpoint: TransactionOutpoint {
542                        transaction_id: TransactionId::from_slice(&[
543                            0x16, 0x5e, 0x38, 0xe8, 0xb3, 0x91, 0x45, 0x95, 0xd9, 0xc6, 0x41, 0xf3, 0xb8, 0xee, 0xc2, 0xf3, 0x46,
544                            0x11, 0x89, 0x6b, 0x82, 0x1a, 0x68, 0x3b, 0x7a, 0x4e, 0xde, 0xfe, 0x2c, 0x00, 0x00, 0x00,
545                        ]),
546                        index: 0xfffffffa,
547                    },
548                    signature_script: vec![
549                        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
550                        0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
551                    ],
552                    sequence: 2,
553                    sig_op_count: 3,
554                },
555                TransactionInput {
556                    previous_outpoint: TransactionOutpoint {
557                        transaction_id: TransactionId::from_slice(&[
558                            0x4b, 0xb0, 0x75, 0x35, 0xdf, 0xd5, 0x8e, 0x0b, 0x3c, 0xd6, 0x4f, 0xd7, 0x15, 0x52, 0x80, 0x87, 0x2a,
559                            0x04, 0x71, 0xbc, 0xf8, 0x30, 0x95, 0x52, 0x6a, 0xce, 0x0e, 0x38, 0xc6, 0x00, 0x00, 0x00,
560                        ]),
561                        index: 0xfffffffb,
562                    },
563                    signature_script: vec![
564                        0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,
565                        0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
566                    ],
567                    sequence: 4,
568                    sig_op_count: 5,
569                },
570            ],
571            vec![
572                TransactionOutput { value: 6, script_public_key: script_public_key.clone() },
573                TransactionOutput { value: 7, script_public_key },
574            ],
575            8,
576            SUBNETWORK_ID_COINBASE,
577            9,
578            vec![
579                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
580                0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
581                0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
582                0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
583                0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e,
584                0x5f, 0x60, 0x61, 0x62, 0x63,
585            ],
586        )
587    }
588
589    #[test]
590    fn test_transaction_bincode() {
591        let tx = test_transaction();
592        let bts = bincode::serialize(&tx).unwrap();
593
594        // standard, based on https://github.com/kaspanet/rusty-kaspa/commit/7e947a06d2434daf4bc7064d4cd87dc1984b56fe
595        let expected_bts = vec![
596            1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 22, 94, 56, 232, 179, 145, 69, 149, 217, 198, 65, 243, 184, 238, 194, 243, 70, 17, 137, 107,
597            130, 26, 104, 59, 122, 78, 222, 254, 44, 0, 0, 0, 250, 255, 255, 255, 32, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
598            9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 2, 0, 0, 0, 0, 0, 0, 0, 3, 75,
599            176, 117, 53, 223, 213, 142, 11, 60, 214, 79, 215, 21, 82, 128, 135, 42, 4, 113, 188, 248, 48, 149, 82, 106, 206, 14, 56,
600            198, 0, 0, 0, 251, 255, 255, 255, 32, 0, 0, 0, 0, 0, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
601            48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 4, 0, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 0, 0, 0, 0, 0, 6, 0,
602            0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 118, 169, 33, 3, 47, 126, 67, 10, 164, 201, 209, 89, 67, 126, 132, 185,
603            117, 220, 118, 217, 0, 59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0,
604            36, 0, 0, 0, 0, 0, 0, 0, 118, 169, 33, 3, 47, 126, 67, 10, 164, 201, 209, 89, 67, 126, 132, 185, 117, 220, 118, 217, 0,
605            59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
606            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
607            13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
608            43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
609            73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 0, 0, 0, 0, 0,
610            0, 0, 0, 69, 146, 193, 64, 98, 49, 45, 0, 77, 32, 25, 122, 77, 15, 211, 252, 61, 210, 82, 177, 39, 153, 127, 33, 188, 172,
611            138, 38, 67, 75, 241, 176,
612        ];
613        assert_eq!(expected_bts, bts);
614        assert_eq!(tx, bincode::deserialize(&bts).unwrap());
615    }
616
617    #[test]
618    fn test_transaction_json() {
619        let tx = test_transaction();
620        let str = serde_json::to_string_pretty(&tx).unwrap();
621        let expected_str = r#"{
622  "version": 1,
623  "inputs": [
624    {
625      "previousOutpoint": {
626        "transactionId": "165e38e8b3914595d9c641f3b8eec2f34611896b821a683b7a4edefe2c000000",
627        "index": 4294967290
628      },
629      "signatureScript": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
630      "sequence": 2,
631      "sigOpCount": 3
632    },
633    {
634      "previousOutpoint": {
635        "transactionId": "4bb07535dfd58e0b3cd64fd7155280872a0471bcf83095526ace0e38c6000000",
636        "index": 4294967291
637      },
638      "signatureScript": "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
639      "sequence": 4,
640      "sigOpCount": 5
641    }
642  ],
643  "outputs": [
644    {
645      "value": 6,
646      "scriptPublicKey": "000076a921032f7e430aa4c9d159437e84b975dc76d9003bf0922cf3aa4528464bab780dba5e"
647    },
648    {
649      "value": 7,
650      "scriptPublicKey": "000076a921032f7e430aa4c9d159437e84b975dc76d9003bf0922cf3aa4528464bab780dba5e"
651    }
652  ],
653  "lockTime": 8,
654  "subnetworkId": "0100000000000000000000000000000000000000",
655  "gas": 9,
656  "payload": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60616263",
657  "mass": 0,
658  "id": "4592c14062312d004d20197a4d0fd3fc3dd252b127997f21bcac8a26434bf1b0"
659}"#;
660        assert_eq!(expected_str, str);
661        assert_eq!(tx, serde_json::from_str(&str).unwrap());
662    }
663
664    #[test]
665    fn test_spk_serde_json() {
666        let vec = (0..SCRIPT_VECTOR_SIZE as u8).collect::<Vec<_>>();
667        let spk = ScriptPublicKey::from_vec(0xc0de, vec.clone());
668        let hex: String = serde_json::to_string(&spk).unwrap();
669        assert_eq!("\"c0de000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223\"", hex);
670        let spk = serde_json::from_str::<ScriptPublicKey>(&hex).unwrap();
671        assert_eq!(spk.version, 0xc0de);
672        assert_eq!(spk.script.as_slice(), vec.as_slice());
673        let result = "00".parse::<ScriptPublicKey>();
674        assert!(matches!(result, Err(faster_hex::Error::InvalidLength(2))));
675        let result = "0000".parse::<ScriptPublicKey>();
676        let _empty = ScriptPublicKey { version: 0, script: ScriptVec::new() };
677        assert!(matches!(result, Ok(_empty)));
678    }
679
680    #[test]
681    fn test_spk_borsh() {
682        // Tests for ScriptPublicKey Borsh ser/deser since we manually implemented them
683        let spk = ScriptPublicKey::from_vec(12, vec![32; 20]);
684        let bin = borsh::to_vec(&spk).unwrap();
685        let spk2: ScriptPublicKey = BorshDeserialize::try_from_slice(&bin).unwrap();
686        assert_eq!(spk, spk2);
687
688        let spk = ScriptPublicKey::from_vec(55455, vec![11; 200]);
689        let bin = borsh::to_vec(&spk).unwrap();
690        let spk2: ScriptPublicKey = BorshDeserialize::try_from_slice(&bin).unwrap();
691        assert_eq!(spk, spk2);
692    }
693
694    // use wasm_bindgen_test::wasm_bindgen_test;
695    // #[wasm_bindgen_test]
696    // pub fn test_wasm_serde_spk_constructor() {
697    //     let str = "kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j";
698    //     let a = Address::constructor(str);
699    //     let value = to_value(&a).unwrap();
700    //
701    //     assert_eq!(JsValue::from_str("string"), value.js_typeof());
702    //     assert_eq!(value, JsValue::from_str(str));
703    //     assert_eq!(a, from_value(value).unwrap());
704    // }
705    //
706    // #[wasm_bindgen_test]
707    // pub fn test_wasm_js_serde_spk_object() {
708    //     let expected = Address::constructor("kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j");
709    //
710    //     use web_sys::console;
711    //     console::log_4(&"address: ".into(), &expected.version().into(), &expected.prefix().into(), &expected.payload().into());
712    //
713    //     let obj = Object::new();
714    //     obj.set("version", &JsValue::from_str("PubKey")).unwrap();
715    //     obj.set("prefix", &JsValue::from_str("kaspa")).unwrap();
716    //     obj.set("payload", &JsValue::from_str("qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j")).unwrap();
717    //
718    //     assert_eq!(JsValue::from_str("object"), obj.js_typeof());
719    //
720    //     let obj_js = obj.into_js_result().unwrap();
721    //     let actual = from_value(obj_js).unwrap();
722    //     assert_eq!(expected, actual);
723    // }
724    //
725    // #[wasm_bindgen_test]
726    // pub fn test_wasm_serde_spk_object() {
727    //     use wasm_bindgen::convert::IntoWasmAbi;
728    //
729    //     let expected = Address::constructor("kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j");
730    //     let wasm_js_value: JsValue = expected.clone().into_abi().into();
731    //
732    //     // use web_sys::console;
733    //     // console::log_4(&"address: ".into(), &expected.version().into(), &expected.prefix().into(), &expected.payload().into());
734    //
735    //     let actual = from_value(wasm_js_value).unwrap();
736    //     assert_eq!(expected, actual);
737    // }
738}