saorsa_node/payment/
quote.rs1use crate::error::Result;
11use crate::payment::metrics::QuotingMetricsTracker;
12use ant_evm::{PaymentQuote, QuotingMetrics, RewardsAddress};
13use std::time::SystemTime;
14use tracing::debug;
15
16pub type XorName = [u8; 32];
18
19pub type SignFn = Box<dyn Fn(&[u8]) -> Vec<u8> + Send + Sync>;
21
22pub struct QuoteGenerator {
27 rewards_address: RewardsAddress,
29 metrics_tracker: QuotingMetricsTracker,
31 sign_fn: Option<SignFn>,
34 pub_key: Vec<u8>,
36}
37
38impl QuoteGenerator {
39 #[must_use]
48 pub fn new(rewards_address: RewardsAddress, metrics_tracker: QuotingMetricsTracker) -> Self {
49 Self {
50 rewards_address,
51 metrics_tracker,
52 sign_fn: None,
53 pub_key: Vec::new(),
54 }
55 }
56
57 pub fn set_signer<F>(&mut self, pub_key: Vec<u8>, sign_fn: F)
64 where
65 F: Fn(&[u8]) -> Vec<u8> + Send + Sync + 'static,
66 {
67 self.pub_key = pub_key;
68 self.sign_fn = Some(Box::new(sign_fn));
69 }
70
71 #[must_use]
73 pub fn can_sign(&self) -> bool {
74 self.sign_fn.is_some()
75 }
76
77 pub fn create_quote(
93 &self,
94 content: XorName,
95 data_size: usize,
96 data_type: u32,
97 ) -> Result<PaymentQuote> {
98 let sign_fn = self.sign_fn.as_ref().ok_or_else(|| {
99 crate::error::Error::Payment("Quote signing not configured".to_string())
100 })?;
101
102 let timestamp = SystemTime::now();
103
104 let quoting_metrics = self.metrics_tracker.get_metrics(data_size, data_type);
106
107 let xor_name = xor_name::XorName(content);
109
110 let bytes = PaymentQuote::bytes_for_signing(
112 xor_name,
113 timestamp,
114 "ing_metrics,
115 &self.rewards_address,
116 );
117
118 let signature = sign_fn(&bytes);
120
121 let quote = PaymentQuote {
122 content: xor_name,
123 timestamp,
124 quoting_metrics,
125 pub_key: self.pub_key.clone(),
126 rewards_address: self.rewards_address,
127 signature,
128 };
129
130 debug!(
131 "Generated quote for {} (size: {}, type: {})",
132 hex::encode(content),
133 data_size,
134 data_type
135 );
136
137 Ok(quote)
138 }
139
140 #[must_use]
142 pub fn rewards_address(&self) -> &RewardsAddress {
143 &self.rewards_address
144 }
145
146 #[must_use]
148 pub fn current_metrics(&self) -> QuotingMetrics {
149 self.metrics_tracker.get_metrics(0, 0)
150 }
151
152 pub fn record_payment(&self) {
154 self.metrics_tracker.record_payment();
155 }
156
157 pub fn record_store(&self, data_type: u32) {
159 self.metrics_tracker.record_store(data_type);
160 }
161}
162
163#[must_use]
174pub fn verify_quote_content(quote: &PaymentQuote, expected_content: &XorName) -> bool {
175 if quote.content.0 != *expected_content {
177 debug!(
178 "Quote content mismatch: expected {}, got {}",
179 hex::encode(expected_content),
180 hex::encode(quote.content.0)
181 );
182 return false;
183 }
184 true
185}
186
187#[cfg(test)]
188#[allow(clippy::expect_used)]
189mod tests {
190 use super::*;
191 use crate::payment::metrics::QuotingMetricsTracker;
192
193 fn create_test_generator() -> QuoteGenerator {
194 let rewards_address = RewardsAddress::new([1u8; 20]);
195 let metrics_tracker = QuotingMetricsTracker::new(1000, 100);
196
197 let mut generator = QuoteGenerator::new(rewards_address, metrics_tracker);
198
199 generator.set_signer(vec![0u8; 64], |bytes| {
201 let mut sig = vec![0u8; 64];
203 for (i, b) in bytes.iter().take(64).enumerate() {
204 sig[i] = *b;
205 }
206 sig
207 });
208
209 generator
210 }
211
212 #[test]
213 fn test_create_quote() {
214 let generator = create_test_generator();
215 let content = [42u8; 32];
216
217 let quote = generator.create_quote(content, 1024, 0);
218 assert!(quote.is_ok());
219
220 let quote = quote.expect("valid quote");
221 assert_eq!(quote.content.0, content);
222 }
223
224 #[test]
225 fn test_verify_quote_content() {
226 let generator = create_test_generator();
227 let content = [42u8; 32];
228
229 let quote = generator
230 .create_quote(content, 1024, 0)
231 .expect("valid quote");
232 assert!(verify_quote_content("e, &content));
233
234 let wrong_content = [99u8; 32];
236 assert!(!verify_quote_content("e, &wrong_content));
237 }
238
239 #[test]
240 fn test_generator_without_signer() {
241 let rewards_address = RewardsAddress::new([1u8; 20]);
242 let metrics_tracker = QuotingMetricsTracker::new(1000, 100);
243 let generator = QuoteGenerator::new(rewards_address, metrics_tracker);
244
245 assert!(!generator.can_sign());
246
247 let content = [42u8; 32];
248 let result = generator.create_quote(content, 1024, 0);
249 assert!(result.is_err());
250 }
251}