1use std::collections::HashSet;
10
11use super::merkle_tree::{BadMerkleProof, MerkleBranch, MidpointProof};
12use crate::RewardsAddress;
13use evmlib::merkle_batch_payment::{CandidateNode, PoolCommitment, PoolHash};
14use evmlib::quoting_metrics::QuotingMetrics;
15use libp2p::{
16 PeerId,
17 identity::{Keypair, PublicKey},
18};
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21use xor_name::XorName;
22
23pub use evmlib::merkle_batch_payment::CANDIDATES_PER_POOL;
24
25#[derive(Debug, Error)]
27pub enum MerklePaymentVerificationError {
28 #[error("Winner pool hash mismatch: expected {expected:?}, got {got:?}")]
29 WinnerPoolHashMismatch { expected: PoolHash, got: PoolHash },
30 #[error("Merkle proof verification failed: {0}")]
31 MerkleProofFailed(#[from] BadMerkleProof),
32 #[error(
33 "Paid addresses not subset of candidate pool. Paid: {smart_contract_paid_node_addresses:?}, Candidates: {candidate_addresses:?}"
34 )]
35 PaidAddressesNotSubset {
36 smart_contract_paid_node_addresses: Vec<RewardsAddress>,
37 candidate_addresses: Vec<RewardsAddress>,
38 },
39 #[error("Wrong number of paid addresses: expected {expected}, got {got}")]
40 WrongPaidAddressCount { expected: usize, got: usize },
41 #[error("Invalid node signature for address {address}")]
42 InvalidNodeSignature { address: RewardsAddress },
43 #[error("Timestamp mismatch for node {address}: expected {expected}, got {got}")]
44 TimestampMismatch {
45 address: RewardsAddress,
46 expected: u64,
47 got: u64,
48 },
49 #[error("Pool commitment does not match the pool")]
50 CommitmentDoesNotMatchPool,
51 #[error("Paid node index {index} is out of bounds for pool size {pool_size}")]
52 PaidNodeIndexOutOfBounds { index: usize, pool_size: usize },
53 #[error("Address mismatch at index {index}: expected {expected}, got {actual}")]
54 PaidAddressMismatch {
55 index: usize,
56 expected: RewardsAddress,
57 actual: RewardsAddress,
58 },
59 #[error("Invalid node peer id for address {address} at index {index}")]
60 InvalidNodePeerId {
61 index: usize,
62 address: RewardsAddress,
63 },
64 #[error("Data type mismatch: expected {expected}, got {got} at node {address}")]
65 DataTypeMismatch {
66 address: RewardsAddress,
67 expected: u32,
68 got: u32,
69 },
70 #[error("Data size mismatch: expected {expected}, got {got} at node {address}")]
71 DataSizeMismatch {
72 address: RewardsAddress,
73 expected: usize,
74 got: usize,
75 },
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
86pub struct MerklePaymentCandidateNode {
87 pub pub_key: Vec<u8>,
90
91 pub quoting_metrics: QuotingMetrics,
93
94 pub reward_address: RewardsAddress,
96
97 pub merkle_payment_timestamp: u64,
99
100 pub signature: Vec<u8>,
102}
103
104impl MerklePaymentCandidateNode {
105 pub fn new(
116 keypair: &Keypair,
117 quoting_metrics: QuotingMetrics,
118 reward_address: RewardsAddress,
119 merkle_payment_timestamp: u64,
120 ) -> Result<Self, libp2p::identity::SigningError> {
121 let pub_key = keypair.public().encode_protobuf();
123
124 let msg = Self::bytes_to_sign("ing_metrics, &reward_address, merkle_payment_timestamp);
126 let signature = keypair.sign(&msg)?;
127
128 Ok(Self {
129 pub_key,
130 quoting_metrics,
131 reward_address,
132 merkle_payment_timestamp,
133 signature,
134 })
135 }
136
137 pub fn bytes_to_sign(
139 quoting_metrics: &QuotingMetrics,
140 reward_address: &RewardsAddress,
141 timestamp: u64,
142 ) -> Vec<u8> {
143 let mut bytes = Vec::new();
144 bytes.extend_from_slice("ing_metrics.to_bytes());
145 bytes.extend_from_slice(reward_address.as_slice());
146 bytes.extend_from_slice(×tamp.to_le_bytes());
147 bytes
148 }
149
150 fn to_bytes(&self) -> Vec<u8> {
152 let mut bytes = Vec::new();
153 bytes.extend_from_slice(&self.pub_key);
154 bytes.extend_from_slice(&self.quoting_metrics.to_bytes());
155 bytes.extend_from_slice(self.reward_address.as_slice());
156 bytes.extend_from_slice(&self.merkle_payment_timestamp.to_le_bytes());
157 bytes.extend_from_slice(&self.signature);
158 bytes
159 }
160
161 pub fn peer_id(&self) -> Result<PeerId, libp2p::identity::DecodingError> {
163 PublicKey::try_decode_protobuf(&self.pub_key).map(|pk| pk.to_peer_id())
164 }
165
166 pub fn verify_signature(&self) -> bool {
168 let pub_key = match PublicKey::try_decode_protobuf(&self.pub_key) {
169 Ok(pk) => pk,
170 Err(_) => return false,
171 };
172
173 let msg = Self::bytes_to_sign(
174 &self.quoting_metrics,
175 &self.reward_address,
176 self.merkle_payment_timestamp,
177 );
178 pub_key.verify(&msg, &self.signature)
179 }
180}
181
182#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
184pub struct MerklePaymentCandidatePool {
185 pub midpoint_proof: MidpointProof,
187
188 pub candidate_nodes: [MerklePaymentCandidateNode; CANDIDATES_PER_POOL],
191}
192
193impl MerklePaymentCandidatePool {
194 pub fn hash(&self) -> PoolHash {
196 let mut bytes = Vec::new();
197
198 bytes.extend_from_slice(self.midpoint_proof.hash().as_ref());
200
201 bytes.extend_from_slice(&(self.candidate_nodes.len() as u32).to_le_bytes());
203
204 for node in &self.candidate_nodes {
206 bytes.extend_from_slice(&node.to_bytes());
207 }
208
209 XorName::from_content(&bytes).0
210 }
211
212 pub fn to_commitment(&self) -> PoolCommitment {
214 let candidates: [CandidateNode; CANDIDATES_PER_POOL] =
215 self.candidate_nodes.clone().map(|node| CandidateNode {
216 rewards_address: node.reward_address,
217 metrics: node.quoting_metrics.clone(),
218 });
219
220 PoolCommitment {
221 pool_hash: self.hash(),
222 candidates,
223 }
224 }
225
226 pub fn verify_commitment(
230 &self,
231 commitment: &PoolCommitment,
232 merkle_payment_timestamp: u64,
233 ) -> Result<(), MerklePaymentVerificationError> {
234 self.verify_signatures(merkle_payment_timestamp)?;
235 let expected_commitment = self.to_commitment();
236 if commitment != &expected_commitment {
237 return Err(MerklePaymentVerificationError::CommitmentDoesNotMatchPool);
238 }
239 Ok(())
240 }
241
242 pub fn candidate_nodes_addresses(&self) -> HashSet<RewardsAddress> {
244 self.candidate_nodes
245 .iter()
246 .map(|node| node.reward_address)
247 .collect()
248 }
249
250 pub fn verify_signatures(
259 &self,
260 merkle_payment_timestamp: u64,
261 ) -> Result<(), MerklePaymentVerificationError> {
262 for node in &self.candidate_nodes {
264 if !node.verify_signature() {
265 return Err(MerklePaymentVerificationError::InvalidNodeSignature {
266 address: node.reward_address,
267 });
268 }
269 }
270
271 for node in &self.candidate_nodes {
273 if node.merkle_payment_timestamp != merkle_payment_timestamp {
274 return Err(MerklePaymentVerificationError::TimestampMismatch {
275 address: node.reward_address,
276 expected: merkle_payment_timestamp,
277 got: node.merkle_payment_timestamp,
278 });
279 }
280 }
281
282 if let Some(first_node) = self.candidate_nodes.first() {
285 let expected_data_type = first_node.quoting_metrics.data_type;
286 let expected_data_size = first_node.quoting_metrics.data_size;
287
288 for node in &self.candidate_nodes[..] {
289 if node.quoting_metrics.data_type != expected_data_type {
290 return Err(MerklePaymentVerificationError::DataTypeMismatch {
291 address: node.reward_address,
292 expected: expected_data_type,
293 got: node.quoting_metrics.data_type,
294 });
295 }
296
297 if node.quoting_metrics.data_size != expected_data_size {
298 return Err(MerklePaymentVerificationError::DataSizeMismatch {
299 address: node.reward_address,
300 expected: expected_data_size,
301 got: node.quoting_metrics.data_size,
302 });
303 }
304 }
305 }
306
307 Ok(())
308 }
309}
310
311#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
318pub struct MerklePaymentProof {
319 pub address: XorName,
321
322 pub data_proof: MerkleBranch,
324
325 pub winner_pool: MerklePaymentCandidatePool,
328}
329
330impl MerklePaymentProof {
331 pub fn new(
333 address: XorName,
334 data_proof: MerkleBranch,
335 winner_pool: MerklePaymentCandidatePool,
336 ) -> Self {
337 Self {
338 address,
339 data_proof,
340 winner_pool,
341 }
342 }
343
344 pub fn winner_pool_hash(&self) -> PoolHash {
346 self.winner_pool.hash()
347 }
348
349 pub fn corresponding_peer_ids(
363 &self,
364 paid_nodes: &[(RewardsAddress, usize)],
365 ) -> Result<Vec<PeerId>, MerklePaymentVerificationError> {
366 let mut peer_ids = Vec::with_capacity(paid_nodes.len());
367
368 for (expected_address, index) in paid_nodes {
369 let node = self.winner_pool.candidate_nodes.get(*index).ok_or(
370 MerklePaymentVerificationError::PaidNodeIndexOutOfBounds {
371 index: *index,
372 pool_size: self.winner_pool.candidate_nodes.len(),
373 },
374 )?;
375
376 if node.reward_address != *expected_address {
378 return Err(MerklePaymentVerificationError::PaidAddressMismatch {
379 index: *index,
380 expected: *expected_address,
381 actual: node.reward_address,
382 });
383 }
384
385 let peer_id =
387 node.peer_id()
388 .map_err(|_| MerklePaymentVerificationError::InvalidNodePeerId {
389 address: node.reward_address,
390 index: *index,
391 })?;
392
393 peer_ids.push(peer_id);
394 }
395
396 Ok(peer_ids)
397 }
398
399 pub fn verify(
407 &self,
408 smart_contract_depth: u8,
409 smart_contract_timestamp: u64,
410 smart_contract_pool_hash: &PoolHash,
411 smart_contract_paid_nodes: &[(RewardsAddress, usize)],
412 ) -> Result<(), MerklePaymentVerificationError> {
413 self.winner_pool
415 .verify_signatures(smart_contract_timestamp)?;
416
417 let actual_hash = self.winner_pool.hash();
419 if actual_hash != *smart_contract_pool_hash {
420 return Err(MerklePaymentVerificationError::WinnerPoolHashMismatch {
421 expected: *smart_contract_pool_hash,
422 got: actual_hash,
423 });
424 }
425 let smart_contract_root = self.winner_pool.midpoint_proof.root();
426
427 crate::merkle_payments::verify_merkle_proof(
429 &self.address,
430 &self.data_proof,
431 &self.winner_pool.midpoint_proof,
432 smart_contract_depth,
433 smart_contract_root,
434 smart_contract_timestamp,
435 )?;
436
437 if smart_contract_paid_nodes.len() != smart_contract_depth as usize {
439 return Err(MerklePaymentVerificationError::WrongPaidAddressCount {
440 expected: smart_contract_depth as usize,
441 got: smart_contract_paid_nodes.len(),
442 });
443 }
444
445 self.corresponding_peer_ids(smart_contract_paid_nodes)?;
448
449 Ok(())
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use crate::merkle_payments::merkle_tree::MerkleTree;
457 use evmlib::quoting_metrics::QuotingMetrics;
458 use std::time::{SystemTime, UNIX_EPOCH};
459 use tempfile::TempDir;
460
461 fn make_test_addresses(count: usize) -> Vec<XorName> {
462 (0..count)
463 .map(|i| XorName::from_content(&i.to_le_bytes()))
464 .collect()
465 }
466
467 fn create_mock_quoting_metrics(node_id: usize) -> QuotingMetrics {
468 QuotingMetrics {
469 data_type: 0,
470 data_size: 4 * 1024 * 1024, close_records_stored: node_id * 100,
472 records_per_type: vec![],
473 max_records: 1000,
474 received_payment_count: node_id * 10,
475 live_time: 3600 + (node_id as u64),
476 network_density: Some([node_id as u8; 32]),
477 network_size: Some(1000),
478 }
479 }
480
481 fn create_test_candidate_nodes(
483 timestamp: u64,
484 ) -> [MerklePaymentCandidateNode; CANDIDATES_PER_POOL] {
485 std::array::from_fn(|i| {
486 let keypair = Keypair::generate_ed25519();
487 MerklePaymentCandidateNode::new(
488 &keypair,
489 create_mock_quoting_metrics(i),
490 RewardsAddress::from([i as u8; 20]),
491 timestamp,
492 )
493 .expect("Failed to create candidate node")
494 })
495 }
496
497 fn create_test_candidate_nodes_with_peer_ids(
499 timestamp: u64,
500 ) -> (
501 [MerklePaymentCandidateNode; CANDIDATES_PER_POOL],
502 Vec<PeerId>,
503 ) {
504 let mut peer_ids = Vec::with_capacity(CANDIDATES_PER_POOL);
505 let nodes = std::array::from_fn(|i| {
506 let keypair = Keypair::generate_ed25519();
507 let peer_id = keypair.public().to_peer_id();
508 let node = MerklePaymentCandidateNode::new(
509 &keypair,
510 create_mock_quoting_metrics(i),
511 RewardsAddress::from([i as u8; 20]),
512 timestamp,
513 )
514 .expect("Failed to create candidate node");
515 peer_ids.push(peer_id);
516 node
517 });
518 (nodes, peer_ids)
519 }
520
521 #[test]
522 fn test_candidate_node_constructor_and_signature() {
523 let keypair = Keypair::generate_ed25519();
525 let peer_id = keypair.public().to_peer_id();
526
527 let quoting_metrics = create_mock_quoting_metrics(42);
529 let reward_address = RewardsAddress::from([0x42; 20]);
530 let timestamp = SystemTime::now()
531 .duration_since(UNIX_EPOCH)
532 .unwrap()
533 .as_secs();
534
535 let node = MerklePaymentCandidateNode::new(
537 &keypair,
538 quoting_metrics.clone(),
539 reward_address,
540 timestamp,
541 )
542 .expect("Failed to create candidate node");
543
544 assert_eq!(
546 node.peer_id().expect("Failed to derive peer_id"),
547 peer_id,
548 "PeerId should match keypair"
549 );
550
551 assert!(
553 node.verify_signature(),
554 "Signature should be valid for the signed data"
555 );
556
557 assert_eq!(node.reward_address, reward_address);
559 assert_eq!(node.merkle_payment_timestamp, timestamp);
560 assert_eq!(
561 node.quoting_metrics.close_records_stored,
562 quoting_metrics.close_records_stored
563 );
564 }
565
566 #[test]
567 fn test_signature_verification_with_tampering() {
568 let keypair = Keypair::generate_ed25519();
570 let quoting_metrics = create_mock_quoting_metrics(1);
571 let reward_address = RewardsAddress::from([0x11; 20]);
572 let timestamp = SystemTime::now()
573 .duration_since(UNIX_EPOCH)
574 .unwrap()
575 .as_secs();
576
577 let mut node = MerklePaymentCandidateNode::new(
578 &keypair,
579 quoting_metrics.clone(),
580 reward_address,
581 timestamp,
582 )
583 .expect("Failed to create candidate node");
584
585 assert!(
587 node.verify_signature(),
588 "Original signature should be valid"
589 );
590
591 node.reward_address = RewardsAddress::from([0x22; 20]);
593 assert!(
594 !node.verify_signature(),
595 "Signature should fail after tampering with reward_address"
596 );
597
598 node.reward_address = reward_address;
600 node.quoting_metrics.close_records_stored = 999;
601 assert!(
602 !node.verify_signature(),
603 "Signature should fail after tampering with quoting_metrics"
604 );
605
606 node.quoting_metrics = quoting_metrics;
608 node.merkle_payment_timestamp = timestamp + 3600; assert!(
610 !node.verify_signature(),
611 "Signature should fail after tampering with timestamp"
612 );
613
614 let wrong_keypair = Keypair::generate_ed25519();
616 let wrong_node = MerklePaymentCandidateNode::new(
617 &wrong_keypair,
618 create_mock_quoting_metrics(2),
619 RewardsAddress::from([0x33; 20]),
620 timestamp,
621 )
622 .expect("Failed to create node with wrong keypair");
623
624 let original_signature = node.signature.clone();
626 node.signature = wrong_node.signature.clone();
627 assert!(
628 !node.verify_signature(),
629 "Signature from different keypair should fail"
630 );
631
632 node.signature = original_signature;
634 node.reward_address = reward_address;
635 node.merkle_payment_timestamp = timestamp;
636 assert!(
637 node.verify_signature(),
638 "Original signature should work after restoration"
639 );
640 }
641
642 #[test]
643 fn test_pool_commitment_verification() {
644 let timestamp = SystemTime::now()
645 .duration_since(UNIX_EPOCH)
646 .unwrap()
647 .as_secs();
648
649 let addresses = make_test_addresses(10);
651 let tree = MerkleTree::from_xornames(addresses).unwrap();
652
653 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
655 let reward_pool = &reward_candidates[0];
656
657 let candidate_nodes = create_test_candidate_nodes(timestamp);
659
660 let pool = MerklePaymentCandidatePool {
661 midpoint_proof: reward_pool.clone(),
662 candidate_nodes,
663 };
664
665 let commitment = pool.to_commitment();
667
668 assert!(
670 pool.verify_commitment(&commitment, timestamp).is_ok(),
671 "Commitment should verify against original pool"
672 );
673
674 assert_eq!(
676 commitment.pool_hash,
677 pool.hash(),
678 "Commitment pool_hash should match pool.hash()"
679 );
680
681 let expected_candidates: [CandidateNode; CANDIDATES_PER_POOL] =
683 pool.candidate_nodes.clone().map(|node| CandidateNode {
684 rewards_address: node.reward_address,
685 metrics: node.quoting_metrics.clone(),
686 });
687 assert_eq!(
688 commitment.candidates, expected_candidates,
689 "Commitment candidates should match pool nodes"
690 );
691 assert_eq!(
692 commitment.candidates.len(),
693 CANDIDATES_PER_POOL,
694 "Should have exactly {CANDIDATES_PER_POOL} candidates",
695 );
696
697 let mut tampered_pool = pool.clone();
699 tampered_pool.candidate_nodes[0].reward_address = RewardsAddress::from([0xFF; 20]);
700 assert!(
701 tampered_pool
702 .verify_commitment(&commitment, timestamp)
703 .is_err(),
704 "Commitment should not verify against tampered pool"
705 );
706
707 let tampered_commitment = tampered_pool.to_commitment();
709 assert_ne!(
710 commitment.pool_hash, tampered_commitment.pool_hash,
711 "Tampered pool should have different hash"
712 );
713 assert_ne!(
714 commitment.candidates[0].rewards_address,
715 tampered_commitment.candidates[0].rewards_address,
716 "Tampered pool should have different addresses"
717 );
718
719 let commitment2 = pool.to_commitment();
721 assert_eq!(
722 commitment.pool_hash, commitment2.pool_hash,
723 "Same pool should generate same commitment hash"
724 );
725 assert_eq!(
726 commitment.candidates, commitment2.candidates,
727 "Same pool should generate same candidates"
728 );
729 }
730
731 #[test]
732 fn test_pool_verify_method() {
733 let timestamp = SystemTime::now()
734 .duration_since(UNIX_EPOCH)
735 .unwrap()
736 .as_secs();
737
738 let addresses = make_test_addresses(10);
740 let tree = MerkleTree::from_xornames(addresses).unwrap();
741 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
742 let reward_pool = &reward_candidates[0];
743
744 let candidate_nodes = create_test_candidate_nodes(timestamp);
746
747 let pool = MerklePaymentCandidatePool {
748 midpoint_proof: reward_pool.clone(),
749 candidate_nodes,
750 };
751
752 assert!(
754 pool.verify_signatures(timestamp).is_ok(),
755 "Valid pool should verify successfully"
756 );
757
758 let mut invalid_sig_pool = pool.clone();
760 invalid_sig_pool.candidate_nodes[0].signature = vec![0xFF; 64]; assert!(
762 invalid_sig_pool.verify_signatures(timestamp).is_err(),
763 "Pool with invalid signature should fail verification"
764 );
765
766 let mut tampered_pool = pool.clone();
768 tampered_pool.candidate_nodes[0].reward_address = RewardsAddress::from([0xFF; 20]);
769 assert!(
770 tampered_pool.verify_signatures(timestamp).is_err(),
771 "Pool with tampered node data should fail verification (signature mismatch)"
772 );
773 }
774
775 #[test]
776 fn test_pool_verify_timestamp_consistency() {
777 let timestamp = SystemTime::now()
778 .duration_since(UNIX_EPOCH)
779 .unwrap()
780 .as_secs();
781
782 let addresses = make_test_addresses(10);
784 let tree = MerkleTree::from_xornames(addresses).unwrap();
785 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
786 let reward_pool = &reward_candidates[0];
787
788 let candidate_nodes = create_test_candidate_nodes(timestamp);
790
791 let pool = MerklePaymentCandidatePool {
792 midpoint_proof: reward_pool.clone(),
793 candidate_nodes,
794 };
795
796 assert!(
798 pool.verify_signatures(timestamp).is_ok(),
799 "Pool with identical timestamps should verify"
800 );
801
802 let mut mismatched_pool = pool.clone();
804 let different_keypair = Keypair::generate_ed25519();
805 let different_timestamp = timestamp + 3600; mismatched_pool.candidate_nodes[5] = MerklePaymentCandidateNode::new(
807 &different_keypair,
808 create_mock_quoting_metrics(5),
809 RewardsAddress::from([5u8; 20]),
810 different_timestamp,
811 )
812 .expect("Failed to create node with different timestamp");
813
814 assert!(
815 mismatched_pool.verify_signatures(timestamp).is_err(),
816 "Pool with mismatched timestamps should fail verification"
817 );
818 }
819
820 #[test]
821 fn test_invalid_public_key_error() {
822 let keypair = Keypair::generate_ed25519();
824 let mut node = MerklePaymentCandidateNode::new(
825 &keypair,
826 create_mock_quoting_metrics(1),
827 RewardsAddress::from([0x11; 20]),
828 SystemTime::now()
829 .duration_since(UNIX_EPOCH)
830 .unwrap()
831 .as_secs(),
832 )
833 .expect("Failed to create candidate node");
834
835 node.pub_key = vec![0xFF; 10]; assert!(
840 node.peer_id().is_err(),
841 "Should fail to derive peer_id from invalid pub_key"
842 );
843
844 assert!(
846 !node.verify_signature(),
847 "Signature verification should fail with invalid pub_key"
848 );
849 }
850
851 #[test]
852 fn test_node_hash_determinism() {
853 let keypair = Keypair::generate_ed25519();
854 let quoting_metrics = create_mock_quoting_metrics(42);
855 let reward_address = RewardsAddress::from([0x42; 20]);
856 let timestamp = SystemTime::now()
857 .duration_since(UNIX_EPOCH)
858 .unwrap()
859 .as_secs();
860
861 let node1 = MerklePaymentCandidateNode::new(
863 &keypair,
864 quoting_metrics.clone(),
865 reward_address,
866 timestamp,
867 )
868 .expect("Failed to create first node");
869
870 let node2 =
871 MerklePaymentCandidateNode::new(&keypair, quoting_metrics, reward_address, timestamp)
872 .expect("Failed to create second node");
873
874 assert_eq!(
876 node1.to_bytes(),
877 node2.to_bytes(),
878 "Same inputs should produce same byte representation"
879 );
880
881 let addresses = make_test_addresses(10);
883 let tree = MerkleTree::from_xornames(addresses).unwrap();
884 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
885 let reward_pool = &reward_candidates[0];
886
887 let pool1 = MerklePaymentCandidatePool {
888 midpoint_proof: reward_pool.clone(),
889 candidate_nodes: std::array::from_fn(|_| node1.clone()),
890 };
891
892 let pool2 = MerklePaymentCandidatePool {
893 midpoint_proof: reward_pool.clone(),
894 candidate_nodes: std::array::from_fn(|_| node2.clone()),
895 };
896
897 assert_eq!(
898 pool1.hash(),
899 pool2.hash(),
900 "Identical pools should produce identical hashes"
901 );
902 }
903
904 #[test]
905 fn test_corresponding_peer_ids_happy_path() {
906 let timestamp = SystemTime::now()
907 .duration_since(UNIX_EPOCH)
908 .unwrap()
909 .as_secs();
910
911 let addresses = make_test_addresses(10);
913 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
914 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
915 let reward_pool = &reward_candidates[0];
916
917 let (candidate_nodes, peer_ids) = create_test_candidate_nodes_with_peer_ids(timestamp);
919 let expected_peer_ids: Vec<_> = peer_ids
920 .iter()
921 .enumerate()
922 .map(|(i, pid)| (RewardsAddress::from([i as u8; 20]), *pid))
923 .collect();
924
925 let proof = MerklePaymentProof::new(
926 addresses[0],
927 tree.generate_address_proof(0, addresses[0]).unwrap(),
928 MerklePaymentCandidatePool {
929 midpoint_proof: reward_pool.clone(),
930 candidate_nodes,
931 },
932 );
933
934 let paid_nodes = vec![
936 (RewardsAddress::from([0; 20]), 0),
937 (RewardsAddress::from([1; 20]), 1),
938 (RewardsAddress::from([2; 20]), 2),
939 ];
940
941 let result = proof.corresponding_peer_ids(&paid_nodes);
942 assert!(
943 result.is_ok(),
944 "Should succeed when indices and addresses match"
945 );
946
947 let peer_ids = result.unwrap();
948 assert_eq!(peer_ids.len(), 3, "Should return 3 PeerIds");
949
950 for (i, (_addr, expected_peer_id)) in expected_peer_ids.iter().take(3).enumerate() {
952 assert_eq!(
953 peer_ids[i], *expected_peer_id,
954 "Should return PeerId at index {i} in correct order"
955 );
956 }
957 }
958
959 #[test]
960 fn test_corresponding_peer_ids_index_out_of_bounds() {
961 let timestamp = SystemTime::now()
962 .duration_since(UNIX_EPOCH)
963 .unwrap()
964 .as_secs();
965
966 let addresses = make_test_addresses(10);
968 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
969 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
970 let reward_pool = &reward_candidates[0];
971
972 let candidate_nodes = create_test_candidate_nodes(timestamp);
974
975 let proof = MerklePaymentProof::new(
976 addresses[0],
977 tree.generate_address_proof(0, addresses[0]).unwrap(),
978 MerklePaymentCandidatePool {
979 midpoint_proof: reward_pool.clone(),
980 candidate_nodes,
981 },
982 );
983
984 let paid_nodes = vec![
986 (RewardsAddress::from([0; 20]), 0), (RewardsAddress::from([99; 20]), 99), ];
989
990 let result = proof.corresponding_peer_ids(&paid_nodes);
991 assert!(result.is_err(), "Should fail when index is out of bounds");
992
993 match result {
994 Err(MerklePaymentVerificationError::PaidNodeIndexOutOfBounds { index, pool_size }) => {
995 assert_eq!(index, 99);
996 assert_eq!(pool_size, CANDIDATES_PER_POOL);
997 }
998 _ => panic!("Expected PaidNodeIndexOutOfBounds error"),
999 }
1000 }
1001
1002 #[test]
1003 fn test_corresponding_peer_ids_address_mismatch() {
1004 let timestamp = SystemTime::now()
1005 .duration_since(UNIX_EPOCH)
1006 .unwrap()
1007 .as_secs();
1008
1009 let addresses = make_test_addresses(10);
1011 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
1012 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
1013 let reward_pool = &reward_candidates[0];
1014
1015 let candidate_nodes = create_test_candidate_nodes(timestamp);
1017
1018 let proof = MerklePaymentProof::new(
1019 addresses[0],
1020 tree.generate_address_proof(0, addresses[0]).unwrap(),
1021 MerklePaymentCandidatePool {
1022 midpoint_proof: reward_pool.clone(),
1023 candidate_nodes,
1024 },
1025 );
1026
1027 let paid_nodes = vec![
1029 (RewardsAddress::from([99; 20]), 0), ];
1031
1032 let result = proof.corresponding_peer_ids(&paid_nodes);
1033 assert!(
1034 result.is_err(),
1035 "Should fail when address doesn't match index"
1036 );
1037
1038 match result {
1039 Err(MerklePaymentVerificationError::PaidAddressMismatch {
1040 index,
1041 expected,
1042 actual,
1043 }) => {
1044 assert_eq!(index, 0);
1045 assert_eq!(expected, RewardsAddress::from([99; 20]));
1046 assert_eq!(actual, RewardsAddress::from([0; 20]));
1047 }
1048 _ => panic!("Expected PaidAddressMismatch error"),
1049 }
1050 }
1051
1052 #[test]
1053 fn test_corresponding_peer_ids_multiple_nodes_same_address() {
1054 let timestamp = SystemTime::now()
1055 .duration_since(UNIX_EPOCH)
1056 .unwrap()
1057 .as_secs();
1058
1059 let addresses = make_test_addresses(10);
1061 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
1062 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
1063 let reward_pool = &reward_candidates[0];
1064
1065 let shared_address = RewardsAddress::from([42; 20]);
1067 let mut candidate_nodes = Vec::new();
1068 let mut peer_ids_for_shared_address = Vec::new();
1069
1070 for i in 0..3 {
1072 let keypair = Keypair::generate_ed25519();
1073 let peer_id = keypair.public().to_peer_id();
1074 let node = MerklePaymentCandidateNode::new(
1075 &keypair,
1076 create_mock_quoting_metrics(i),
1077 shared_address, timestamp,
1079 )
1080 .expect("Failed to create candidate node");
1081 candidate_nodes.push(node);
1082 peer_ids_for_shared_address.push(peer_id);
1083 }
1084
1085 for i in 3..CANDIDATES_PER_POOL {
1087 let keypair = Keypair::generate_ed25519();
1088 let node = MerklePaymentCandidateNode::new(
1089 &keypair,
1090 create_mock_quoting_metrics(i),
1091 RewardsAddress::from([i as u8; 20]),
1092 timestamp,
1093 )
1094 .expect("Failed to create candidate node");
1095 candidate_nodes.push(node);
1096 }
1097
1098 let proof = MerklePaymentProof::new(
1099 addresses[0],
1100 tree.generate_address_proof(0, addresses[0]).unwrap(),
1101 MerklePaymentCandidatePool {
1102 midpoint_proof: reward_pool.clone(),
1103 candidate_nodes: candidate_nodes
1104 .try_into()
1105 .expect("Should have exactly CANDIDATES_PER_POOL nodes"),
1106 },
1107 );
1108
1109 let paid_nodes = vec![
1112 (shared_address, 0), (shared_address, 1), ];
1115
1116 let result = proof.corresponding_peer_ids(&paid_nodes);
1117 assert!(
1118 result.is_ok(),
1119 "Should succeed when indices and addresses match"
1120 );
1121
1122 let peer_ids = result.unwrap();
1123 assert_eq!(
1124 peer_ids.len(),
1125 2,
1126 "Should return exactly 2 PeerIds for the 2 paid indices"
1127 );
1128
1129 assert_eq!(peer_ids[0], peer_ids_for_shared_address[0]);
1131 assert_eq!(peer_ids[1], peer_ids_for_shared_address[1]);
1132
1133 let paid_nodes = vec![(shared_address, 2)];
1135 let result = proof.corresponding_peer_ids(&paid_nodes);
1136 assert!(result.is_ok());
1137 let peer_ids = result.unwrap();
1138 assert_eq!(peer_ids[0], peer_ids_for_shared_address[2]);
1139 }
1140
1141 #[test]
1142 fn test_corresponding_peer_ids_invalid_public_key() {
1143 let timestamp = SystemTime::now()
1144 .duration_since(UNIX_EPOCH)
1145 .unwrap()
1146 .as_secs();
1147
1148 let addresses = make_test_addresses(10);
1150 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
1151 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
1152 let reward_pool = &reward_candidates[0];
1153
1154 let mut candidate_nodes = Vec::new();
1156 for i in 0..CANDIDATES_PER_POOL {
1157 let keypair = Keypair::generate_ed25519();
1158 let node = MerklePaymentCandidateNode::new(
1159 &keypair,
1160 create_mock_quoting_metrics(i),
1161 RewardsAddress::from([i as u8; 20]),
1162 timestamp,
1163 )
1164 .expect("Failed to create candidate node");
1165 candidate_nodes.push(node);
1166 }
1167
1168 candidate_nodes[5].pub_key = vec![0xFF; 10];
1170
1171 let proof = MerklePaymentProof::new(
1172 addresses[0],
1173 tree.generate_address_proof(0, addresses[0]).unwrap(),
1174 MerklePaymentCandidatePool {
1175 midpoint_proof: reward_pool.clone(),
1176 candidate_nodes: candidate_nodes
1177 .try_into()
1178 .expect("Should have exactly CANDIDATES_PER_POOL nodes"),
1179 },
1180 );
1181
1182 let paid_nodes = vec![(RewardsAddress::from([5; 20]), 5)];
1184
1185 let result = proof.corresponding_peer_ids(&paid_nodes);
1186 assert!(
1187 result.is_err(),
1188 "Should fail when candidate has invalid pub_key"
1189 );
1190
1191 match result {
1192 Err(MerklePaymentVerificationError::InvalidNodePeerId { address, index }) => {
1193 assert_eq!(address, RewardsAddress::from([5; 20]));
1194 assert_eq!(index, 5);
1195 }
1196 _ => panic!("Expected InvalidNodePeerId error"),
1197 }
1198 }
1199
1200 #[test]
1201 fn test_corresponding_peer_ids_empty_input() {
1202 let timestamp = SystemTime::now()
1203 .duration_since(UNIX_EPOCH)
1204 .unwrap()
1205 .as_secs();
1206
1207 let addresses = make_test_addresses(10);
1209 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
1210 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
1211 let reward_pool = &reward_candidates[0];
1212
1213 let mut candidate_nodes = Vec::new();
1215 for i in 0..CANDIDATES_PER_POOL {
1216 let keypair = Keypair::generate_ed25519();
1217 let node = MerklePaymentCandidateNode::new(
1218 &keypair,
1219 create_mock_quoting_metrics(i),
1220 RewardsAddress::from([i as u8; 20]),
1221 timestamp,
1222 )
1223 .expect("Failed to create candidate node");
1224 candidate_nodes.push(node);
1225 }
1226
1227 let proof = MerklePaymentProof::new(
1228 addresses[0],
1229 tree.generate_address_proof(0, addresses[0]).unwrap(),
1230 MerklePaymentCandidatePool {
1231 midpoint_proof: reward_pool.clone(),
1232 candidate_nodes: candidate_nodes
1233 .try_into()
1234 .expect("Should have exactly CANDIDATES_PER_POOL nodes"),
1235 },
1236 );
1237
1238 let paid_nodes: Vec<(RewardsAddress, usize)> = vec![];
1240
1241 let result = proof.corresponding_peer_ids(&paid_nodes);
1242 assert!(result.is_ok(), "Should succeed with empty input");
1243
1244 let peer_ids = result.unwrap();
1245 assert_eq!(peer_ids.len(), 0, "Should return empty Vec for empty input");
1246 }
1247
1248 #[test]
1249 fn test_corresponding_peer_ids_partial_payment() {
1250 let timestamp = SystemTime::now()
1251 .duration_since(UNIX_EPOCH)
1252 .unwrap()
1253 .as_secs();
1254
1255 let addresses = make_test_addresses(10);
1257 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
1258 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
1259 let reward_pool = &reward_candidates[0];
1260
1261 let mut candidate_nodes = Vec::new();
1263 let mut all_peer_ids = Vec::new();
1264 for i in 0..CANDIDATES_PER_POOL {
1265 let keypair = Keypair::generate_ed25519();
1266 let peer_id = keypair.public().to_peer_id();
1267 let node = MerklePaymentCandidateNode::new(
1268 &keypair,
1269 create_mock_quoting_metrics(i),
1270 RewardsAddress::from([i as u8; 20]),
1271 timestamp,
1272 )
1273 .expect("Failed to create candidate node");
1274 candidate_nodes.push(node);
1275 all_peer_ids.push(peer_id);
1276 }
1277
1278 let proof = MerklePaymentProof::new(
1279 addresses[0],
1280 tree.generate_address_proof(0, addresses[0]).unwrap(),
1281 MerklePaymentCandidatePool {
1282 midpoint_proof: reward_pool.clone(),
1283 candidate_nodes: candidate_nodes
1284 .try_into()
1285 .expect("Should have exactly CANDIDATES_PER_POOL nodes"),
1286 },
1287 );
1288
1289 let paid_nodes = vec![
1291 (RewardsAddress::from([0; 20]), 0),
1292 (RewardsAddress::from([1; 20]), 1),
1293 (RewardsAddress::from([2; 20]), 2),
1294 (RewardsAddress::from([3; 20]), 3),
1295 (RewardsAddress::from([4; 20]), 4),
1296 (RewardsAddress::from([5; 20]), 5),
1297 (RewardsAddress::from([6; 20]), 6),
1298 ];
1299
1300 let result = proof.corresponding_peer_ids(&paid_nodes);
1301 assert!(
1302 result.is_ok(),
1303 "Should succeed when indices and addresses match"
1304 );
1305
1306 let peer_ids = result.unwrap();
1307 assert_eq!(
1308 peer_ids.len(),
1309 7,
1310 "Should return only 7 PeerIds for the 7 paid nodes"
1311 );
1312
1313 for i in 0..7 {
1315 assert_eq!(
1316 peer_ids[i], all_peer_ids[i],
1317 "Should return PeerId at index {i} in correct order"
1318 );
1319 }
1320 }
1321
1322 #[test]
1323 fn test_complete_merkle_batch_payment_flow() {
1324 let address_count = 100;
1326 let addresses = make_test_addresses(address_count);
1327 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
1328 let _root = tree.root();
1329 let depth = tree.depth();
1330 assert_eq!(depth, 7); let merkle_payment_timestamp = SystemTime::now()
1334 .duration_since(UNIX_EPOCH)
1335 .unwrap()
1336 .as_secs();
1337
1338 let reward_candidates = tree.reward_candidates(merkle_payment_timestamp).unwrap();
1339 let expected_pools = crate::merkle_payments::expected_reward_pools(depth);
1340 assert_eq!(reward_candidates.len(), expected_pools);
1341
1342 let mut all_candidate_pools = Vec::new();
1344 for (pool_idx, reward_pool) in reward_candidates.iter().enumerate() {
1345 let mut candidate_nodes = Vec::new();
1346 for node_id in 0..CANDIDATES_PER_POOL {
1347 let keypair = Keypair::generate_ed25519();
1348
1349 let current_time = SystemTime::now()
1351 .duration_since(UNIX_EPOCH)
1352 .unwrap()
1353 .as_secs();
1354 assert!(
1355 merkle_payment_timestamp <= current_time,
1356 "Timestamp should not be in the future"
1357 );
1358 assert!(
1359 current_time - merkle_payment_timestamp
1360 < crate::merkle_payments::merkle_tree::MERKLE_PAYMENT_EXPIRATION,
1361 "Timestamp should not be expired"
1362 );
1363
1364 let node = MerklePaymentCandidateNode::new(
1366 &keypair,
1367 create_mock_quoting_metrics(node_id),
1368 RewardsAddress::from([(pool_idx * CANDIDATES_PER_POOL + node_id) as u8; 20]),
1369 merkle_payment_timestamp,
1370 )
1371 .expect("Failed to create candidate node");
1372
1373 assert!(node.verify_signature());
1375
1376 assert_eq!(
1378 node.merkle_payment_timestamp, merkle_payment_timestamp,
1379 "Honest nodes should return the same merkle payment timestamp"
1380 );
1381
1382 candidate_nodes.push(node);
1384 }
1385
1386 let pool = MerklePaymentCandidatePool {
1387 midpoint_proof: reward_pool.clone(),
1388 candidate_nodes: candidate_nodes
1389 .try_into()
1390 .expect("Should have exactly CANDIDATES_PER_POOL nodes"),
1391 };
1392 all_candidate_pools.push(pool);
1393 }
1394
1395 let pool_commitments: Vec<PoolCommitment> = all_candidate_pools
1397 .iter()
1398 .map(|pool| pool.to_commitment())
1399 .collect();
1400
1401 let temp_dir = TempDir::new().unwrap();
1402 let contract = evmlib::merkle_batch_payment::DiskMerklePaymentContract::new_with_path(
1403 temp_dir.path().to_path_buf(),
1404 )
1405 .unwrap();
1406
1407 let (winner_pool_hash, _amount) = contract
1408 .pay_for_merkle_tree(depth, pool_commitments.clone(), merkle_payment_timestamp)
1409 .unwrap();
1410
1411 let payment_info = contract.get_payment_info(winner_pool_hash).unwrap();
1413 assert_eq!(payment_info.depth, depth);
1414 assert_eq!(
1415 payment_info.merkle_payment_timestamp,
1416 merkle_payment_timestamp
1417 );
1418 assert_eq!(payment_info.paid_node_addresses.len(), depth as usize);
1419
1420 let (winner_pool, winner_commitment) = all_candidate_pools
1422 .iter()
1423 .zip(pool_commitments.iter())
1424 .find(|(pool, _)| pool.hash() == winner_pool_hash)
1425 .expect("Winner pool should be found");
1426
1427 assert!(
1428 winner_pool
1429 .verify_commitment(winner_commitment, merkle_payment_timestamp)
1430 .is_ok(),
1431 "Winner commitment should verify against full pool data"
1432 );
1433
1434 let payment_proofs: Vec<MerklePaymentProof> = addresses
1437 .iter()
1438 .enumerate()
1439 .map(|(i, address_hash)| {
1440 let address_proof = tree.generate_address_proof(i, *address_hash).unwrap();
1441 MerklePaymentProof::new(*address_hash, address_proof, winner_pool.clone())
1442 })
1443 .collect();
1444
1445 for payment_proof in &payment_proofs {
1447 let winner_to_fetch = payment_proof.winner_pool_hash();
1449 let payment = contract
1450 .get_payment_info(winner_to_fetch)
1451 .expect("Payment should be found");
1452
1453 let verification_result = payment_proof.verify(
1455 payment.depth,
1456 payment.merkle_payment_timestamp,
1457 &winner_to_fetch,
1458 &payment.paid_node_addresses,
1459 );
1460 if let Err(e) = &verification_result {
1461 eprintln!("Verification failed: {e:?}");
1462 }
1463 assert!(
1464 verification_result.is_ok(),
1465 "Payment proof should verify against smart contract data: {verification_result:?}"
1466 );
1467
1468 }
1470 }
1471}