|
6 | 6 | // KIND, either express or implied. Please review the Licences for the specific language governing |
7 | 7 | // permissions and limitations relating to use of the SAFE Network Software. |
8 | 8 |
|
9 | | -//! Merkle batch payment types and disk-based mock smart contract |
| 9 | +//! Merkle batch payment types |
10 | 10 | //! |
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. |
14 | 12 |
|
15 | 13 | use crate::common::{Address as RewardsAddress, Amount}; |
16 | 14 |
|
17 | 15 | #[cfg(test)] |
18 | 16 | use crate::common::U256; |
19 | 17 | use serde::{Deserialize, Serialize}; |
20 | 18 |
|
21 | | -#[cfg(test)] |
22 | | -use std::path::PathBuf; |
23 | | - |
24 | | -#[cfg(test)] |
25 | | -use thiserror::Error; |
26 | | - |
27 | 19 | /// Pool hash type (32 bytes) - compatible with XorName without the dependency |
28 | 20 | pub type PoolHash = [u8; 32]; |
29 | 21 |
|
@@ -81,180 +73,6 @@ pub struct OnChainPaymentInfo { |
81 | 73 | pub paid_node_addresses: Vec<(RewardsAddress, usize, Amount)>, |
82 | 74 | } |
83 | 75 |
|
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 | | - |
258 | 76 | #[cfg(test)] |
259 | 77 | mod tests { |
260 | 78 | use super::*; |
|
0 commit comments