openzeppelin_monitor/models/blockchain/midnight/
transaction.rs1use 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#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
29pub enum RpcTransaction {
30 MidnightTransaction {
32 #[serde(skip)]
34 tx_raw: String,
35 tx: MidnightRpcTransaction,
37 },
38 MalformedMidnightTransaction,
40 Timestamp(u64),
42 RuntimeUpgrade,
44 UnknownTransaction,
46}
47
48#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
55pub enum Operation {
56 Call {
58 address: String,
60 entry_point: String,
62 },
63 Deploy {
65 address: String,
67 },
68 FallibleCoins,
70 GuaranteedCoins,
72 Maintain {
74 address: String,
76 },
77 ClaimMint {
79 value: u128,
81 coin_type: String,
83 },
84}
85
86#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
93pub struct MidnightRpcTransaction {
94 pub tx_hash: String,
96 pub operations: Vec<Operation>,
98 pub identifiers: Vec<String>,
100}
101
102#[derive(Debug, Serialize, Deserialize, Clone)]
108pub struct Transaction(pub MidnightRpcTransaction);
109
110impl Transaction {
111 pub fn hash(&self) -> &String {
116 &self.0.tx_hash
117 }
118
119 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 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 String::from_utf8(hex::decode(entry_point.clone()).unwrap_or_default())
153 .unwrap_or_default(),
154 ),
155 _ => None,
156 })
157 .collect()
158 }
159
160 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 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 fn from(tx: MidnightRpcTransaction) -> Self {
200 Self(tx)
201 }
202}
203
204impl From<Transaction> for MidnightRpcTransaction {
205 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 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 fn deref(&self) -> &Self::Target {
255 &self.0
256 }
257}
258
259impl
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 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 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 #[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 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 #[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 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 #[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 #[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 #[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}