ant_evm/merkle_payments/
merkle_payment.rs

1// Copyright 2025 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use 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/// Errors that can occur during Merkle payment verification
26#[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/// A node's signed quote for potential reward eligibility
79///
80/// Nodes create this structure in response to a client's quote request. The client provides
81/// a `merkle_payment_timestamp`, which nodes verify is not outdated (not in the future or expired).
82/// Nodes then sign their quoting metrics and payment address with this timestamp, establishing
83/// their candidacy to be selected for payment rewards. The client collects these from multiple
84/// nodes to build a [`MerklePaymentCandidatePool`].
85#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
86pub struct MerklePaymentCandidateNode {
87    /// Node's libp2p public key
88    /// PeerId can be derived from this: PeerId::from(PublicKey::try_decode_protobuf(pub_key))
89    pub pub_key: Vec<u8>,
90
91    /// Node's storage metrics at quote time
92    pub quoting_metrics: QuotingMetrics,
93
94    /// Node's Ethereum address for payment
95    pub reward_address: RewardsAddress,
96
97    /// Quote timestamp (provided by the client)
98    pub merkle_payment_timestamp: u64,
99
100    /// Signature over hash(quoting_metrics || reward_address || timestamp)
101    pub signature: Vec<u8>,
102}
103
104impl MerklePaymentCandidateNode {
105    /// Create a new candidate node with signed commitment
106    ///
107    /// # Arguments
108    /// * `keypair` - Node's libp2p keypair for signing
109    /// * `quoting_metrics` - Node's storage metrics at quote time
110    /// * `reward_address` - Node's Ethereum address for payment
111    /// * `timestamp` - Quote timestamp
112    ///
113    /// # Returns
114    /// * `Result<Self, MerklePaymentError>` - Signed candidate node or signing error
115    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        // Extract public key in protobuf format
122        let pub_key = keypair.public().encode_protobuf();
123
124        // Sign the content
125        let msg = Self::bytes_to_sign(&quoting_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    /// Get the bytes to sign
138    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(&quoting_metrics.to_bytes());
145        bytes.extend_from_slice(reward_address.as_slice());
146        bytes.extend_from_slice(&timestamp.to_le_bytes());
147        bytes
148    }
149
150    /// Convert to deterministic byte representation for hashing
151    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    /// Derive PeerId from public key
162    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    /// Verify signature is valid for this node
167    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/// One candidate pool: midpoint proof + nodes who could store addresses
183#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
184pub struct MerklePaymentCandidatePool {
185    /// The midpoint proof from ant-evm's merkle_tree module
186    pub midpoint_proof: MidpointProof,
187
188    /// Candidate nodes for this pool
189    /// Provides redundancy - only 'depth' of these will be selected as winners
190    pub candidate_nodes: [MerklePaymentCandidateNode; CANDIDATES_PER_POOL],
191}
192
193impl MerklePaymentCandidatePool {
194    /// Compute deterministic hash for on-chain storage key
195    pub fn hash(&self) -> PoolHash {
196        let mut bytes = Vec::new();
197
198        // Hash of the intersection proof
199        bytes.extend_from_slice(self.midpoint_proof.hash().as_ref());
200
201        // Number of candidate nodes
202        bytes.extend_from_slice(&(self.candidate_nodes.len() as u32).to_le_bytes());
203
204        // Each candidate node's data
205        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    /// Convert to minimal commitment for smart contract submission
213    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    /// Helper function for PoolCommitment verification
227    ///
228    /// This verifies that a commitment matches a pool and that the pool signatures are valid.
229    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    /// Get the addresses of the candidate nodes
243    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    /// Verify that the signatures in the candidate pool are valid
251    ///
252    /// Checks:
253    /// 1. All node signatures are valid
254    /// 2. All timestamps match the merkle payment timestamp
255    /// 3. All nodes quote the same data_type and data_size
256    ///
257    /// It does not verify the pool branch proof.
258    pub fn verify_signatures(
259        &self,
260        merkle_payment_timestamp: u64,
261    ) -> Result<(), MerklePaymentVerificationError> {
262        // Verify all node signatures
263        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        // Verify all timestamps match the merkle payment timestamp
272        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        // Verify all nodes are quoting for the same data_type and data_size
283        // All nodes in a pool should be providing quotes for the same data
284        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/// Data package sent from client to node for data storage and payment verification
312///
313/// Contains everything a node needs to verify:
314/// 1. The data belongs to a paid Merkle tree (via proof)
315/// 2. Payment was made to the correct pool (via winner pool proof and smart contract query)
316/// 3. The node is eligible to store this data (if they're in the winner pool)
317#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
318pub struct MerklePaymentProof {
319    /// The data's XorName
320    pub address: XorName,
321
322    /// Merkle proof that this data belongs to the paid tree
323    pub data_proof: MerkleBranch,
324
325    /// The winner pool selected by the smart contract
326    /// Contains the full candidate pool with signatures and intersection proof
327    pub winner_pool: MerklePaymentCandidatePool,
328}
329
330impl MerklePaymentProof {
331    /// Create a new Merkle payment proof
332    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    /// Get the hash of the winner pool (used to query smart contract for payment info)
345    pub fn winner_pool_hash(&self) -> PoolHash {
346        self.winner_pool.hash()
347    }
348
349    /// Get the corresponding peer ids for the paid nodes using their indices in the winner pool
350    ///
351    /// This uses the indices from the smart contract to identify exactly which nodes were paid.
352    /// This is necessary because multiple nodes can share the same reward address.
353    ///
354    /// # Arguments
355    /// * `paid_nodes` - The (address, index) pairs from the smart contract
356    ///
357    /// # Returns
358    /// * `Ok(Vec<PeerId>)` - PeerIds of the paid nodes, in the same order as the input
359    /// * `Err(PaidNodeIndexOutOfBounds)` - If any index is out of bounds
360    /// * `Err(InvalidNodePeerId)` - If any paid node has an invalid public key
361    /// * `Err(PaidAddressMismatch)` - If address at index doesn't match expected address
362    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            // Verify the address at this index matches what the smart contract says
377            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            // Derive the PeerId from the node's public key
386            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    /// Verify the payment proof against the smart contract payment info
400    ///
401    /// # Arguments
402    /// * `smart_contract_depth` - The depth value stored in the smart contract
403    /// * `smart_contract_timestamp` - The merkle payment timestamp stored in the smart contract
404    /// * `smart_contract_pool_hash` - The hash of the winner pool stored in the smart contract
405    /// * `smart_contract_paid_nodes` - The (address, index) pairs of paid nodes from the smart contract
406    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        // Verify the winner pool signatures and timestamps first
414        self.winner_pool
415            .verify_signatures(smart_contract_timestamp)?;
416
417        // Verify the winner pool hash matches the smart contract pool hash
418        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        // Verify the core Merkle proof using the tree-level verification
428        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        // Verify the correct number of nodes were paid (should equal depth)
438        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        // Verify all paid node (address, index) pairs are valid
446        // This also verifies addresses match what's at those indices
447        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, // 4MB
471            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    /// Helper to create an array of CANDIDATES_PER_POOL test nodes with unique addresses
482    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    /// Helper to create an array of CANDIDATES_PER_POOL test nodes and return their PeerIds
498    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        // Create a keypair for signing
524        let keypair = Keypair::generate_ed25519();
525        let peer_id = keypair.public().to_peer_id();
526
527        // Create test data
528        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        // Create candidate node using constructor
536        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        // Verify the peer_id matches
545        assert_eq!(
546            node.peer_id().expect("Failed to derive peer_id"),
547            peer_id,
548            "PeerId should match keypair"
549        );
550
551        // Verify the signature is valid
552        assert!(
553            node.verify_signature(),
554            "Signature should be valid for the signed data"
555        );
556
557        // Verify all fields are correctly set
558        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        // Create a valid signed node
569        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        // Valid signature should verify
586        assert!(
587            node.verify_signature(),
588            "Original signature should be valid"
589        );
590
591        // Tamper with reward address - signature should now fail
592        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        // Restore and tamper with quoting metrics
599        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        // Restore and tamper with timestamp (use a clearly different time)
607        node.quoting_metrics = quoting_metrics;
608        node.merkle_payment_timestamp = timestamp + 3600; // 1 hour later
609        assert!(
610            !node.verify_signature(),
611            "Signature should fail after tampering with timestamp"
612        );
613
614        // Test with wrong keypair's signature
615        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        // Swap signatures between nodes
625        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        // Verify original signature still works when restored
633        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        // Create a simple merkle tree
650        let addresses = make_test_addresses(10);
651        let tree = MerkleTree::from_xornames(addresses).unwrap();
652
653        // Get a reward candidate pool
654        let reward_candidates = tree.reward_candidates(timestamp).unwrap();
655        let reward_pool = &reward_candidates[0];
656
657        // Create candidate nodes array directly
658        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        // Create commitment from pool
666        let commitment = pool.to_commitment();
667
668        // Verify commitment matches pool
669        assert!(
670            pool.verify_commitment(&commitment, timestamp).is_ok(),
671            "Commitment should verify against original pool"
672        );
673
674        // Test 1: Verify pool_hash is correct
675        assert_eq!(
676            commitment.pool_hash,
677            pool.hash(),
678            "Commitment pool_hash should match pool.hash()"
679        );
680
681        // Test 2: Verify candidates match
682        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        // Test 3: Tamper with pool - verification should fail
698        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        // Test 4: Create commitment from tampered pool and verify it doesn't match original commitment
708        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        // Test 5: Verify determinism - same pool generates same commitment
720        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        // Create a valid pool with properly signed nodes
739        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        // Create candidate nodes array directly
745        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        // Test 1: Valid pool should verify
753        assert!(
754            pool.verify_signatures(timestamp).is_ok(),
755            "Valid pool should verify successfully"
756        );
757
758        // Test 2: Pool with invalid signature should fail
759        let mut invalid_sig_pool = pool.clone();
760        invalid_sig_pool.candidate_nodes[0].signature = vec![0xFF; 64]; // Corrupt signature
761        assert!(
762            invalid_sig_pool.verify_signatures(timestamp).is_err(),
763            "Pool with invalid signature should fail verification"
764        );
765
766        // Test 3: Pool with tampered node data should fail
767        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        // Create a valid pool
783        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        // Create candidate nodes array directly
789        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        // Valid pool with identical timestamps should verify
797        assert!(
798            pool.verify_signatures(timestamp).is_ok(),
799            "Pool with identical timestamps should verify"
800        );
801
802        // Create pool with mismatched timestamps
803        let mut mismatched_pool = pool.clone();
804        let different_keypair = Keypair::generate_ed25519();
805        let different_timestamp = timestamp + 3600; // 1 hour later
806        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        // Create a node with invalid pub_key
823        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        // Corrupt pub_key with invalid data
836        node.pub_key = vec![0xFF; 10]; // Too short and invalid format
837
838        // Should fail to derive peer_id
839        assert!(
840            node.peer_id().is_err(),
841            "Should fail to derive peer_id from invalid pub_key"
842        );
843
844        // Signature verification should also fail
845        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        // Create two identical nodes
862        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        // to_bytes() should be deterministic
875        assert_eq!(
876            node1.to_bytes(),
877            node2.to_bytes(),
878            "Same inputs should produce same byte representation"
879        );
880
881        // Hashes of pools containing these nodes should be deterministic
882        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        // Create a merkle tree and get a reward pool
912        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        // Create candidate nodes array and track their PeerIds
918        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        // Test: Get PeerIds using (address, index) pairs
935        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        // Verify returned PeerIds match expected (in order)
951        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        // Create a merkle tree and get a reward pool
967        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        // Create candidate nodes array directly
973        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        // Test: Index out of bounds (attack scenario)
985        let paid_nodes = vec![
986            (RewardsAddress::from([0; 20]), 0),   // valid
987            (RewardsAddress::from([99; 20]), 99), // index 99 is out of bounds!
988        ];
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        // Create a merkle tree and get a reward pool
1010        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        // Create candidate nodes array directly
1016        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        // Test: Address doesn't match what's at the index (attack scenario)
1028        let paid_nodes = vec![
1029            (RewardsAddress::from([99; 20]), 0), // index 0 has address [0;20], not [99;20]!
1030        ];
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        // Create a merkle tree and get a reward pool
1060        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        // Create candidate nodes where MULTIPLE nodes share the SAME reward address
1066        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        // First 3 nodes share the same address
1071        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, // Same address!
1078                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        // Remaining nodes have unique addresses
1086        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        // Test: When nodes at specific indices are paid (even with shared address),
1110        // we can distinguish them by index
1111        let paid_nodes = vec![
1112            (shared_address, 0), // First node with shared address
1113            (shared_address, 1), // Second node with shared address
1114        ];
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        // Verify the specific PeerIds at indices 0 and 1 are returned (in order)
1130        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        // Test: Paying index 2 (third node with same address)
1134        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        // Create a merkle tree and get a reward pool
1149        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        // Create candidate nodes
1155        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        // Corrupt the pub_key of one node
1169        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        // Try to get PeerIds for the corrupted node's address
1183        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        // Create a merkle tree and get a reward pool
1208        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        // Create candidate nodes
1214        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        // Test: Empty paid nodes
1239        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        // Create a merkle tree and get a reward pool
1256        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        // Create 20 candidate nodes
1262        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        // Test: Only subset of candidates were paid (depth=7, so only 7 out of 20 paid)
1290        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        // Verify only the paid nodes' PeerIds are returned (in order)
1314        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        // Phase 1: Client prepares addresses and tree
1325        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); // ceil(log2(100)) = 7
1331
1332        // Phase 2: Client queries candidate pools
1333        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        // Simulate querying 20 nodes for each pool with properly signed commitments
1343        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                // Nodes verify the merkle payment timestamp is not too old (or in the future)
1350                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                // Nodes generate their MerklePaymentCandidateNode and return it to the client
1365                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                // client verifies the node's signature
1374                assert!(node.verify_signature());
1375
1376                // client verifies the node's MerklePaymentCandidateNode is the same as the one provided
1377                assert_eq!(
1378                    node.merkle_payment_timestamp, merkle_payment_timestamp,
1379                    "Honest nodes should return the same merkle payment timestamp"
1380                );
1381
1382                // client adds the node to the pool
1383                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        // Phase 3: Client submits payment to contract
1396        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        // Verify payment info stored correctly
1412        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        // Find winner pool and verify commitment
1421        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        // Phase 4: Generate payment proofs for upload
1435        // Client creates a MerklePaymentProof for each address to send to nodes
1436        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        // Phase 5: Nodes verify payment proofs
1446        for payment_proof in &payment_proofs {
1447            // Node queries smart contract using the winner_pool_hash
1448            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            // Node verifies the payment proof using the smart contract data
1454            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            // Node verifies the closest nodes to winner pool include majority of paid nodes (this is done node side)
1469        }
1470    }
1471}