saorsa_node/payment/
verifier.rs1use crate::error::{Error, Result};
7use crate::payment::cache::{VerifiedCache, XorName};
8use ant_evm::ProofOfPayment;
9use evmlib::Network as EvmNetwork;
10use tracing::{debug, info, warn};
11
12#[derive(Debug, Clone)]
14pub struct EvmVerifierConfig {
15 pub network: EvmNetwork,
17 pub enabled: bool,
19}
20
21impl Default for EvmVerifierConfig {
22 fn default() -> Self {
23 Self {
24 network: EvmNetwork::ArbitrumOne,
25 enabled: true,
26 }
27 }
28}
29
30#[derive(Debug, Clone)]
35pub struct PaymentVerifierConfig {
36 pub evm: EvmVerifierConfig,
38 pub cache_capacity: usize,
40}
41
42impl Default for PaymentVerifierConfig {
43 fn default() -> Self {
44 Self {
45 evm: EvmVerifierConfig::default(),
46 cache_capacity: 100_000,
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum PaymentStatus {
54 CachedAsVerified,
56 PaymentRequired,
58 PaymentVerified,
60}
61
62impl PaymentStatus {
63 #[must_use]
65 pub fn can_store(&self) -> bool {
66 matches!(self, Self::CachedAsVerified | Self::PaymentVerified)
67 }
68
69 #[must_use]
71 pub fn is_cached(&self) -> bool {
72 matches!(self, Self::CachedAsVerified)
73 }
74}
75
76pub struct PaymentVerifier {
82 cache: VerifiedCache,
84 config: PaymentVerifierConfig,
86}
87
88impl PaymentVerifier {
89 #[must_use]
91 pub fn new(config: PaymentVerifierConfig) -> Self {
92 let cache = VerifiedCache::with_capacity(config.cache_capacity);
93
94 info!(
95 "Payment verifier initialized (cache_capacity={}, evm_enabled={})",
96 config.cache_capacity, config.evm.enabled
97 );
98
99 Self { cache, config }
100 }
101
102 pub fn check_payment_required(&self, xorname: &XorName) -> PaymentStatus {
117 if self.cache.contains(xorname) {
119 debug!("Data {} found in verified cache", hex::encode(xorname));
120 return PaymentStatus::CachedAsVerified;
121 }
122
123 debug!(
125 "Data {} not in cache - payment required",
126 hex::encode(xorname)
127 );
128 PaymentStatus::PaymentRequired
129 }
130
131 pub async fn verify_payment(
151 &self,
152 xorname: &XorName,
153 payment_proof: Option<&[u8]>,
154 ) -> Result<PaymentStatus> {
155 let status = self.check_payment_required(xorname);
157
158 match status {
159 PaymentStatus::CachedAsVerified => {
160 Ok(status)
162 }
163 PaymentStatus::PaymentRequired => {
164 match payment_proof {
166 Some(proof) => {
167 if proof.is_empty() {
168 return Err(Error::Payment("Empty payment proof".to_string()));
169 }
170
171 let payment: ProofOfPayment =
173 rmp_serde::from_slice(proof).map_err(|e| {
174 Error::Payment(format!("Failed to deserialize payment proof: {e}"))
175 })?;
176
177 self.verify_evm_payment(xorname, &payment).await?;
179
180 self.cache.insert(*xorname);
182
183 Ok(PaymentStatus::PaymentVerified)
184 }
185 None => {
186 Err(Error::Payment(format!(
188 "Payment required for new data {}",
189 hex::encode(xorname)
190 )))
191 }
192 }
193 }
194 PaymentStatus::PaymentVerified => {
195 Ok(status)
197 }
198 }
199 }
200
201 #[must_use]
203 pub fn cache_stats(&self) -> crate::payment::cache::CacheStats {
204 self.cache.stats()
205 }
206
207 #[must_use]
209 pub fn cache_len(&self) -> usize {
210 self.cache.len()
211 }
212
213 #[must_use]
215 pub fn evm_enabled(&self) -> bool {
216 self.config.evm.enabled
217 }
218
219 async fn verify_evm_payment(&self, xorname: &XorName, payment: &ProofOfPayment) -> Result<()> {
225 debug!(
226 "Verifying EVM payment for {} with {} quotes",
227 hex::encode(xorname),
228 payment.peer_quotes.len()
229 );
230
231 if !self.config.evm.enabled {
233 warn!("EVM verification disabled - accepting payment without on-chain check");
234 return Ok(());
235 }
236
237 for (encoded_peer_id, quote) in &payment.peer_quotes {
239 let peer_id = encoded_peer_id
240 .to_peer_id()
241 .map_err(|e| Error::Payment(format!("Invalid peer ID in payment proof: {e}")))?;
242
243 if !quote.check_is_signed_by_claimed_peer(peer_id) {
244 return Err(Error::Payment(format!(
245 "Quote signature invalid for peer {peer_id}"
246 )));
247 }
248 }
249
250 let payment_digest = payment.digest();
252
253 if payment_digest.is_empty() {
254 return Err(Error::Payment("Payment has no quotes".to_string()));
255 }
256
257 let owned_quote_hashes = vec![];
261 match evmlib::contract::payment_vault::verify_data_payment(
262 &self.config.evm.network,
263 owned_quote_hashes,
264 payment_digest,
265 )
266 .await
267 {
268 Ok(_amount) => {
269 info!("EVM payment verified for {}", hex::encode(xorname));
270 Ok(())
271 }
272 Err(evmlib::contract::payment_vault::error::Error::PaymentInvalid) => {
273 Err(Error::Payment(format!(
274 "Payment verification failed on-chain for {}",
275 hex::encode(xorname)
276 )))
277 }
278 Err(e) => Err(Error::Payment(format!(
279 "EVM verification error for {}: {e}",
280 hex::encode(xorname)
281 ))),
282 }
283 }
284}
285
286#[cfg(test)]
287#[allow(clippy::expect_used)]
288mod tests {
289 use super::*;
290
291 fn create_test_verifier() -> PaymentVerifier {
292 let config = PaymentVerifierConfig {
293 evm: EvmVerifierConfig {
294 enabled: false, ..Default::default()
296 },
297 cache_capacity: 100,
298 };
299 PaymentVerifier::new(config)
300 }
301
302 #[test]
303 fn test_payment_required_for_new_data() {
304 let verifier = create_test_verifier();
305 let xorname = [1u8; 32];
306
307 let status = verifier.check_payment_required(&xorname);
309 assert_eq!(status, PaymentStatus::PaymentRequired);
310 }
311
312 #[test]
313 fn test_cache_hit() {
314 let verifier = create_test_verifier();
315 let xorname = [1u8; 32];
316
317 verifier.cache.insert(xorname);
319
320 let status = verifier.check_payment_required(&xorname);
322 assert_eq!(status, PaymentStatus::CachedAsVerified);
323 }
324
325 #[tokio::test]
326 async fn test_verify_payment_without_proof() {
327 let verifier = create_test_verifier();
328 let xorname = [1u8; 32];
329
330 let result = verifier.verify_payment(&xorname, None).await;
332 assert!(result.is_err());
333 }
334
335 #[tokio::test]
336 async fn test_verify_payment_with_proof() {
337 let verifier = create_test_verifier();
338 let xorname = [1u8; 32];
339
340 let proof = ProofOfPayment {
342 peer_quotes: vec![],
343 };
344 let proof_bytes = rmp_serde::to_vec(&proof).expect("should serialize");
345
346 let result = verifier.verify_payment(&xorname, Some(&proof_bytes)).await;
349 assert!(result.is_ok(), "Expected Ok, got: {result:?}");
350 assert_eq!(result.expect("verified"), PaymentStatus::PaymentVerified);
351 }
352
353 #[tokio::test]
354 async fn test_verify_payment_cached() {
355 let verifier = create_test_verifier();
356 let xorname = [1u8; 32];
357
358 verifier.cache.insert(xorname);
360
361 let result = verifier.verify_payment(&xorname, None).await;
363 assert!(result.is_ok());
364 assert_eq!(result.expect("cached"), PaymentStatus::CachedAsVerified);
365 }
366
367 #[test]
368 fn test_payment_status_can_store() {
369 assert!(PaymentStatus::CachedAsVerified.can_store());
370 assert!(PaymentStatus::PaymentVerified.can_store());
371 assert!(!PaymentStatus::PaymentRequired.can_store());
372 }
373
374 #[test]
375 fn test_payment_status_is_cached() {
376 assert!(PaymentStatus::CachedAsVerified.is_cached());
377 assert!(!PaymentStatus::PaymentVerified.is_cached());
378 assert!(!PaymentStatus::PaymentRequired.is_cached());
379 }
380}