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