1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4use crate::StdError;
5use crate::entities::{
6 BlockNumber, CardanoTransactionsSetProof, ProtocolMessage, ProtocolMessagePartKey,
7 TransactionHash,
8};
9use crate::messages::CardanoTransactionsSetProofMessagePart;
10
11#[cfg(target_family = "wasm")]
12use wasm_bindgen::prelude::*;
13
14#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
16#[cfg_attr(
17 target_family = "wasm",
18 wasm_bindgen(getter_with_clone, js_name = "CardanoTransactionsProofs")
19)]
20pub struct CardanoTransactionsProofsMessage {
21 pub certificate_hash: String,
23
24 pub certified_transactions: Vec<CardanoTransactionsSetProofMessagePart>,
26
27 pub non_certified_transactions: Vec<TransactionHash>,
29
30 #[cfg_attr(target_family = "wasm", wasm_bindgen(skip))]
32 pub latest_block_number: BlockNumber,
33}
34
35#[cfg(target_family = "wasm")]
36#[wasm_bindgen(js_class = "CardanoTransactionsProofs")]
37impl CardanoTransactionsProofsMessage {
38 #[wasm_bindgen(getter)]
40 pub fn latest_block_number(&self) -> u64 {
41 *self.latest_block_number
42 }
43}
44
45#[cfg_attr(
46 target_family = "wasm",
47 wasm_bindgen(js_class = "CardanoTransactionsProofs")
48)]
49impl CardanoTransactionsProofsMessage {
50 #[cfg_attr(target_family = "wasm", wasm_bindgen(getter))]
52 pub fn transactions_hashes(&self) -> Vec<TransactionHash> {
53 self.certified_transactions
54 .iter()
55 .flat_map(|ct| ct.transactions_hashes.clone())
56 .collect::<Vec<_>>()
57 }
58}
59
60#[derive(Debug, Clone, PartialEq)]
65pub struct VerifiedCardanoTransactions {
66 certificate_hash: String,
67 merkle_root: String,
68 certified_transactions: Vec<TransactionHash>,
69 latest_block_number: BlockNumber,
70}
71
72impl VerifiedCardanoTransactions {
73 pub fn certificate_hash(&self) -> &str {
75 &self.certificate_hash
76 }
77
78 pub fn certified_transactions(&self) -> &[TransactionHash] {
80 &self.certified_transactions
81 }
82
83 pub fn fill_protocol_message(&self, message: &mut ProtocolMessage) {
86 message.set_message_part(
87 ProtocolMessagePartKey::CardanoTransactionsMerkleRoot,
88 self.merkle_root.clone(),
89 );
90
91 message.set_message_part(
92 ProtocolMessagePartKey::LatestBlockNumber,
93 self.latest_block_number.to_string(),
94 );
95 }
96}
97
98#[derive(Error, Debug)]
100pub enum VerifyCardanoTransactionsProofsError {
101 #[error("Invalid set proof for transactions hashes: {transactions_hashes:?}")]
103 InvalidSetProof {
104 transactions_hashes: Vec<TransactionHash>,
106 source: StdError,
108 },
109
110 #[error("There's no certified transaction to verify")]
112 NoCertifiedTransaction,
113
114 #[error("All certified transactions set proofs must share the same Merkle root")]
119 NonMatchingMerkleRoot,
120
121 #[error("Malformed data or unknown Cardano Set Proof format")]
124 MalformedData(#[source] StdError),
125}
126
127impl CardanoTransactionsProofsMessage {
128 pub fn new(
130 certificate_hash: &str,
131 certified_transactions: Vec<CardanoTransactionsSetProofMessagePart>,
132 non_certified_transactions: Vec<TransactionHash>,
133 latest_block_number: BlockNumber,
134 ) -> Self {
135 Self {
136 certificate_hash: certificate_hash.to_string(),
137 certified_transactions,
138 non_certified_transactions,
139 latest_block_number,
140 }
141 }
142
143 pub fn verify(
155 &self,
156 ) -> Result<VerifiedCardanoTransactions, VerifyCardanoTransactionsProofsError> {
157 let mut merkle_root = None;
158
159 for certified_transaction in &self.certified_transactions {
160 let certified_transaction: CardanoTransactionsSetProof = certified_transaction
161 .clone()
162 .try_into()
163 .map_err(VerifyCardanoTransactionsProofsError::MalformedData)?;
164 certified_transaction.verify().map_err(|e| {
165 VerifyCardanoTransactionsProofsError::InvalidSetProof {
166 transactions_hashes: certified_transaction.transactions_hashes().to_vec(),
167 source: e,
168 }
169 })?;
170
171 let tx_merkle_root = Some(certified_transaction.merkle_root());
172
173 if merkle_root.is_none() {
174 merkle_root = tx_merkle_root;
175 } else if merkle_root != tx_merkle_root {
176 return Err(VerifyCardanoTransactionsProofsError::NonMatchingMerkleRoot);
177 }
178 }
179
180 Ok(VerifiedCardanoTransactions {
181 certificate_hash: self.certificate_hash.clone(),
182 merkle_root: merkle_root
183 .ok_or(VerifyCardanoTransactionsProofsError::NoCertifiedTransaction)?,
184 certified_transactions: self
185 .certified_transactions
186 .iter()
187 .flat_map(|c| c.transactions_hashes.clone())
188 .collect(),
189 latest_block_number: self.latest_block_number,
190 })
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use std::sync::Arc;
197
198 use crate::crypto_helper::{MKMap, MKMapNode, MKProof, MKTreeStoreInMemory};
199 use crate::entities::{BlockNumber, BlockRange, CardanoTransaction, SlotNumber};
200 use crate::signable_builder::{
201 CardanoTransactionsSignableBuilder, MockLegacyBlockRangeRootRetriever,
202 MockTransactionsImporter, SignableBuilder,
203 };
204 use crate::test::crypto_helper::MKProofTestExtension;
205 use crate::test::double::Dummy;
206 use crate::test::entities_extensions::CardanoTransactionsSetProofTestExtension;
207
208 use super::*;
209
210 #[test]
211 fn verify_malformed_proofs_fail() {
212 let txs_proofs = CardanoTransactionsProofsMessage::new(
213 "whatever",
214 vec![CardanoTransactionsSetProofMessagePart {
215 transactions_hashes: vec![],
216 proof: "invalid".to_string(),
217 }],
218 vec![],
219 BlockNumber(99999),
220 );
221
222 let error = txs_proofs
223 .verify()
224 .expect_err("Malformed txs proofs should fail to verify itself");
225 assert!(
226 matches!(
227 error,
228 VerifyCardanoTransactionsProofsError::MalformedData(_)
229 ),
230 "Expected 'MalformedData' error but got '{error:?}'"
231 );
232 }
233
234 #[test]
235 fn verify_no_certified_transaction_fail() {
236 let txs_proofs =
237 CardanoTransactionsProofsMessage::new("whatever", vec![], vec![], BlockNumber(99999));
238
239 let error = txs_proofs
240 .verify()
241 .expect_err("Proofs without certified transactions should fail to verify itself");
242 assert!(
243 matches!(
244 error,
245 VerifyCardanoTransactionsProofsError::NoCertifiedTransaction
246 ),
247 "Expected 'NoCertifiedTransactions' error but got '{error:?}'"
248 );
249 }
250
251 #[test]
252 fn verify_valid_proofs() {
253 let set_proof = CardanoTransactionsSetProof::dummy();
254 let expected = VerifiedCardanoTransactions {
255 certificate_hash: "whatever".to_string(),
256 merkle_root: set_proof.merkle_root(),
257 certified_transactions: set_proof.transactions_hashes().to_vec(),
258 latest_block_number: BlockNumber(99999),
259 };
260 let txs_proofs = CardanoTransactionsProofsMessage::new(
261 "whatever",
262 vec![set_proof.try_into().unwrap()],
263 vec![],
264 BlockNumber(99999),
265 );
266
267 let verified_txs = txs_proofs.verify().expect("Valid txs proofs should verify itself");
268
269 assert_eq!(expected, verified_txs);
270 }
271
272 #[test]
273 fn verify_invalid_proofs() {
274 let set_proof = CardanoTransactionsSetProof::new(
275 vec!["invalid1".to_string()],
276 MKProof::from_leaves(&["invalid2"]).unwrap(),
277 );
278 let txs_proofs = CardanoTransactionsProofsMessage::new(
279 "whatever",
280 vec![set_proof.try_into().unwrap()],
281 vec![],
282 BlockNumber(99999),
283 );
284
285 let error = txs_proofs
286 .verify()
287 .expect_err("Invalid txs proofs should fail to verify itself");
288
289 assert!(
290 matches!(
291 error,
292 VerifyCardanoTransactionsProofsError::InvalidSetProof { .. },
293 ),
294 "Expected 'InvalidSetProof' error but got '{error:?}'"
295 );
296 }
297
298 #[test]
299 fn verify_valid_proof_with_different_merkle_root_fail() {
300 let set_proofs = vec![
301 CardanoTransactionsSetProof::new(
302 vec!["tx-1".to_string()],
303 MKProof::from_leaves(&["tx-1"]).unwrap(),
304 ),
305 CardanoTransactionsSetProof::new(
306 vec!["tx-2".to_string()],
307 MKProof::from_leaves(&["tx-2"]).unwrap(),
308 ),
309 ];
310 let txs_proofs = CardanoTransactionsProofsMessage::new(
311 "whatever",
312 set_proofs.into_iter().map(|p| p.try_into().unwrap()).collect(),
313 vec![],
314 BlockNumber(99999),
315 );
316
317 let error = txs_proofs
318 .verify()
319 .expect_err("Txs proofs with non matching merkle root should fail to verify itself");
320
321 assert!(
322 matches!(
323 error,
324 VerifyCardanoTransactionsProofsError::NonMatchingMerkleRoot,
325 ),
326 "Expected 'NonMatchingMerkleRoot' error but got '{error:?}'"
327 );
328 }
329
330 #[tokio::test]
331 async fn verify_hashes_from_verified_cardano_transaction_and_from_signable_builder_are_equals()
332 {
333 let transactions = vec![
334 CardanoTransaction::new("tx-hash-123", BlockNumber(10), SlotNumber(1), "block_hash"),
335 CardanoTransaction::new("tx-hash-456", BlockNumber(20), SlotNumber(2), "block_hash"),
336 ];
337
338 assert_eq!(
339 from_verified_cardano_transaction(&transactions, 99999).compute_hash(),
340 from_signable_builder(&transactions, BlockNumber(99999))
341 .await
342 .compute_hash()
343 );
344
345 assert_ne!(
346 from_verified_cardano_transaction(&transactions, 99999).compute_hash(),
347 from_signable_builder(&transactions, BlockNumber(123456))
348 .await
349 .compute_hash()
350 );
351 }
352
353 fn from_verified_cardano_transaction(
354 transactions: &[CardanoTransaction],
355 block_number: u64,
356 ) -> ProtocolMessage {
357 let set_proof = CardanoTransactionsSetProof::from_leaves::<MKTreeStoreInMemory>(
358 transactions
359 .iter()
360 .map(|t| (t.block_number, t.transaction_hash.clone()))
361 .collect::<Vec<_>>()
362 .as_slice(),
363 )
364 .unwrap();
365
366 let verified_transactions_fake = VerifiedCardanoTransactions {
367 certificate_hash: "whatever".to_string(),
368 merkle_root: set_proof.merkle_root(),
369 certified_transactions: set_proof.transactions_hashes().to_vec(),
370 latest_block_number: BlockNumber(block_number),
371 };
372
373 let mut message = ProtocolMessage::new();
374 verified_transactions_fake.fill_protocol_message(&mut message);
375
376 message
377 }
378
379 async fn from_signable_builder(
380 transactions: &[CardanoTransaction],
381 block_number: BlockNumber,
382 ) -> ProtocolMessage {
383 let mut transaction_importer = MockTransactionsImporter::new();
384 transaction_importer.expect_import().return_once(move |_| Ok(()));
385 let mut block_range_root_retriever = MockLegacyBlockRangeRootRetriever::new();
386
387 let transactions_imported = transactions.to_vec();
388 block_range_root_retriever
389 .expect_compute_merkle_map_from_block_range_roots()
390 .return_once(move |_| {
391 MKMap::<
392 BlockRange,
393 MKMapNode<BlockRange, MKTreeStoreInMemory>,
394 MKTreeStoreInMemory,
395 >::new_from_iter(transactions_imported.into_iter().map(
396 |tx| {
397 (
398 BlockRange::from_block_number(tx.block_number),
399 MKMapNode::TreeNode(tx.transaction_hash.clone().into()),
400 )
401 },
402 ))
403 });
404 let cardano_transaction_signable_builder = CardanoTransactionsSignableBuilder::new(
405 Arc::new(transaction_importer),
406 Arc::new(block_range_root_retriever),
407 );
408 cardano_transaction_signable_builder
409 .compute_protocol_message(block_number)
410 .await
411 .unwrap()
412 }
413}