cfxcore_pow/
target_difficulty_manager.rs

1use crate::{ConsensusProvider, ProofOfWorkConfig};
2use cfx_types::{H256, U256};
3use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
4use malloc_size_of_derive::MallocSizeOf as DeriveMallocSizeOf;
5use parking_lot::RwLock;
6use std::collections::{HashMap, VecDeque};
7
8//FIXME: make entries replaceable
9#[derive(DeriveMallocSizeOf)]
10struct TargetDifficultyCacheInner {
11    capacity: usize,
12    meta: VecDeque<H256>,
13    cache: HashMap<H256, U256>,
14}
15
16impl TargetDifficultyCacheInner {
17    pub fn new(capacity: usize) -> Self {
18        TargetDifficultyCacheInner {
19            capacity,
20            meta: Default::default(),
21            cache: Default::default(),
22        }
23    }
24
25    pub fn is_full(&self) -> bool { self.meta.len() >= self.capacity }
26
27    pub fn evict_one(&mut self) {
28        let hash = self.meta.pop_front();
29        if let Some(h) = hash {
30            self.cache.remove(&h);
31        }
32    }
33
34    pub fn insert(&mut self, hash: H256, difficulty: U256) {
35        self.meta.push_back(hash.clone());
36        self.cache.insert(hash, difficulty);
37    }
38}
39
40struct TargetDifficultyCache {
41    inner: RwLock<TargetDifficultyCacheInner>,
42}
43
44impl MallocSizeOf for TargetDifficultyCache {
45    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
46        self.inner.read().size_of(ops)
47    }
48}
49
50impl TargetDifficultyCache {
51    pub fn new(capacity: usize) -> Self {
52        TargetDifficultyCache {
53            inner: RwLock::new(TargetDifficultyCacheInner::new(capacity)),
54        }
55    }
56
57    pub fn get(&self, hash: &H256) -> Option<U256> {
58        let inner = self.inner.read();
59        inner.cache.get(hash).map(|diff| *diff)
60    }
61
62    pub fn set(&self, hash: H256, difficulty: U256) {
63        let mut inner = self.inner.write();
64        while inner.is_full() {
65            inner.evict_one();
66        }
67        inner.insert(hash, difficulty);
68    }
69}
70
71//FIXME: Add logic for persisting entries
72/// This is a data structure to cache the computed target difficulty
73/// of a adjustment period. Each element is indexed by the hash of
74/// the upper boundary block of the period.
75#[derive(DeriveMallocSizeOf)]
76pub struct TargetDifficultyManager {
77    cache: TargetDifficultyCache,
78}
79
80impl TargetDifficultyManager {
81    pub fn new(capacity: usize) -> Self {
82        TargetDifficultyManager {
83            cache: TargetDifficultyCache::new(capacity),
84        }
85    }
86
87    pub fn get(&self, hash: &H256) -> Option<U256> { self.cache.get(hash) }
88
89    pub fn set(&self, hash: H256, difficulty: U256) {
90        self.cache.set(hash, difficulty);
91    }
92
93    /// This function computes the target difficulty of the next period
94    /// based on the current period. `cur_hash` should be the hash of
95    /// the block at the current period upper boundary and it must have been
96    /// inserted to BlockDataManager, otherwise the function will panic.
97    /// `num_blocks_in_epoch` is a function that returns the epoch size
98    /// under the epoch view of a given block.
99    pub fn target_difficulty<C>(
100        &self, consensus: C, pow_config: &ProofOfWorkConfig, cur_hash: &H256,
101    ) -> U256
102    where C: ConsensusProvider {
103        if let Some(target_diff) = self.get(cur_hash) {
104            // The target difficulty of this period is already computed and
105            // cached.
106            return target_diff;
107        }
108
109        let mut cur_header = consensus
110            .block_header_by_hash(cur_hash)
111            .expect("Must already in BlockDataManager block_header");
112        let epoch = cur_header.height();
113        assert_ne!(epoch, 0);
114        debug_assert!(
115            epoch
116                == (epoch
117                    / pow_config.difficulty_adjustment_epoch_period(epoch))
118                    * pow_config.difficulty_adjustment_epoch_period(epoch)
119        );
120
121        let mut cur = cur_hash.clone();
122        let cur_difficulty = cur_header.difficulty().clone();
123        let mut block_count = 0 as u64;
124        let max_time = cur_header.timestamp();
125        let mut min_time = 0;
126
127        // Collect the total block count and the timespan in the current period
128        for _ in 0..pow_config.difficulty_adjustment_epoch_period(epoch) {
129            block_count += consensus.num_blocks_in_epoch(&cur);
130            cur = cur_header.parent_hash().clone();
131            cur_header = consensus.block_header_by_hash(&cur).unwrap();
132            if cur_header.timestamp() != 0 {
133                min_time = cur_header.timestamp();
134            }
135            assert!(max_time >= min_time);
136        }
137
138        let expected_diff = pow_config.target_difficulty(
139            block_count,
140            max_time - min_time,
141            &cur_difficulty,
142        );
143        // d_{t+1}=0.8*d_t+0.2*d'
144        // where d_t is the difficulty of the current period, and d' is the
145        // expected difficulty to reach the ideal block_generation_period.
146        let mut target_diff = if epoch < pow_config.cip86_height {
147            expected_diff
148        } else {
149            cur_difficulty / 5 * 4 + expected_diff / 5
150        };
151
152        let (lower, upper) = pow_config.get_adjustment_bound(cur_difficulty);
153        if target_diff > upper {
154            target_diff = upper;
155        }
156        if target_diff < lower {
157            target_diff = lower;
158        }
159
160        // Caching the computed target difficulty of this period.
161        self.set(*cur_hash, target_diff);
162
163        target_diff
164    }
165}