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