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