openzeppelin_monitor/models/blockchain/midnight/
transaction.rs

1//! Midnight transaction data structures.
2//!
3//! This module provides data structures and implementations for working with Midnight blockchain transactions.
4//! It includes types for representing RPC transactions, operations, and transaction processing.
5//!
6//! Note: These structures are based on the Midnight RPC implementation:
7//! <https://github.com/midnightntwrk/midnight-node/blob/39dbdf54afc5f0be7e7913b387637ac52d0c50f2/pallets/midnight/rpc/src/lib.rs>
8
9use alloy::hex::ToHexExt;
10use midnight_ledger::structure::{ContractAction, Transaction as MidnightNodeTransaction};
11
12use midnight_storage::DefaultDB;
13
14use serde::{Deserialize, Serialize};
15use std::ops::Deref;
16
17use crate::{
18	models::{ChainConfiguration, SecretValue},
19	services::filter::midnight_helpers::process_transaction_for_coins,
20};
21
22/// Represents a Midnight RPC transaction Enum
23///
24/// This enum represents different types of transactions that can be received from the Midnight RPC.
25/// It includes standard Midnight transactions, malformed transactions, timestamps, and runtime upgrades.
26///
27/// <https://github.com/midnightntwrk/midnight-node/blob/39dbdf54afc5f0be7e7913b387637ac52d0c50f2/pallets/midnight/rpc/src/lib.rs#L200-L211>
28#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
29pub enum RpcTransaction {
30	/// A standard Midnight transaction with raw data and parsed transaction information
31	MidnightTransaction {
32		/// Raw transaction data (serialized as string)
33		#[serde(skip)]
34		tx_raw: String,
35		/// Parsed transaction information
36		tx: MidnightRpcTransaction,
37	},
38	/// A transaction that could not be properly parsed
39	MalformedMidnightTransaction,
40	/// A timestamp transaction
41	Timestamp(u64),
42	/// A runtime upgrade transaction
43	RuntimeUpgrade,
44	/// An unknown transaction type
45	UnknownTransaction,
46}
47
48/// Represents a Midnight transaction operations
49///
50/// This enum defines the various operations that can be performed in a Midnight transaction,
51/// including contract calls, deployments, coin operations, and maintenance actions.
52///
53/// <https://github.com/midnightntwrk/midnight-node/blob/39dbdf54afc5f0be7e7913b387637ac52d0c50f2/pallets/midnight/rpc/src/lib.rs#L185-L192>
54#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
55pub enum Operation {
56	/// A contract call operation with target address and entry point
57	Call {
58		/// The contract address to call
59		address: String,
60		/// The entry point to call (hex-encoded)
61		entry_point: String,
62	},
63	/// A contract deployment operation
64	Deploy {
65		/// The address where the contract is deployed
66		address: String,
67	},
68	/// A fallible coin operation
69	FallibleCoins,
70	/// A guaranteed coin operation
71	GuaranteedCoins,
72	/// A contract maintenance operation
73	Maintain {
74		/// The contract address to maintain
75		address: String,
76	},
77	/// A claim mint operation
78	ClaimMint {
79		/// The value to mint
80		value: u128,
81		/// The type of coin to mint
82		coin_type: String,
83	},
84}
85
86/// Represents a Midnight transaction
87///
88/// This struct contains the core information about a Midnight transaction,
89/// including its hash, operations, and identifiers.
90///
91/// <https://github.com/midnightntwrk/midnight-node/blob/39dbdf54afc5f0be7e7913b387637ac52d0c50f2/pallets/midnight/rpc/src/lib.rs#L194-L198>
92#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
93pub struct MidnightRpcTransaction {
94	/// The transaction hash
95	pub tx_hash: String,
96	/// The list of operations in the transaction
97	pub operations: Vec<Operation>,
98	/// The list of identifiers associated with the transaction
99	pub identifiers: Vec<String>,
100}
101
102/// Wrapper around MidnightRpcTransaction that provides additional functionality
103///
104/// This type implements convenience methods for working with Midnight transactions
105/// while maintaining compatibility with the RPC response format. It provides methods
106/// for accessing transaction details and processing transaction data.
107#[derive(Debug, Serialize, Deserialize, Clone)]
108pub struct Transaction(pub MidnightRpcTransaction);
109
110impl Transaction {
111	/// Get the transaction hash
112	///
113	/// # Returns
114	/// A reference to the transaction hash string
115	pub fn hash(&self) -> &String {
116		&self.0.tx_hash
117	}
118
119	/// Get the contract addresses involved in the transaction
120	///
121	/// This method extracts all contract addresses from Call, Deploy, and Maintain operations.
122	///
123	/// # Returns
124	/// A vector of contract addresses as strings
125	pub fn contract_addresses(&self) -> Vec<String> {
126		self.0
127			.operations
128			.iter()
129			.filter_map(|op| match op {
130				Operation::Call { address, .. } => Some(address.clone()),
131				Operation::Deploy { address, .. } => Some(address.clone()),
132				Operation::Maintain { address, .. } => Some(address.clone()),
133				_ => None,
134			})
135			.collect()
136	}
137
138	/// Get the contract entry points called in the transaction
139	///
140	/// This method extracts and decodes entry points from Call operations.
141	/// Entry points are decoded from hex to UTF-8 strings.
142	///
143	/// # Returns
144	/// A vector of decoded entry point strings
145	pub fn entry_points(&self) -> Vec<String> {
146		self.0
147			.operations
148			.iter()
149			.filter_map(|op| match op {
150				Operation::Call { entry_point, .. } => Some(
151					// Decode the entry point from hex to utf8
152					String::from_utf8(hex::decode(entry_point.clone()).unwrap_or_default())
153						.unwrap_or_default(),
154				),
155				_ => None,
156			})
157			.collect()
158	}
159
160	/// Get the contract addresses and their corresponding entry points
161	///
162	/// This method pairs contract addresses with their entry points for Call operations.
163	/// Entry points are decoded from hex to UTF-8 strings.
164	///
165	/// # Returns
166	/// A vector of (address, entry_point) pairs
167	pub fn contract_addresses_and_entry_points(&self) -> Vec<(String, String)> {
168		self.0
169			.operations
170			.iter()
171			.map(|op| match op {
172				Operation::Call {
173					address,
174					entry_point,
175					..
176				} => (
177					address.clone(),
178					// Decode the entry point from hex to utf8
179					String::from_utf8(hex::decode(entry_point.clone()).unwrap_or_default())
180						.unwrap_or_default(),
181				),
182				Operation::Deploy { address, .. } => (address.clone(), "".to_string()),
183				Operation::Maintain { address, .. } => (address.clone(), "".to_string()),
184				_ => ("".to_string(), "".to_string()),
185			})
186			.filter(|(addr, entry)| !addr.is_empty() && !entry.is_empty())
187			.collect()
188	}
189}
190
191impl From<MidnightRpcTransaction> for Transaction {
192	/// Creates a new Transaction from a MidnightRpcTransaction
193	///
194	/// # Arguments
195	/// * `tx` - The MidnightRpcTransaction to convert
196	///
197	/// # Returns
198	/// A new Transaction instance
199	fn from(tx: MidnightRpcTransaction) -> Self {
200		Self(tx)
201	}
202}
203
204impl From<Transaction> for MidnightRpcTransaction {
205	/// Converts a Transaction back into a MidnightRpcTransaction
206	///
207	/// # Arguments
208	/// * `tx` - The Transaction to convert
209	///
210	/// # Returns
211	/// The underlying MidnightRpcTransaction
212	fn from(tx: Transaction) -> Self {
213		tx.0
214	}
215}
216
217impl<P: midnight_ledger::structure::ProofKind<D>, D: midnight_storage::db::DB>
218	From<ContractAction<P, D>> for Operation
219{
220	/// Converts a ContractAction into an Operation
221	///
222	/// This implementation handles the conversion of different types of contract actions
223	/// into their corresponding Operation variants, including proper encoding of addresses
224	/// and entry points.
225	///
226	/// # Arguments
227	/// * `action` - The ContractAction to convert
228	///
229	/// # Returns
230	/// The corresponding Operation variant
231	fn from(action: ContractAction<P, D>) -> Self {
232		match action {
233			ContractAction::Call(call) => Operation::Call {
234				address: call.address.0 .0.encode_hex(),
235				entry_point: String::from_utf8_lossy(&call.entry_point.0).to_string(),
236			},
237			ContractAction::Deploy(deploy) => Operation::Deploy {
238				address: deploy.address().0 .0.encode_hex(),
239			},
240			ContractAction::Maintain(update) => Operation::Maintain {
241				address: update.address.0 .0.encode_hex(),
242			},
243		}
244	}
245}
246
247impl Deref for Transaction {
248	type Target = MidnightRpcTransaction;
249
250	/// Dereferences the Transaction to access the underlying MidnightRpcTransaction
251	///
252	/// # Returns
253	/// A reference to the underlying MidnightRpcTransaction
254	fn deref(&self) -> &Self::Target {
255		&self.0
256	}
257}
258
259// Specific implementation for DefaultDB to match the actual usage
260impl
261	TryFrom<(
262		Transaction,
263		Option<
264			MidnightNodeTransaction<
265				midnight_base_crypto::signatures::Signature,
266				(),
267				midnight_transient_crypto::commitment::Pedersen,
268				midnight_storage::DefaultDB,
269			>,
270		>,
271		&Vec<ChainConfiguration>,
272	)> for Transaction
273{
274	type Error = anyhow::Error;
275
276	fn try_from(
277		(block_tx, ledger_tx, chain_configurations): (
278			Transaction,
279			Option<
280				MidnightNodeTransaction<
281					midnight_base_crypto::signatures::Signature,
282					(),
283					midnight_transient_crypto::commitment::Pedersen,
284					midnight_storage::DefaultDB,
285				>,
286			>,
287			&Vec<ChainConfiguration>,
288		),
289	) -> Result<Self, Self::Error> {
290		// Check if chain_configuration has viewing keys and decrypt the transaction's coins
291		for chain_configuration in chain_configurations {
292			if let Some(midnight) = &chain_configuration.midnight {
293				for viewing_key in &midnight.viewing_keys {
294					if let SecretValue::Plain(secret) = viewing_key {
295						let viewing_key_str = secret.as_str();
296						if let Some(ref ledger_tx) = ledger_tx {
297							// TODO: Do something with the coins...
298							let _ = process_transaction_for_coins::<DefaultDB>(
299								viewing_key_str,
300								ledger_tx,
301							);
302						}
303					}
304				}
305			}
306		}
307
308		Ok(block_tx)
309	}
310}
311
312#[cfg(test)]
313mod tests {
314	use super::*;
315
316	/// Tests the conversion from MidnightRpcTransaction to Transaction
317	#[test]
318	fn test_transaction_from_rpc_transaction() {
319		let tx_info = MidnightRpcTransaction {
320			tx_hash: "test_hash".to_string(),
321			operations: vec![Operation::Call {
322				address: "0x1234567890abcdef".to_string(),
323				entry_point: "0x1234567890abcdef".to_string(),
324			}],
325			identifiers: vec!["0x1234567890abcdef".to_string()],
326		};
327
328		let transaction = Transaction::from(tx_info);
329
330		// Verify the transaction was created
331		assert_eq!(transaction.hash(), "test_hash");
332		assert_eq!(
333			transaction.operations,
334			vec![Operation::Call {
335				address: "0x1234567890abcdef".to_string(),
336				entry_point: "0x1234567890abcdef".to_string(),
337			}]
338		);
339		assert_eq!(
340			transaction.identifiers,
341			vec!["0x1234567890abcdef".to_string()]
342		);
343	}
344
345	/// Tests the Deref implementation for Transaction
346	#[test]
347	fn test_transaction_deref() {
348		let tx_info = MidnightRpcTransaction {
349			tx_hash: "test_hash".to_string(),
350			operations: vec![Operation::Call {
351				address: "0x1234567890abcdef".to_string(),
352				entry_point: "0x1234567890abcdef".to_string(),
353			}],
354			identifiers: vec!["0x1234567890abcdef".to_string()],
355		};
356
357		let transaction = Transaction::from(tx_info);
358
359		// Test that we can access MidnightRpcTransaction fields through deref
360		assert_eq!(transaction.tx_hash, "test_hash");
361		assert_eq!(
362			transaction.operations,
363			vec![Operation::Call {
364				address: "0x1234567890abcdef".to_string(),
365				entry_point: "0x1234567890abcdef".to_string(),
366			}]
367		);
368		assert_eq!(
369			transaction.identifiers,
370			vec!["0x1234567890abcdef".to_string()]
371		);
372	}
373
374	/// Tests the contract_addresses method
375	#[test]
376	fn test_contract_addresses() {
377		let tx_info = MidnightRpcTransaction {
378			tx_hash: "test_hash".to_string(),
379			operations: vec![
380				Operation::Call {
381					address: "0x123".to_string(),
382					entry_point: "656E74727931".to_string(),
383				},
384				Operation::Deploy {
385					address: "0x456".to_string(),
386				},
387				Operation::Maintain {
388					address: "0x789".to_string(),
389				},
390				Operation::GuaranteedCoins,
391			],
392			identifiers: vec![],
393		};
394
395		let transaction = Transaction::from(tx_info);
396		let addresses = transaction.contract_addresses();
397
398		assert_eq!(addresses.len(), 3);
399		assert!(addresses.contains(&"0x123".to_string()));
400		assert!(addresses.contains(&"0x456".to_string()));
401		assert!(addresses.contains(&"0x789".to_string()));
402	}
403
404	/// Tests the entry_points method
405	#[test]
406	fn test_entry_points() {
407		let tx_info = MidnightRpcTransaction {
408			tx_hash: "test_hash".to_string(),
409			operations: vec![
410				Operation::Call {
411					address: "0x123".to_string(),
412					entry_point: "656E74727931".to_string(),
413				},
414				Operation::Call {
415					address: "0x456".to_string(),
416					entry_point: "656E74727932".to_string(),
417				},
418				Operation::Deploy {
419					address: "0x789".to_string(),
420				},
421			],
422			identifiers: vec![],
423		};
424
425		let transaction = Transaction::from(tx_info);
426		let entry_points = transaction.entry_points();
427
428		assert_eq!(entry_points.len(), 2);
429		assert!(entry_points.contains(&"entry1".to_string()));
430		assert!(entry_points.contains(&"entry2".to_string()));
431	}
432
433	/// Tests the contract_addresses_and_entry_points method
434	#[test]
435	fn test_contract_addresses_and_entry_points() {
436		let tx_info = MidnightRpcTransaction {
437			tx_hash: "test_hash".to_string(),
438			operations: vec![
439				Operation::Call {
440					address: "0x123".to_string(),
441					entry_point: "656E74727931".to_string(),
442				},
443				Operation::Call {
444					address: "0x456".to_string(),
445					entry_point: "656E74727932".to_string(),
446				},
447				Operation::Deploy {
448					address: "0x789".to_string(),
449				},
450				Operation::GuaranteedCoins,
451			],
452			identifiers: vec![],
453		};
454
455		let transaction = Transaction::from(tx_info);
456
457		let pairs = transaction.contract_addresses_and_entry_points();
458
459		assert_eq!(pairs.len(), 2);
460		assert!(pairs.contains(&("0x123".to_string(), "entry1".to_string())));
461		assert!(pairs.contains(&("0x456".to_string(), "entry2".to_string())));
462	}
463}