1use 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#[derive(Debug, Clone, Serialize)]
52pub struct SpentTransaction {
53 pub inner: Transaction,
55 pub block_height: u64,
57 pub gas_spent: u64,
60 pub err: Option<String>,
63}
64
65impl SpentTransaction {
66 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 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 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 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 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}