Skip to main content

dusk_node_data/events/
transactions.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::collections::HashMap;
8
9use dusk_core::transfer::RefundAddress;
10
11use super::*;
12use crate::ledger::{Hash, LedgerTransaction, SpentTransaction};
13
14/// Represents events related to transactions.
15///
16/// - `Deferred(Hash, &'static str)`
17///
18///   Indicates that a transaction has been staged outside the real mempool and
19///   is waiting on an admission precondition such as a missing intermediate
20///   nonce. The `Hash` identifies the staged transaction and the string
21///   carries a compact reason.
22///
23/// - `Dropped(Hash, &'static str)`
24///
25///   Indicates that a transaction has been discarded before entering the real
26///   mempool. The `Hash` identifies the dropped transaction and the string
27///   carries a compact reason.
28///
29/// - `Removed(Hash)`
30///
31///   Indicates that a transaction has been removed from the mempool. The
32///   `Hash` represents the unique identifier of the removed transaction.
33///
34///   This event is triggered when a transaction is removed from the mempool
35///   or discarded from the mempool.
36///
37/// - `Included(&'t LedgerTransaction)`
38///
39///     A transaction has been included in the mempool.
40///
41/// - `Executed(&'t SpentTransaction)`
42///
43///   Denotes that a transaction has been executed into an accepted block.
44///   Executed transactions also include failed transaction, as they have been
45///   spent and were correctly executed according to any contract logic.
46///   (including logic that triggers panics)
47///
48///     - A "successful" transaction: executed and the `err` field is `None`.
49///     - A "failed" transaction: executed and the `err` field is `Some`.
50#[derive(Clone, Debug)]
51pub enum TransactionEvent<'t> {
52    Deferred(Hash, &'static str),
53    Dropped(Hash, &'static str),
54    Removed(Hash),
55    Included(&'t LedgerTransaction),
56    Executed(&'t SpentTransaction),
57}
58
59impl EventSource for TransactionEvent<'_> {
60    const COMPONENT: &'static str = "transactions";
61
62    fn topic(&self) -> &'static str {
63        match self {
64            Self::Deferred(_, _) => "deferred",
65            Self::Dropped(_, _) => "dropped",
66            Self::Removed(_) => "removed",
67            Self::Executed(_) => "executed",
68            Self::Included(_) => "included",
69        }
70    }
71    fn data(&self) -> Option<serde_json::Value> {
72        match self {
73            Self::Deferred(_, reason) | Self::Dropped(_, reason) => {
74                Some(serde_json::json!({ "reason": reason }))
75            }
76            Self::Removed(_) => None,
77            Self::Executed(t) => serde_json::to_value(t).ok(),
78            Self::Included(t) => serde_json::to_value(t).ok(),
79        }
80    }
81    fn entity(&self) -> String {
82        let hash = match self {
83            Self::Deferred(hash, _)
84            | Self::Dropped(hash, _)
85            | Self::Removed(hash) => *hash,
86            Self::Executed(tx) => tx.inner.id(),
87            Self::Included(tx) => tx.id(),
88        };
89        hex::encode(hash)
90    }
91}
92use base64::Engine;
93use base64::engine::general_purpose::STANDARD as BASE64_ENGINE;
94use dusk_bytes::Serializable;
95use dusk_core::transfer::Transaction as ProtocolTransaction;
96use serde::ser::{Serialize, SerializeStruct, Serializer};
97
98impl Serialize for LedgerTransaction {
99    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
100    where
101        S: Serializer,
102    {
103        let mut state = serializer.serialize_struct("Transaction", 1)?;
104        match self.protocol() {
105            ProtocolTransaction::Phoenix(p) => {
106                state.serialize_field("type", "phoenix")?;
107
108                let root = p.root().to_bytes();
109                state.serialize_field("root", &hex::encode(root))?;
110
111                let nullifiers: Vec<_> = p
112                    .nullifiers()
113                    .iter()
114                    .map(|n| hex::encode(n.to_bytes()))
115                    .collect();
116                state.serialize_field("nullifiers", &nullifiers)?;
117            }
118            ProtocolTransaction::Moonlight(m) => {
119                state.serialize_field("type", "moonlight")?;
120
121                let sender = m.sender();
122                let sender = bs58::encode(sender.to_bytes()).into_string();
123                state.serialize_field("sender", &sender)?;
124
125                let receiver = m.receiver().map(|receiver| {
126                    bs58::encode(receiver.to_bytes()).into_string()
127                });
128                state.serialize_field("receiver", &receiver)?;
129
130                state.serialize_field("value", &m.value())?;
131
132                state.serialize_field("nonce", &m.nonce())?;
133            }
134        }
135
136        let tx = self.protocol();
137
138        state.serialize_field("deposit", &tx.deposit())?;
139
140        let notes: Vec<Note<'_>> =
141            tx.outputs().iter().map(Note::from).collect();
142
143        if !notes.is_empty() {
144            state.serialize_field("outputs", &notes)?;
145        }
146
147        let fee = {
148            let mut fee = HashMap::new();
149            fee.insert("gas_limit", tx.gas_limit().to_string());
150            fee.insert("gas_price", tx.gas_price().to_string());
151
152            let encoded_address = match tx.refund_address() {
153                RefundAddress::Phoenix(address) => {
154                    bs58::encode(address.to_bytes()).into_string()
155                }
156                RefundAddress::Moonlight(address) => {
157                    bs58::encode(address.to_bytes()).into_string()
158                }
159            };
160            fee.insert("refund_address", encoded_address);
161            if let ProtocolTransaction::Phoenix(tx) = tx {
162                fee.insert(
163                    "phoenix sender",
164                    hex::encode(tx.sender().to_bytes()),
165                );
166            }
167
168            fee
169        };
170
171        state.serialize_field("fee", &fee)?;
172
173        let call = tx.call().map(|c| {
174            let mut call = HashMap::new();
175            call.insert("contract", hex::encode(c.contract));
176            call.insert("fn_name", c.fn_name.to_string());
177            call.insert("fn_args", BASE64_ENGINE.encode(&c.fn_args));
178            call
179        });
180        state.serialize_field("call", &call)?;
181
182        state.serialize_field("is_deploy", &tx.deploy().is_some())?;
183        state.serialize_field("memo", &tx.memo().map(hex::encode))?;
184        state.end()
185    }
186}
187
188struct Note<'a>(&'a dusk_core::transfer::phoenix::Note);
189
190impl<'a> From<&'a dusk_core::transfer::phoenix::Note> for Note<'a> {
191    fn from(value: &'a dusk_core::transfer::phoenix::Note) -> Self {
192        Self(value)
193    }
194}
195
196impl Serialize for Note<'_> {
197    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198    where
199        S: Serializer,
200    {
201        let mut state = serializer.serialize_struct("Note", 5)?;
202        let n = self.0;
203
204        state.serialize_field("type", &(n.note_type() as u8))?;
205
206        let commitment = [
207            hex::encode(n.value_commitment().get_u().to_bytes()),
208            hex::encode(n.value_commitment().get_v().to_bytes()),
209        ];
210        state.serialize_field("value_commitment", &commitment)?;
211
212        let stealth_address = n.stealth_address().to_bytes();
213        state.serialize_field(
214            "stealth_address",
215            &bs58::encode(stealth_address).into_string(),
216        )?;
217
218        state.serialize_field("value_enc", &hex::encode(n.value_enc()))?;
219        state.serialize_field("sender", &hex::encode(n.sender().to_bytes()))?;
220        state.end()
221    }
222}