1use crate::associations::AccountId;
2use crate::scw_verifier::{SmartContractSignatureVerifier, ValidationResponse, VerifierError};
3use alloy::primitives::{BlockNumber, Bytes, keccak256};
4use lru::LruCache;
5use parking_lot::Mutex;
6use std::num::NonZeroUsize;
7
8type CacheKey = [u8; 32];
12
13fn build_cache_key(
18 account_id: &AccountId,
19 hash: &[u8; 32],
20 signature: &[u8],
21 block_number: Option<BlockNumber>,
22) -> CacheKey {
23 let chain_id = account_id.get_chain_id().as_bytes();
24 let account_address = account_id.get_account_address().as_bytes();
25 let bn_bytes = block_number.map(|bn| bn.to_be_bytes());
26
27 let capacity = 4 + chain_id.len() + 4 + account_address.len() + 32 + 4 + signature.len() + 9;
29 let mut buf = Vec::with_capacity(capacity);
30
31 buf.extend_from_slice(&(chain_id.len() as u32).to_be_bytes());
33 buf.extend_from_slice(chain_id);
34 buf.extend_from_slice(&(account_address.len() as u32).to_be_bytes());
35 buf.extend_from_slice(account_address);
36 buf.extend_from_slice(hash);
37 buf.extend_from_slice(&(signature.len() as u32).to_be_bytes());
38 buf.extend_from_slice(signature);
39 match bn_bytes {
40 Some(bytes) => {
41 buf.push(0x01);
42 buf.extend_from_slice(&bytes);
43 }
44 None => buf.push(0x00),
45 }
46 *keccak256(&buf)
47}
48
49pub struct CachedSmartContractSignatureVerifier {
54 verifier: Box<dyn SmartContractSignatureVerifier>,
55 cache: Mutex<LruCache<CacheKey, ValidationResponse>>,
56}
57
58impl CachedSmartContractSignatureVerifier {
59 pub fn new(
64 verifier: impl SmartContractSignatureVerifier + 'static,
65 cache_size: NonZeroUsize,
66 ) -> Result<Self, VerifierError> {
67 Ok(Self {
68 verifier: Box::new(verifier),
69 cache: Mutex::new(LruCache::new(cache_size)),
70 })
71 }
72}
73
74#[xmtp_common::async_trait]
75impl SmartContractSignatureVerifier for CachedSmartContractSignatureVerifier {
76 async fn is_valid_signature(
82 &self,
83 account_id: AccountId,
84 hash: [u8; 32],
85 signature: Bytes,
86 block_number: Option<BlockNumber>,
87 ) -> Result<ValidationResponse, VerifierError> {
88 if block_number.is_none() {
90 return self
91 .verifier
92 .is_valid_signature(account_id, hash, signature, None)
93 .await;
94 }
95 let cache_key = build_cache_key(&account_id, &hash, &signature, block_number);
96
97 if let Some(cached_response) = {
98 let mut cache = self.cache.lock();
99 cache.get(&cache_key).cloned()
100 } {
101 return Ok(cached_response);
102 }
103
104 let response = self
105 .verifier
106 .is_valid_signature(account_id, hash, signature, block_number)
107 .await?;
108
109 let mut cache = self.cache.lock();
110 cache.put(cache_key, response.clone());
111
112 Ok(response)
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use std::sync::{
120 Arc,
121 atomic::{AtomicUsize, Ordering},
122 };
123 struct CountingVerifier {
124 calls: Arc<AtomicUsize>,
125 valid: bool,
126 error: bool,
127 }
128 #[xmtp_common::async_trait]
129 impl SmartContractSignatureVerifier for CountingVerifier {
130 async fn is_valid_signature(
131 &self,
132 _: AccountId,
133 _: [u8; 32],
134 _: Bytes,
135 block: Option<BlockNumber>,
136 ) -> Result<ValidationResponse, VerifierError> {
137 let call = self.calls.fetch_add(1, Ordering::SeqCst);
138 if self.error {
139 return Err(VerifierError::NoVerifier("eip155:1".into()));
140 }
141 Ok(ValidationResponse {
142 is_valid: self.valid,
143 block_number: Some(block.unwrap_or(call as u64)),
144 error: None,
145 })
146 }
147 }
148 #[xmtp_common::test(unwrap_try = true)]
149 async fn cache_preserves_numbered_verdicts_and_bypasses_latest() {
150 for valid in [true, false] {
151 let calls = Arc::new(AtomicUsize::new(0));
152 let cache = CachedSmartContractSignatureVerifier::new(
153 CountingVerifier {
154 calls: calls.clone(),
155 valid,
156 error: false,
157 },
158 NonZeroUsize::new(1).unwrap(),
159 )?;
160 let account = AccountId::new_evm(1, "0xaaa".into());
161 for _ in 0..2 {
162 let result = cache
163 .is_valid_signature(account.clone(), [0; 32], Bytes::new(), Some(1))
164 .await?;
165 assert_eq!(result.is_valid, valid);
166 }
167 assert_eq!(calls.load(Ordering::SeqCst), 1);
168 let first = cache
169 .is_valid_signature(account.clone(), [0; 32], Bytes::new(), None)
170 .await?;
171 let second = cache
172 .is_valid_signature(account.clone(), [0; 32], Bytes::new(), None)
173 .await?;
174 assert_ne!(first.block_number, second.block_number);
175 assert_eq!(calls.load(Ordering::SeqCst), 3);
176 cache
177 .is_valid_signature(account.clone(), [0; 32], Bytes::new(), Some(2))
178 .await?;
179 cache
180 .is_valid_signature(account, [0; 32], Bytes::new(), Some(1))
181 .await?;
182 assert_eq!(calls.load(Ordering::SeqCst), 5);
183 }
184 }
185 #[xmtp_common::test(unwrap_try = true)]
186 async fn verifier_errors_are_never_cached() {
187 let calls = Arc::new(AtomicUsize::new(0));
188 let cache = CachedSmartContractSignatureVerifier::new(
189 CountingVerifier {
190 calls: calls.clone(),
191 valid: false,
192 error: true,
193 },
194 NonZeroUsize::new(1).unwrap(),
195 )?;
196 for _ in 0..2 {
197 assert!(matches!(
198 cache
199 .is_valid_signature(
200 AccountId::new_evm(1, "0xaaa".into()),
201 [0; 32],
202 Bytes::new(),
203 Some(1)
204 )
205 .await,
206 Err(VerifierError::NoVerifier(_))
207 ));
208 }
209 assert_eq!(calls.load(Ordering::SeqCst), 2);
210 }
211 #[xmtp_common::test(unwrap_try = true)]
212 fn cache_key_binds_every_parameter() {
213 let account = AccountId::new("eip155:1".into(), "0xaaa".into());
214 let key = build_cache_key(&account, &[0; 32], &[1], Some(0));
215 assert_ne!(
216 key,
217 build_cache_key(
218 &AccountId::new("eip155:2".into(), "0xaaa".into()),
219 &[0; 32],
220 &[1],
221 Some(0)
222 )
223 );
224 assert_ne!(
225 key,
226 build_cache_key(
227 &AccountId::new("eip155:1".into(), "0xbbb".into()),
228 &[0; 32],
229 &[1],
230 Some(0)
231 )
232 );
233 assert_ne!(key, build_cache_key(&account, &[1; 32], &[1], Some(0)));
234 assert_ne!(key, build_cache_key(&account, &[0; 32], &[2], Some(0)));
235 assert_ne!(key, build_cache_key(&account, &[0; 32], &[1], Some(1)));
236 assert_ne!(key, build_cache_key(&account, &[0; 32], &[1], None));
237 let left = AccountId::new("ab".into(), "c".into());
238 let right = AccountId::new("a".into(), "bc".into());
239 assert_ne!(
240 build_cache_key(&left, &[0; 32], &[1], Some(0)),
241 build_cache_key(&right, &[0; 32], &[1], Some(0))
242 );
243 }
244}