use crate::entities::{
CardanoTransactionsSetProof, ProtocolMessage, ProtocolMessagePartKey, TransactionHash,
};
use crate::messages::CardanoTransactionsSetProofMessagePart;
use crate::StdError;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(target_family = "wasm")]
use wasm_bindgen::prelude::*;
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[cfg_attr(
target_family = "wasm",
wasm_bindgen(getter_with_clone, js_name = "CardanoTransactionsProofs")
)]
pub struct CardanoTransactionsProofsMessage {
pub certificate_hash: String,
pub certified_transactions: Vec<CardanoTransactionsSetProofMessagePart>,
pub non_certified_transactions: Vec<TransactionHash>,
pub latest_immutable_file_number: u64,
}
#[cfg_attr(
target_family = "wasm",
wasm_bindgen(js_class = "CardanoTransactionsProofs")
)]
impl CardanoTransactionsProofsMessage {
#[cfg_attr(target_family = "wasm", wasm_bindgen(getter))]
pub fn transactions_hashes(&self) -> Vec<TransactionHash> {
self.certified_transactions
.iter()
.flat_map(|ct| ct.transactions_hashes.clone())
.collect::<Vec<_>>()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VerifiedCardanoTransactions {
certificate_hash: String,
merkle_root: String,
certified_transactions: Vec<TransactionHash>,
latest_immutable_file_number: u64,
}
impl VerifiedCardanoTransactions {
pub fn certificate_hash(&self) -> &str {
&self.certificate_hash
}
pub fn certified_transactions(&self) -> &[TransactionHash] {
&self.certified_transactions
}
pub fn fill_protocol_message(&self, message: &mut ProtocolMessage) {
message.set_message_part(
ProtocolMessagePartKey::CardanoTransactionsMerkleRoot,
self.merkle_root.clone(),
);
message.set_message_part(
ProtocolMessagePartKey::LatestImmutableFileNumber,
self.latest_immutable_file_number.to_string(),
);
}
}
#[derive(Error, Debug)]
pub enum VerifyCardanoTransactionsProofsError {
#[error("Invalid set proof for transactions hashes: {transactions_hashes:?}")]
InvalidSetProof {
transactions_hashes: Vec<TransactionHash>,
source: StdError,
},
#[error("There's no certified transaction to verify")]
NoCertifiedTransaction,
#[error("All certified transactions set proofs must share the same Merkle root")]
NonMatchingMerkleRoot,
#[error("Malformed data or unknown Cardano Set Proof format")]
MalformedData(#[source] StdError),
}
impl CardanoTransactionsProofsMessage {
pub fn new(
certificate_hash: &str,
certified_transactions: Vec<CardanoTransactionsSetProofMessagePart>,
non_certified_transactions: Vec<TransactionHash>,
latest_immutable_file_number: u64,
) -> Self {
Self {
certificate_hash: certificate_hash.to_string(),
certified_transactions,
non_certified_transactions,
latest_immutable_file_number,
}
}
pub fn verify(
&self,
) -> Result<VerifiedCardanoTransactions, VerifyCardanoTransactionsProofsError> {
let mut merkle_root = None;
for certified_transaction in &self.certified_transactions {
let certified_transaction: CardanoTransactionsSetProof = certified_transaction
.clone()
.try_into()
.map_err(VerifyCardanoTransactionsProofsError::MalformedData)?;
certified_transaction.verify().map_err(|e| {
VerifyCardanoTransactionsProofsError::InvalidSetProof {
transactions_hashes: certified_transaction.transactions_hashes().to_vec(),
source: e,
}
})?;
let tx_merkle_root = Some(certified_transaction.merkle_root());
if merkle_root.is_none() {
merkle_root = tx_merkle_root;
} else if merkle_root != tx_merkle_root {
return Err(VerifyCardanoTransactionsProofsError::NonMatchingMerkleRoot);
}
}
Ok(VerifiedCardanoTransactions {
certificate_hash: self.certificate_hash.clone(),
merkle_root: merkle_root
.ok_or(VerifyCardanoTransactionsProofsError::NoCertifiedTransaction)?,
certified_transactions: self
.certified_transactions
.iter()
.flat_map(|c| c.transactions_hashes.clone())
.collect(),
latest_immutable_file_number: self.latest_immutable_file_number,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto_helper::MKProof;
#[test]
fn verify_malformed_proofs_fail() {
let txs_proofs = CardanoTransactionsProofsMessage::new(
"whatever",
vec![CardanoTransactionsSetProofMessagePart {
transactions_hashes: vec![],
proof: "invalid".to_string(),
}],
vec![],
99999,
);
let error = txs_proofs
.verify()
.expect_err("Malformed txs proofs should fail to verify itself");
assert!(
matches!(
error,
VerifyCardanoTransactionsProofsError::MalformedData(_)
),
"Expected 'MalformedData' error but got '{:?}'",
error
);
}
#[test]
fn verify_no_certified_transaction_fail() {
let txs_proofs = CardanoTransactionsProofsMessage::new("whatever", vec![], vec![], 99999);
let error = txs_proofs
.verify()
.expect_err("Proofs without certified transactions should fail to verify itself");
assert!(
matches!(
error,
VerifyCardanoTransactionsProofsError::NoCertifiedTransaction
),
"Expected 'NoCertifiedTransactions' error but got '{:?}'",
error
);
}
#[test]
fn verify_valid_proofs() {
let set_proof = CardanoTransactionsSetProof::dummy();
let expected = VerifiedCardanoTransactions {
certificate_hash: "whatever".to_string(),
merkle_root: set_proof.merkle_root(),
certified_transactions: set_proof.transactions_hashes().to_vec(),
latest_immutable_file_number: 99999,
};
let txs_proofs = CardanoTransactionsProofsMessage::new(
"whatever",
vec![set_proof.try_into().unwrap()],
vec![],
99999,
);
let verified_txs = txs_proofs
.verify()
.expect("Valid txs proofs should verify itself");
assert_eq!(expected, verified_txs);
}
#[test]
fn verify_invalid_proofs() {
let set_proof = CardanoTransactionsSetProof::new(
vec!["invalid1".to_string()],
MKProof::from_leaves(&["invalid2"]).unwrap(),
);
let txs_proofs = CardanoTransactionsProofsMessage::new(
"whatever",
vec![set_proof.try_into().unwrap()],
vec![],
99999,
);
let error = txs_proofs
.verify()
.expect_err("Invalid txs proofs should fail to verify itself");
assert!(
matches!(
error,
VerifyCardanoTransactionsProofsError::InvalidSetProof { .. },
),
"Expected 'InvalidSetProof' error but got '{:?}'",
error
);
}
#[test]
fn verify_valid_proof_with_different_merkle_root_fail() {
let set_proofs = vec![
CardanoTransactionsSetProof::new(
vec!["tx-1".to_string()],
MKProof::from_leaves(&["tx-1"]).unwrap(),
),
CardanoTransactionsSetProof::new(
vec!["tx-2".to_string()],
MKProof::from_leaves(&["tx-2"]).unwrap(),
),
];
let txs_proofs = CardanoTransactionsProofsMessage::new(
"whatever",
set_proofs
.into_iter()
.map(|p| p.try_into().unwrap())
.collect(),
vec![],
99999,
);
let error = txs_proofs
.verify()
.expect_err("Txs proofs with non matching merkle root should fail to verify itself");
assert!(
matches!(
error,
VerifyCardanoTransactionsProofsError::NonMatchingMerkleRoot { .. },
),
"Expected 'NonMatchingMerkleRoot' error but got '{:?}'",
error
);
}
#[cfg(feature = "fs")]
mod fs_only {
use crate::crypto_helper::{MKMap, MKMapNode};
use crate::entities::{BlockRange, CardanoDbBeacon, CardanoTransaction};
use crate::signable_builder::{
CardanoTransactionsSignableBuilder, MockBlockRangeRootRetriever,
MockTransactionsImporter, SignableBuilder,
};
use slog::Logger;
use std::sync::Arc;
use super::*;
#[tokio::test]
async fn verify_hashes_from_verified_cardano_transaction_and_from_signable_builder_are_equals(
) {
let transactions = vec![
CardanoTransaction::new("tx-hash-123", 10, 1, "block_hash", 1),
CardanoTransaction::new("tx-hash-456", 20, 2, "block_hash", 1),
];
assert_eq!(
from_verified_cardano_transaction(&transactions, 99999).compute_hash(),
from_signable_builder(&transactions, 99999)
.await
.compute_hash()
);
assert_ne!(
from_verified_cardano_transaction(&transactions, 99999).compute_hash(),
from_signable_builder(&transactions, 123456)
.await
.compute_hash()
);
}
fn from_verified_cardano_transaction(
transactions: &[CardanoTransaction],
immutable_file_number: u64,
) -> ProtocolMessage {
let set_proof = CardanoTransactionsSetProof::from_leaves(
transactions
.iter()
.map(|t| (t.block_number, t.transaction_hash.clone()))
.collect::<Vec<_>>()
.as_slice(),
)
.unwrap();
let verified_transactions_fake = VerifiedCardanoTransactions {
certificate_hash: "whatever".to_string(),
merkle_root: set_proof.merkle_root(),
certified_transactions: set_proof.transactions_hashes().to_vec(),
latest_immutable_file_number: immutable_file_number,
};
let mut message = ProtocolMessage::new();
verified_transactions_fake.fill_protocol_message(&mut message);
message
}
async fn from_signable_builder(
transactions: &[CardanoTransaction],
immutable_file_number: u64,
) -> ProtocolMessage {
let mut transaction_importer = MockTransactionsImporter::new();
transaction_importer
.expect_import()
.return_once(move |_| Ok(()));
let mut block_range_root_retriever = MockBlockRangeRootRetriever::new();
let transactions_imported = transactions.to_vec();
block_range_root_retriever
.expect_compute_merkle_map_from_block_range_roots()
.return_once(move |_| {
MKMap::<BlockRange, MKMapNode<BlockRange>>::new_from_iter(
transactions_imported.into_iter().map(|tx| {
(
BlockRange::from_block_number(tx.block_number),
MKMapNode::TreeNode(tx.transaction_hash.clone().into()),
)
}),
)
});
let cardano_transaction_signable_builder = CardanoTransactionsSignableBuilder::new(
Arc::new(transaction_importer),
Arc::new(block_range_root_retriever),
Logger::root(slog::Discard, slog::o!()),
);
cardano_transaction_signable_builder
.compute_protocol_message(CardanoDbBeacon {
immutable_file_number,
..CardanoDbBeacon::default()
})
.await
.unwrap()
}
}
}