cfxcore_pow/
cache.rs

1use parking_lot::Mutex;
2
3use super::{
4    compute::Light,
5    keccak::{keccak_512, H256},
6    seed_compute::SeedHashCompute,
7    shared::{
8        get_cache_size, Node, NODE_BYTES, POW_CACHE_ROUNDS, POW_STAGE_LENGTH,
9    },
10};
11
12use lru_cache::LruCache;
13use std::{slice, sync::Arc};
14
15pub type Cache = Vec<Node>;
16
17/// Number of per-stage caches kept in memory, evicted least-recently-used.
18/// A stage spans `POW_STAGE_LENGTH` blocks, so a few entries cover normal
19/// operation around a stage boundary; keeping every stage ever seen would
20/// retain the cache of each stage passed during a sync from genesis.
21const CACHE_KEEP: usize = 3;
22
23#[derive(Clone)]
24pub struct CacheBuilder {
25    seedhash: Arc<Mutex<SeedHashCompute>>,
26    caches: Arc<Mutex<LruCache<u64, Arc<Cache>>>>,
27}
28
29impl CacheBuilder {
30    pub fn new() -> Self {
31        CacheBuilder {
32            seedhash: Arc::new(Mutex::new(SeedHashCompute::default())),
33            caches: Arc::new(Mutex::new(LruCache::new(CACHE_KEEP))),
34        }
35    }
36
37    pub fn light(&self, block_height: u64) -> Light {
38        Light::new_with_builder(self, block_height)
39    }
40
41    fn block_height_to_ident(&self, block_height: u64) -> H256 {
42        self.seedhash.lock().hash_block_height(block_height)
43    }
44
45    #[allow(dead_code)]
46    fn stage_to_ident(&self, stage: u64) -> H256 {
47        self.seedhash.lock().hash_stage(stage)
48    }
49
50    pub fn new_cache(&self, block_height: u64) -> Arc<Cache> {
51        let stage = block_height / POW_STAGE_LENGTH;
52
53        let mut caches = self.caches.lock();
54        if let Some(cache) = caches.get_mut(&stage) {
55            return cache.clone();
56        }
57
58        let ident = self.block_height_to_ident(block_height);
59        let cache_size = get_cache_size(block_height);
60
61        // We use `debug_assert` since it is impossible for `get_cache_size` to
62        // return an unaligned value with the current implementation. If
63        // the implementation changes, CI will catch it.
64        debug_assert!(cache_size % NODE_BYTES == 0, "Unaligned cache size");
65        let num_nodes = cache_size / NODE_BYTES;
66
67        let cache = Arc::new(make_memory_cache(num_nodes, &ident));
68        caches.insert(stage, cache.clone());
69
70        cache
71    }
72}
73
74fn make_memory_cache(num_nodes: usize, ident: &H256) -> Cache {
75    let mut nodes: Vec<Node> = Vec::with_capacity(num_nodes);
76    // Use uninit instead of unnecessarily writing `size_of::<Node>() *
77    // num_nodes` 0s
78    unsafe {
79        initialize_memory(nodes.as_mut_ptr(), num_nodes, ident);
80        nodes.set_len(num_nodes);
81    }
82
83    nodes
84}
85
86// This takes a raw pointer and a counter because `memory` may be uninitialized.
87// `memory` _must_ be a pointer to the beginning of an allocated but
88// possibly-uninitialized block of `num_nodes * NODE_BYTES` bytes
89//
90// We have to use raw pointers to read/write uninit, using "normal" indexing
91// causes LLVM to freak out. It counts as a read and causes all writes
92// afterwards to be elided. Yes, really. I know, I want to refactor this to use
93// less `unsafe` as much as the next rustacean.
94unsafe fn initialize_memory(memory: *mut Node, num_nodes: usize, ident: &H256) {
95    // We use raw pointers here, see above
96    let dst = slice::from_raw_parts_mut(memory as *mut u8, NODE_BYTES);
97
98    debug_assert_eq!(ident.len(), 32);
99    keccak_512::write(&ident[..], dst);
100
101    for i in 1..num_nodes {
102        // We use raw pointers here, see above
103        let dst = slice::from_raw_parts_mut(
104            memory.offset(i as _) as *mut u8,
105            NODE_BYTES,
106        );
107        let src = slice::from_raw_parts(
108            memory.offset(i as isize - 1) as *mut u8,
109            NODE_BYTES,
110        );
111        keccak_512::write(src, dst);
112    }
113
114    // Now this is initialized, we can treat it as a slice.
115    let nodes: &mut [Node] = slice::from_raw_parts_mut(memory, num_nodes);
116
117    for _ in 0..POW_CACHE_ROUNDS {
118        for i in 0..num_nodes {
119            let data_idx = (num_nodes - 1 + i) % num_nodes;
120            let idx =
121                nodes.get_unchecked_mut(i).as_words()[0] as usize % num_nodes;
122
123            let data = {
124                let mut data: Node = nodes.get_unchecked(data_idx).clone();
125                let rhs: &Node = nodes.get_unchecked(idx);
126
127                for (a, b) in
128                    data.as_dwords_mut().iter_mut().zip(rhs.as_dwords())
129                {
130                    *a ^= *b;
131                }
132
133                data
134            };
135
136            keccak_512::write(
137                &data.bytes,
138                &mut nodes.get_unchecked_mut(i).bytes,
139            );
140        }
141    }
142}