Skip to content

Commit 82f2fcc

Browse files
authored
Merge pull request #7 from WithAutonomi/refactor/remove-disk-merkle-contract
refactor: remove unused DiskMerklePaymentContract
2 parents e1494bb + 46029ff commit 82f2fcc

4 files changed

Lines changed: 32 additions & 209 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ homepage = "https://maidsafe.net"
66
license = "GPL-3.0"
77
name = "evmlib"
88
repository = "https://github.com/WithAutonomi/evmlib"
9-
version = "0.7.0"
9+
version = "0.8.0"
1010

1111
[features]
1212
external-signer = []

src/merkle_batch_payment.rs

Lines changed: 2 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,16 @@
66
// KIND, either express or implied. Please review the Licences for the specific language governing
77
// permissions and limitations relating to use of the SAFE Network Software.
88

9-
//! Merkle batch payment types and disk-based mock smart contract
9+
//! Merkle batch payment types
1010
//!
11-
//! This module contains the minimal types needed for Merkle batch payments and a disk-based
12-
//! mock implementation of the smart contract. When the real smart contract is ready, the
13-
//! disk contract will be replaced with actual on-chain calls.
11+
//! This module contains the minimal types needed for Merkle batch payments.
1412
1513
use crate::common::{Address as RewardsAddress, Amount};
1614

1715
#[cfg(test)]
1816
use crate::common::U256;
1917
use serde::{Deserialize, Serialize};
2018

21-
#[cfg(test)]
22-
use std::path::PathBuf;
23-
24-
#[cfg(test)]
25-
use thiserror::Error;
26-
2719
/// Pool hash type (32 bytes) - compatible with XorName without the dependency
2820
pub type PoolHash = [u8; 32];
2921

@@ -81,180 +73,6 @@ pub struct OnChainPaymentInfo {
8173
pub paid_node_addresses: Vec<(RewardsAddress, usize, Amount)>,
8274
}
8375

84-
#[cfg(test)]
85-
/// Errors that can occur during smart contract operations
86-
#[derive(Debug, Error)]
87-
pub enum SmartContractError {
88-
#[error("Wrong number of candidate nodes: expected {expected}, got {got}")]
89-
WrongCandidateCount { expected: usize, got: usize },
90-
91-
#[error("Wrong number of candidate pools: expected {expected}, got {got}")]
92-
WrongPoolCount { expected: usize, got: usize },
93-
94-
#[error("Depth {depth} exceeds maximum supported depth {max}")]
95-
DepthTooLarge { depth: u8, max: u8 },
96-
97-
#[error("Payment not found for winner pool hash: {0}")]
98-
PaymentNotFound(String),
99-
100-
#[error("IO error: {0}")]
101-
IoError(#[from] std::io::Error),
102-
103-
#[error("JSON error: {0}")]
104-
JsonError(#[from] serde_json::Error),
105-
}
106-
107-
#[cfg(test)]
108-
/// Disk-based Merkle payment contract (mock for testing)
109-
///
110-
/// This simulates smart contract behavior by storing payment data to disk.
111-
/// Only available for testing.
112-
pub struct DiskMerklePaymentContract {
113-
storage_path: PathBuf, // ~/.autonomi/merkle_payments/
114-
}
115-
116-
#[cfg(test)]
117-
impl DiskMerklePaymentContract {
118-
/// Create a new contract with a specific storage path
119-
pub fn new_with_path(storage_path: PathBuf) -> Result<Self, SmartContractError> {
120-
std::fs::create_dir_all(&storage_path)?;
121-
Ok(Self { storage_path })
122-
}
123-
124-
/// Create a new contract with the default storage path
125-
/// Uses: DATA_DIR/autonomi/merkle_payments/
126-
pub fn new() -> Result<Self, SmartContractError> {
127-
let storage_path = if let Some(data_dir) = dirs_next::data_dir() {
128-
data_dir.join("autonomi").join("merkle_payments")
129-
} else {
130-
// Fallback to current directory if data_dir is not available
131-
PathBuf::from(".autonomi").join("merkle_payments")
132-
};
133-
Self::new_with_path(storage_path)
134-
}
135-
136-
/// Submit batch payment (simulates smart contract logic)
137-
///
138-
/// # Arguments
139-
/// * `depth` - Tree depth
140-
/// * `pool_commitments` - Minimal pool commitments (2^ceil(depth/2) pools with hashes + addresses)
141-
/// * `merkle_payment_timestamp` - Client-defined timestamp committed to by all nodes in their quotes
142-
///
143-
/// # Returns
144-
/// * `winner_pool_hash` - Hash of winner pool (storage key for verification)
145-
/// * `amount` - Amount paid for the Merkle tree
146-
pub fn pay_for_merkle_tree(
147-
&self,
148-
depth: u8,
149-
pool_commitments: Vec<PoolCommitment>,
150-
merkle_payment_timestamp: u64,
151-
) -> Result<(PoolHash, Amount), SmartContractError> {
152-
// Validate: depth is within supported range
153-
if depth > MAX_MERKLE_DEPTH {
154-
return Err(SmartContractError::DepthTooLarge {
155-
depth,
156-
max: MAX_MERKLE_DEPTH,
157-
});
158-
}
159-
160-
// Validate: correct number of pools (2^ceil(depth/2))
161-
let expected_pools = expected_reward_pools(depth);
162-
if pool_commitments.len() != expected_pools {
163-
return Err(SmartContractError::WrongPoolCount {
164-
expected: expected_pools,
165-
got: pool_commitments.len(),
166-
});
167-
}
168-
169-
// Validate: each pool has exactly CANDIDATES_PER_POOL candidates
170-
for pool in &pool_commitments {
171-
if pool.candidates.len() != CANDIDATES_PER_POOL {
172-
return Err(SmartContractError::WrongCandidateCount {
173-
expected: CANDIDATES_PER_POOL,
174-
got: pool.candidates.len(),
175-
});
176-
}
177-
}
178-
179-
// Select winner pool using random selection
180-
let winner_pool_idx = rand::random::<usize>() % pool_commitments.len();
181-
182-
let winner_pool = &pool_commitments[winner_pool_idx];
183-
let winner_pool_hash = winner_pool.pool_hash;
184-
185-
println!("\n=== MERKLE BATCH PAYMENT ===");
186-
println!("Depth: {depth}");
187-
println!("Total pools: {}", pool_commitments.len());
188-
println!("Nodes per pool: {CANDIDATES_PER_POOL}");
189-
println!("Winner pool index: {winner_pool_idx}");
190-
println!("Winner pool hash: {}", hex::encode(winner_pool_hash));
191-
192-
// Select 'depth' unique winner nodes within the winner pool
193-
use std::collections::HashSet;
194-
let mut winner_node_indices = HashSet::new();
195-
while winner_node_indices.len() < depth as usize {
196-
let idx = rand::random::<usize>() % winner_pool.candidates.len();
197-
winner_node_indices.insert(idx);
198-
}
199-
let winner_node_indices: Vec<usize> = winner_node_indices.into_iter().collect();
200-
201-
println!(
202-
"\nSelected {} winner nodes from pool:",
203-
winner_node_indices.len()
204-
);
205-
206-
// Calculate total amount from winner node prices
207-
let mut total_amount = Amount::ZERO;
208-
209-
// Extract paid node addresses, along with their indices
210-
let mut paid_node_addresses = Vec::new();
211-
for (i, &node_idx) in winner_node_indices.iter().enumerate() {
212-
let candidate = &winner_pool.candidates[node_idx];
213-
let addr = candidate.rewards_address;
214-
paid_node_addresses.push((addr, node_idx, candidate.price));
215-
total_amount += candidate.price;
216-
println!(" Node {}: {addr} (price: {})", i + 1, candidate.price);
217-
}
218-
219-
println!(
220-
"\nSimulating payment to {} nodes, total: {total_amount}...",
221-
paid_node_addresses.len()
222-
);
223-
println!("=========================\n");
224-
225-
// Store payment info on 'blockchain' (indexed by winner_pool_hash)
226-
let info = OnChainPaymentInfo {
227-
depth,
228-
merkle_payment_timestamp,
229-
paid_node_addresses,
230-
};
231-
232-
let file_path = self
233-
.storage_path
234-
.join(format!("{}.json", hex::encode(winner_pool_hash)));
235-
let json = serde_json::to_string_pretty(&info)?;
236-
std::fs::write(&file_path, json)?;
237-
238-
println!("✓ Stored payment info to: {}", file_path.display());
239-
240-
Ok((winner_pool_hash, total_amount))
241-
}
242-
243-
/// Get payment info by winner pool hash
244-
pub fn get_payment_info(
245-
&self,
246-
winner_pool_hash: PoolHash,
247-
) -> Result<OnChainPaymentInfo, SmartContractError> {
248-
let file_path = self
249-
.storage_path
250-
.join(format!("{}.json", hex::encode(winner_pool_hash)));
251-
let json = std::fs::read_to_string(&file_path)
252-
.map_err(|_| SmartContractError::PaymentNotFound(hex::encode(winner_pool_hash)))?;
253-
let info = serde_json::from_str(&json)?;
254-
Ok(info)
255-
}
256-
}
257-
25876
#[cfg(test)]
25977
mod tests {
26078
use super::*;

src/merkle_payments/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ pub use crate::merkle_batch_payment::{
1111
expected_reward_pools,
1212
};
1313

14-
#[cfg(test)]
15-
pub use crate::merkle_batch_payment::SmartContractError;
16-
1714
// Export payment types (nodes, pools, proofs)
1815
pub use merkle_payment::{
1916
MerklePaymentCandidateNode, MerklePaymentCandidatePool, MerklePaymentProof,

tests/payment_vault.rs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -347,26 +347,34 @@ async fn test_pay_for_merkle_tree_duplicate_rejected() {
347347

348348
payment_vault.set_provider(network_token.contract.provider().clone());
349349

350-
// First payment should succeed
351-
let _ = payment_vault
352-
.pay_for_merkle_tree(
353-
depth,
354-
pool_commitments.clone(),
355-
merkle_payment_timestamp,
356-
&transaction_config,
357-
)
358-
.await
359-
.expect("first payment should succeed");
360-
361-
// Second payment with same pools and timestamp should fail (PaymentAlreadyExists)
362-
let result = payment_vault
363-
.pay_for_merkle_tree(
364-
depth,
365-
pool_commitments,
366-
merkle_payment_timestamp,
367-
&transaction_config,
368-
)
369-
.await;
350+
// With 2 pools, each payment randomly picks a winner pool via on-chain entropy
351+
// (block.prevrandao, block.timestamp). By pigeonhole, 3 payments guarantee at least
352+
// one duplicate winner pool hash, which the contract rejects.
353+
let max_attempts = num_pools + 1;
354+
let mut saw_duplicate_rejection = false;
355+
356+
for i in 0..max_attempts {
357+
let result = payment_vault
358+
.pay_for_merkle_tree(
359+
depth,
360+
pool_commitments.clone(),
361+
merkle_payment_timestamp,
362+
&transaction_config,
363+
)
364+
.await;
365+
366+
if result.is_err() {
367+
saw_duplicate_rejection = true;
368+
break;
369+
}
370+
assert!(
371+
i < num_pools,
372+
"Payment {i} succeeded but all {num_pools} pool slots should be filled"
373+
);
374+
}
370375

371-
assert!(result.is_err(), "Duplicate payment should be rejected");
376+
assert!(
377+
saw_duplicate_rejection,
378+
"Expected at least one duplicate rejection in {max_attempts} attempts"
379+
);
372380
}

0 commit comments

Comments
 (0)