dusk_node_data/ledger/
transaction.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7use dusk_bytes::Serializable as DuskSerializable;
8use dusk_core::signatures::bls::PublicKey as AccountPublicKey;
9use dusk_core::transfer::moonlight::Transaction as MoonlightTransaction;
10use dusk_core::transfer::phoenix::Transaction as PhoenixTransaction;
11use dusk_core::transfer::Transaction as ProtocolTransaction;
12use serde::Serialize;
13use sha3::Digest;
14
15use crate::Serializable;
16
17#[derive(Debug, Clone)]
18pub struct Transaction {
19    pub version: u32,
20    pub r#type: u32,
21    pub inner: ProtocolTransaction,
22    pub(crate) size: Option<usize>,
23}
24
25impl Transaction {
26    pub fn size(&self) -> usize {
27        match self.size {
28            Some(size) => size,
29            None => {
30                let mut buf = vec![];
31                self.write(&mut buf).expect("write to vec should not fail");
32                buf.len()
33            }
34        }
35    }
36}
37
38impl From<ProtocolTransaction> for Transaction {
39    fn from(value: ProtocolTransaction) -> Self {
40        Self {
41            inner: value,
42            r#type: 1,
43            version: 1,
44            size: None,
45        }
46    }
47}
48
49/// A spent transaction is a transaction that has been included in a block and
50/// was executed.
51#[derive(Debug, Clone, Serialize)]
52pub struct SpentTransaction {
53    /// The transaction that was executed.
54    pub inner: Transaction,
55    /// The height of the block in which the transaction was included.
56    pub block_height: u64,
57    /// The amount of gas that was spent during the execution of the
58    /// transaction.
59    pub gas_spent: u64,
60    /// An optional error message if the transaction execution yielded an
61    /// error.
62    pub err: Option<String>,
63}
64
65impl SpentTransaction {
66    /// Returns the underlying public transaction, if it is one. Otherwise,
67    /// returns `None`.
68    pub fn public(&self) -> Option<&MoonlightTransaction> {
69        match &self.inner.inner {
70            ProtocolTransaction::Moonlight(public_tx) => Some(public_tx),
71            _ => None,
72        }
73    }
74
75    /// Returns the underlying shielded transaction, if it is one. Otherwise,
76    /// returns `None`.
77    pub fn shielded(&self) -> Option<&PhoenixTransaction> {
78        match &self.inner.inner {
79            ProtocolTransaction::Phoenix(shielded_tx) => Some(shielded_tx),
80            _ => None,
81        }
82    }
83}
84
85impl Transaction {
86    /// Computes the hash digest of the entire transaction data.
87    ///
88    /// This method returns the Sha3 256 digest of the entire
89    /// transaction in its serialized form. If the transaction is a blob
90    /// transaction, the sidecar is stripped
91    ///
92    /// The digest hash is currently only being used in the merkle tree.
93    ///
94    /// ### Returns
95    /// An array of 32 bytes representing the hash of the transaction.
96    pub fn digest(&self) -> [u8; 32] {
97        let tx_bytes = self.inner.blob_to_memo().map_or_else(
98            || self.inner.to_var_bytes(),
99            |mut blob_tx| {
100                let _ = blob_tx.strip_blobs();
101                blob_tx.to_var_bytes()
102            },
103        );
104        sha3::Sha3_256::digest(tx_bytes).into()
105    }
106
107    /// Computes the transaction ID.
108    ///
109    /// The transaction ID is a unique identifier for the transaction.
110    /// Unlike the [`digest()`](#method.digest) method, which is computed over
111    /// the entire transaction, the transaction ID is derived from specific
112    /// fields of the transaction and serves as a unique identifier of the
113    /// transaction itself.
114    ///
115    /// ### Returns
116    /// An array of 32 bytes representing the transaction ID.
117    pub fn id(&self) -> [u8; 32] {
118        self.inner.hash().to_bytes()
119    }
120
121    pub fn gas_price(&self) -> u64 {
122        self.inner.gas_price()
123    }
124
125    pub fn to_spend_ids(&self) -> Vec<SpendingId> {
126        match &self.inner {
127            ProtocolTransaction::Phoenix(p) => p
128                .nullifiers()
129                .iter()
130                .map(|n| SpendingId::Nullifier(n.to_bytes()))
131                .collect(),
132            ProtocolTransaction::Moonlight(m) => {
133                vec![SpendingId::AccountNonce(*m.sender(), m.nonce())]
134            }
135        }
136    }
137
138    pub fn next_spending_id(&self) -> Option<SpendingId> {
139        match &self.inner {
140            ProtocolTransaction::Phoenix(_) => None,
141            ProtocolTransaction::Moonlight(m) => {
142                Some(SpendingId::AccountNonce(*m.sender(), m.nonce() + 1))
143            }
144        }
145    }
146}
147
148impl PartialEq<Self> for Transaction {
149    fn eq(&self, other: &Self) -> bool {
150        self.r#type == other.r#type
151            && self.version == other.version
152            && self.id() == other.id()
153    }
154}
155
156impl Eq for Transaction {}
157
158impl PartialEq<Self> for SpentTransaction {
159    fn eq(&self, other: &Self) -> bool {
160        self.inner == other.inner && self.gas_spent == other.gas_spent
161    }
162}
163
164impl Eq for SpentTransaction {}
165
166pub enum SpendingId {
167    Nullifier([u8; 32]),
168    AccountNonce(AccountPublicKey, u64),
169}
170
171impl SpendingId {
172    pub fn to_bytes(&self) -> Vec<u8> {
173        match self {
174            SpendingId::Nullifier(n) => n.to_vec(),
175            SpendingId::AccountNonce(account, nonce) => {
176                let mut id = account.to_bytes().to_vec();
177                id.extend_from_slice(&nonce.to_le_bytes());
178                id
179            }
180        }
181    }
182
183    pub fn next(&self) -> Option<SpendingId> {
184        match self {
185            SpendingId::Nullifier(_) => None,
186            SpendingId::AccountNonce(account, nonce) => {
187                Some(SpendingId::AccountNonce(*account, nonce + 1))
188            }
189        }
190    }
191}
192
193#[cfg(any(feature = "faker", test))]
194pub mod faker {
195    use dusk_core::transfer::data::{ContractCall, TransactionData};
196    use dusk_core::transfer::phoenix::{
197        Fee, Note, Payload as PhoenixPayload, PublicKey as PhoenixPublicKey,
198        SecretKey as PhoenixSecretKey, Transaction as PhoenixTransaction,
199        TxSkeleton,
200    };
201    use dusk_core::{BlsScalar, JubJubScalar};
202    use rand::Rng;
203
204    use super::*;
205    use crate::ledger::Dummy;
206
207    impl<T> Dummy<T> for Transaction {
208        fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
209            gen_dummy_tx(1_000_000)
210        }
211    }
212
213    impl<T> Dummy<T> for SpentTransaction {
214        fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
215            let tx = gen_dummy_tx(1_000_000);
216            SpentTransaction {
217                inner: tx,
218                block_height: 0,
219                gas_spent: 3,
220                err: Some("error".to_string()),
221            }
222        }
223    }
224
225    /// Generates a decodable transaction from a fixed blob with a specified
226    /// gas price.
227    pub fn gen_dummy_tx(gas_price: u64) -> Transaction {
228        let pk = PhoenixPublicKey::from(&PhoenixSecretKey::new(
229            JubJubScalar::from(42u64),
230            JubJubScalar::from(42u64),
231        ));
232        let gas_limit = 1;
233
234        let fee = Fee::deterministic(
235            &JubJubScalar::from(5u64),
236            &pk,
237            gas_limit,
238            gas_price,
239            &[JubJubScalar::from(9u64), JubJubScalar::from(10u64)],
240        );
241
242        let tx_skeleton = TxSkeleton {
243            root: BlsScalar::from(12345u64),
244            nullifiers: vec![
245                BlsScalar::from(1u64),
246                BlsScalar::from(2u64),
247                BlsScalar::from(3u64),
248            ],
249            outputs: [Note::empty(), Note::empty()],
250            max_fee: gas_price * gas_limit,
251            deposit: 0,
252        };
253
254        let contract_call = ContractCall::new([21; 32], "some_method");
255
256        let payload = PhoenixPayload {
257            chain_id: 0xFA,
258            tx_skeleton,
259            fee,
260            data: Some(TransactionData::Call(contract_call)),
261        };
262        let proof = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
263
264        let tx: ProtocolTransaction =
265            PhoenixTransaction::from_payload_and_proof(payload, proof).into();
266
267        tx.into()
268    }
269}