1use core::cell::Cell;
8use std::env;
9use std::num::NonZeroUsize;
10use std::sync::{Mutex, MutexGuard, OnceLock};
11
12use dusk_core::plonk::PlonkVersion;
13use lru::LruCache;
14
15use super::HardFork;
16
17const fn plonk_cache_revision(version: PlonkVersion) -> u32 {
22 match version {
23 PlonkVersion::V1 => 1,
24 PlonkVersion::V2 => 2,
25 PlonkVersion::V3 => 3,
26 _ => u32::MAX,
27 }
28}
29
30const fn bls_cache_revision(hard_fork: HardFork) -> u32 {
31 match hard_fork {
32 HardFork::PreFork => 0,
33 HardFork::Aegis | HardFork::Boreas => 1,
34 }
35}
36
37#[derive(Debug, Clone, Copy, Eq, PartialEq)]
39pub struct HostQueryPolicy {
40 pub plonk_version: PlonkVersion,
42 pub hard_fork: HardFork,
44}
45
46impl HostQueryPolicy {
47 pub const fn from_versions(
49 plonk_version: PlonkVersion,
50 hard_fork: HardFork,
51 ) -> Self {
52 Self {
53 plonk_version,
54 hard_fork,
55 }
56 }
57}
58
59thread_local! {
60 static HOST_QUERY_POLICY: Cell<HostQueryPolicy> = const {
63 Cell::new(HostQueryPolicy::from_versions(
64 PlonkVersion::V2,
65 HardFork::PreFork,
66 ))
67 };
68}
69
70#[derive(Debug, Clone, Copy, Eq, PartialEq)]
71pub(super) enum CacheDomain {
72 Hash,
73 PoseidonHash,
74 Plonk,
75 Groth16Bn254,
76 Schnorr,
77 Bls,
78 BlsMultisig,
79 Keccak256,
80 Sha256,
81 Kzg,
82 Secp256k1Recover,
83}
84
85impl CacheDomain {
86 const fn tag(self) -> u8 {
87 match self {
88 CacheDomain::Hash => 0,
89 CacheDomain::PoseidonHash => 1,
90 CacheDomain::Plonk => 2,
91 CacheDomain::Groth16Bn254 => 3,
92 CacheDomain::Schnorr => 4,
93 CacheDomain::Bls => 5,
94 CacheDomain::BlsMultisig => 6,
95 CacheDomain::Keccak256 => 7,
96 CacheDomain::Sha256 => 8,
97 CacheDomain::Kzg => 9,
98 CacheDomain::Secp256k1Recover => 10,
99 }
100 }
101}
102
103const fn cache_revision(policy: HostQueryPolicy, domain: CacheDomain) -> u32 {
107 match domain {
108 CacheDomain::Plonk => plonk_cache_revision(policy.plonk_version),
109 CacheDomain::Bls | CacheDomain::BlsMultisig => {
110 bls_cache_revision(policy.hard_fork)
111 }
112 CacheDomain::Hash
113 | CacheDomain::PoseidonHash
114 | CacheDomain::Groth16Bn254
115 | CacheDomain::Schnorr
116 | CacheDomain::Keccak256
117 | CacheDomain::Sha256
118 | CacheDomain::Kzg
119 | CacheDomain::Secp256k1Recover => 0,
120 }
121}
122
123#[derive(Debug)]
125pub struct HostQueryPolicyGuard {
126 prev: HostQueryPolicy,
127}
128
129impl Drop for HostQueryPolicyGuard {
130 fn drop(&mut self) {
131 HOST_QUERY_POLICY.with(|m| m.set(self.prev));
132 }
133}
134
135pub fn host_query_policy() -> HostQueryPolicy {
137 HOST_QUERY_POLICY.with(|m| m.get())
138}
139
140pub fn set_host_query_policy(policy: HostQueryPolicy) -> HostQueryPolicyGuard {
144 let prev = HOST_QUERY_POLICY.with(|m| {
145 let prev = m.get();
146 m.set(policy);
147 prev
148 });
149 HostQueryPolicyGuard { prev }
150}
151
152pub fn plonk_version() -> PlonkVersion {
154 host_query_policy().plonk_version
155}
156
157pub fn hard_fork() -> HardFork {
159 host_query_policy().hard_fork
160}
161
162pub(super) fn cache_key_with_revision(
163 domain: CacheDomain,
164 revision: u32,
165 arg_buf: &[u8],
166) -> [u8; blake2b_simd::OUTBYTES] {
167 let mut state = blake2b_simd::Params::new()
169 .hash_length(blake2b_simd::OUTBYTES)
170 .to_state();
171 state.update(&[domain.tag()]);
172 state.update(&revision.to_le_bytes());
173 state.update(arg_buf);
174 *state.finalize().as_array()
175}
176
177pub(super) fn cache_key(
178 domain: CacheDomain,
179 arg_buf: &[u8],
180) -> [u8; blake2b_simd::OUTBYTES] {
181 let revision = cache_revision(host_query_policy(), domain);
182 cache_key_with_revision(domain, revision, arg_buf)
183}
184
185type ScalarCacheValue = dusk_core::BlsScalar;
186type RecoverCacheValue = Option<[u8; 65]>;
187
188macro_rules! define_cache {
189 ($get_func:ident, $put_func:ident, $cache_func:ident, $type:ty, $size:literal, $var:literal) => {
190 pub fn $get_func(hash: [u8; blake2b_simd::OUTBYTES]) -> Option<$type> {
194 unsafe { $cache_func(|mut cache| cache.get(&hash).cloned()) }
196 }
197
198 pub fn $put_func(hash: [u8; blake2b_simd::OUTBYTES], value: $type) {
200 unsafe {
202 $cache_func(|mut cache| {
203 cache.put(hash, value);
204 });
205 }
206 }
207
208 unsafe fn $cache_func<T, F>(f: F) -> T
213 where
214 F: FnOnce(
215 MutexGuard<LruCache<[u8; blake2b_simd::OUTBYTES], $type>>,
216 ) -> T,
217 {
218 const DEFAULT_SIZE: usize = $size;
219
220 static CACHE: OnceLock<
221 Mutex<LruCache<[u8; blake2b_simd::OUTBYTES], $type>>,
222 > = OnceLock::new();
223
224 CACHE
225 .get_or_init(|| {
226 let mut cache_size = None;
227
228 if let Ok(s) = env::var($var) {
229 cache_size = s.parse().ok();
230 }
231
232 let mut cache_size = cache_size.unwrap_or(DEFAULT_SIZE);
233 if cache_size == 0 {
234 cache_size = DEFAULT_SIZE;
235 }
236
237 Mutex::new(LruCache::new(
238 NonZeroUsize::new(cache_size).unwrap(),
239 ))
240 })
241 .lock()
242 .map(f)
243 .unwrap()
244 }
245 };
246}
247
248define_cache!(
249 get_plonk_verification,
250 put_plonk_verification,
251 with_plonk_cache,
252 bool,
253 2048,
254 "DUSK_VM_PLONK_CACHE_SIZE"
255);
256define_cache!(
257 get_groth16_verification,
258 put_groth16_verification,
259 with_groth16_cache,
260 bool,
261 2048,
262 "DUSK_VM_GROTH16_CACHE_SIZE"
263);
264define_cache!(
265 get_bls_verification,
266 put_bls_verification,
267 with_bls_cache,
268 bool,
269 2048,
270 "DUSK_VM_BLS_CACHE_SIZE"
271);
272define_cache!(
273 get_hash,
274 put_hash,
275 with_hash_cache,
276 ScalarCacheValue,
277 2048,
278 "DUSK_VM_HASH_CACHE_SIZE"
279);
280define_cache!(
281 get_poseidon_hash,
282 put_poseidon_hash,
283 with_poseidon_hash_cache,
284 ScalarCacheValue,
285 2048,
286 "DUSK_VM_POSEIDON_HASH_CACHE_SIZE"
287);
288define_cache!(
289 get_schnorr_verification,
290 put_schnorr_verification,
291 with_schnorr_cache,
292 bool,
293 2048,
294 "DUSK_VM_SCHNORR_CACHE_SIZE"
295);
296define_cache!(
297 get_bls_multisig_verification,
298 put_bls_multisig_verification,
299 with_bls_multisig_cache,
300 bool,
301 2048,
302 "DUSK_VM_BLS_MULTISIG_CACHE_SIZE"
303);
304define_cache!(
305 get_keccak256,
306 put_keccak256,
307 with_keccak256_cache,
308 [u8; 32],
309 2048,
310 "DUSK_VM_KECCAK256_CACHE_SIZE"
311);
312define_cache!(
313 get_sha256,
314 put_sha256,
315 with_sha256_cache,
316 [u8; 32],
317 2048,
318 "DUSK_VM_SHA256_CACHE_SIZE"
319);
320define_cache!(
321 get_kzg_verification,
322 put_kzg_verification,
323 with_kzg_cache,
324 bool,
325 2048,
326 "DUSK_VM_KZG_CACHE_SIZE"
327);
328define_cache!(
329 get_secp256k1_recover,
330 put_secp256k1_recover,
331 with_secp256k1_recover_cache,
332 RecoverCacheValue,
333 2048,
334 "DUSK_VM_SECP256K1_RECOVER_CACHE_SIZE"
335);
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn host_query_policy_revisions_follow_versions() {
343 let prefork =
344 HostQueryPolicy::from_versions(PlonkVersion::V1, HardFork::PreFork);
345 let aegis =
346 HostQueryPolicy::from_versions(PlonkVersion::V3, HardFork::Aegis);
347 let boreas =
348 HostQueryPolicy::from_versions(PlonkVersion::V3, HardFork::Boreas);
349
350 assert_ne!(
351 cache_revision(prefork, CacheDomain::Plonk),
352 cache_revision(aegis, CacheDomain::Plonk)
353 );
354 assert_ne!(
355 cache_revision(prefork, CacheDomain::Bls),
356 cache_revision(aegis, CacheDomain::Bls)
357 );
358 assert_eq!(
359 cache_revision(aegis, CacheDomain::Bls),
360 cache_revision(aegis, CacheDomain::BlsMultisig)
361 );
362 assert_eq!(
363 cache_revision(aegis, CacheDomain::Bls),
364 cache_revision(boreas, CacheDomain::Bls)
365 );
366 assert_eq!(cache_revision(prefork, CacheDomain::Schnorr), 0);
367 }
368
369 #[test]
370 fn cache_key_is_domain_and_revision_separated() {
371 let arg = [1u8, 2, 3, 4];
372
373 let hash_key = cache_key_with_revision(CacheDomain::Hash, 0, &arg);
374 let schnorr_key =
375 cache_key_with_revision(CacheDomain::Schnorr, 0, &arg);
376 let bumped_hash_key =
377 cache_key_with_revision(CacheDomain::Hash, 1, &arg);
378
379 assert_ne!(hash_key, schnorr_key);
380 assert_ne!(hash_key, bumped_hash_key);
381 }
382
383 #[test]
384 fn set_host_query_policy_restores_previous_policy() {
385 let prev = host_query_policy();
386 let next =
387 HostQueryPolicy::from_versions(PlonkVersion::V3, HardFork::Boreas);
388
389 {
390 let _guard = set_host_query_policy(next);
391 assert_eq!(host_query_policy(), next);
392 }
393
394 assert_eq!(host_query_policy(), prev);
395 }
396
397 #[test]
398 fn host_query_policy_updates_cache_revisions() {
399 let prev = host_query_policy();
400
401 {
402 let _guard = set_host_query_policy(HostQueryPolicy::from_versions(
403 PlonkVersion::V1,
404 prev.hard_fork,
405 ));
406 assert_eq!(plonk_version(), PlonkVersion::V1);
407 assert_eq!(
408 cache_revision(host_query_policy(), CacheDomain::Plonk),
409 plonk_cache_revision(PlonkVersion::V1)
410 );
411 }
412
413 assert_eq!(host_query_policy(), prev);
414
415 {
416 let _guard = set_host_query_policy(HostQueryPolicy::from_versions(
417 prev.plonk_version,
418 HardFork::Boreas,
419 ));
420 assert_eq!(hard_fork(), HardFork::Boreas);
421 assert_eq!(
422 cache_revision(host_query_policy(), CacheDomain::Bls),
423 bls_cache_revision(HardFork::Boreas)
424 );
425 assert_eq!(
426 cache_revision(host_query_policy(), CacheDomain::BlsMultisig),
427 bls_cache_revision(HardFork::Boreas)
428 );
429 }
430
431 assert_eq!(host_query_policy(), prev);
432 }
433}