cfxcore_pow/
lib.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5mod cache;
6mod compute;
7mod keccak;
8mod seed_compute;
9mod shared;
10mod target_difficulty_manager;
11mod traits;
12
13pub use traits::ConsensusProvider;
14
15pub use self::{
16    cache::CacheBuilder, shared::POW_STAGE_LENGTH,
17    target_difficulty_manager::TargetDifficultyManager,
18};
19use keccak_hash::keccak as keccak_hash;
20
21use cfx_parameters::pow::*;
22use cfx_types::{BigEndianHash, H256, U256, U512};
23
24use malloc_size_of_derive::MallocSizeOf as DeriveMallocSizeOf;
25use primitives::BlockHeader;
26use static_assertions::_core::str::FromStr;
27use std::convert::TryFrom;
28
29#[cfg(target_endian = "big")]
30compile_error!("The PoW implementation requires little-endian platform");
31
32#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
33pub struct ProofOfWorkProblem {
34    pub block_height: u64,
35    pub block_hash: H256,
36    pub difficulty: U256,
37    pub boundary: U256,
38}
39
40impl ProofOfWorkProblem {
41    pub const NO_BOUNDARY: U256 = U256::MAX;
42
43    pub fn new(block_height: u64, block_hash: H256, difficulty: U256) -> Self {
44        let boundary = difficulty_to_boundary(&difficulty);
45        Self {
46            block_height,
47            block_hash,
48            difficulty,
49            boundary,
50        }
51    }
52
53    pub fn from_block_header(block_header: &BlockHeader) -> Self {
54        Self::new(
55            block_header.height(),
56            block_header.problem_hash(),
57            *block_header.difficulty(),
58        )
59    }
60
61    #[inline]
62    pub fn validate_hash_against_boundary(
63        hash: &H256, nonce: &U256, boundary: &U256,
64    ) -> bool {
65        let lower_bound = nonce_to_lower_bound(nonce);
66        let (against_lower_bound_u256, _) =
67            BigEndianHash::into_uint(hash).overflowing_sub(lower_bound);
68        against_lower_bound_u256.lt(boundary)
69            || boundary.eq(&ProofOfWorkProblem::NO_BOUNDARY)
70    }
71}
72
73#[derive(Debug, Copy, Clone)]
74pub struct ProofOfWorkSolution {
75    pub nonce: U256,
76}
77
78#[derive(Debug, Clone, DeriveMallocSizeOf)]
79pub enum MiningType {
80    Stratum,
81    CPU,
82    Disable,
83}
84
85impl FromStr for MiningType {
86    type Err = String;
87
88    fn from_str(s: &str) -> Result<Self, Self::Err> {
89        let mining_type = match s {
90            "stratum" => Self::Stratum,
91            "cpu" => Self::CPU,
92            "disable" => Self::Disable,
93            _ => return Err("invalid mining type".into()),
94        };
95        Ok(mining_type)
96    }
97}
98
99#[derive(Debug, Clone, DeriveMallocSizeOf)]
100pub struct ProofOfWorkConfig {
101    pub test_mode: bool,
102    pub use_octopus_in_test_mode: bool,
103    pub mining_type: MiningType,
104    pub initial_difficulty: u64,
105    pub block_generation_period: u64,
106    pub stratum_listen_addr: String,
107    pub stratum_port: u16,
108    pub stratum_secret: Option<H256>,
109    pub pow_problem_window_size: usize,
110    pub cip86_height: u64,
111}
112
113impl ProofOfWorkConfig {
114    pub fn new(
115        test_mode: bool, use_octopus_in_test_mode: bool, mining_type: &str,
116        initial_difficulty: Option<u64>, stratum_listen_addr: String,
117        stratum_port: u16, stratum_secret: Option<H256>,
118        pow_problem_window_size: usize, cip86_height: u64,
119    ) -> Self {
120        if test_mode {
121            ProofOfWorkConfig {
122                test_mode,
123                use_octopus_in_test_mode,
124                mining_type: mining_type.parse().expect("Invalid mining type"),
125                initial_difficulty: initial_difficulty.unwrap_or(4),
126                block_generation_period: 1000000,
127                stratum_listen_addr,
128                stratum_port,
129                stratum_secret,
130                pow_problem_window_size,
131                cip86_height,
132            }
133        } else {
134            ProofOfWorkConfig {
135                test_mode,
136                use_octopus_in_test_mode,
137                mining_type: mining_type.parse().expect("Invalid mining type"),
138                initial_difficulty: INITIAL_DIFFICULTY,
139                block_generation_period: TARGET_AVERAGE_BLOCK_GENERATION_PERIOD,
140                stratum_listen_addr,
141                stratum_port,
142                stratum_secret,
143                pow_problem_window_size,
144                cip86_height,
145            }
146        }
147    }
148
149    pub fn difficulty_adjustment_epoch_period(&self, cur_height: u64) -> u64 {
150        if self.test_mode {
151            20
152        } else {
153            if cur_height > self.cip86_height {
154                DIFFICULTY_ADJUSTMENT_EPOCH_PERIOD_CIP
155            } else {
156                DIFFICULTY_ADJUSTMENT_EPOCH_PERIOD
157            }
158        }
159    }
160
161    pub fn use_octopus(&self) -> bool {
162        !self.test_mode || self.use_octopus_in_test_mode
163    }
164
165    pub fn use_stratum(&self) -> bool {
166        matches!(self.mining_type, MiningType::Stratum)
167    }
168
169    pub fn enable_mining(&self) -> bool {
170        !matches!(self.mining_type, MiningType::Disable)
171    }
172
173    pub fn target_difficulty(
174        &self, block_count: u64, timespan: u64, cur_difficulty: &U256,
175    ) -> U256 {
176        if timespan == 0 || block_count <= 1 || self.test_mode {
177            return self.initial_difficulty.into();
178        }
179
180        let target = (U512::from(*cur_difficulty)
181            * U512::from(self.block_generation_period)
182            // - 1 for unbiased estimation, like stdvar
183            * U512::from(block_count - 1))
184            / (U512::from(timespan) * U512::from(1000000));
185        if target.is_zero() {
186            return 1.into();
187        }
188        if target > U256::max_value().into() {
189            return U256::max_value();
190        }
191        U256::try_from(target).unwrap()
192    }
193
194    pub fn get_adjustment_bound(&self, diff: U256) -> (U256, U256) {
195        let adjustment = diff / DIFFICULTY_ADJUSTMENT_FACTOR;
196        let mut min_diff = diff - adjustment;
197        let mut max_diff = diff + adjustment;
198        let initial_diff: U256 = self.initial_difficulty.into();
199
200        if min_diff < initial_diff {
201            min_diff = initial_diff;
202        }
203
204        if max_diff < min_diff {
205            max_diff = min_diff;
206        }
207
208        (min_diff, max_diff)
209    }
210}
211
212// We will use the top 128 bits (excluding the highest bit) to be the lower
213// bound of our PoW. The rationale is to provide a solution for block
214// withholding attack among mining pools.
215pub fn nonce_to_lower_bound(nonce: &U256) -> U256 {
216    let mut buf = nonce.to_big_endian();
217    for i in 16..32 {
218        buf[i] = 0;
219    }
220    buf[0] = buf[0] & 0x7f;
221    let lower_bound = U256::from_big_endian(&buf);
222    lower_bound
223}
224
225pub fn pow_hash_to_quality(hash: &H256, nonce: &U256) -> U256 {
226    let hash_as_uint = BigEndianHash::into_uint(hash);
227    let lower_bound = nonce_to_lower_bound(nonce);
228    let (against_bound_u256, _) = hash_as_uint.overflowing_sub(lower_bound);
229    if against_bound_u256.eq(&U256::MAX) {
230        U256::one()
231    } else {
232        boundary_to_difficulty(&(against_bound_u256 + U256::one()))
233    }
234}
235
236/// This should only be used in tests.
237pub fn pow_quality_to_hash(pow_quality: &U256, nonce: &U256) -> H256 {
238    let lower_bound = nonce_to_lower_bound(nonce);
239    let hash_u256 = if pow_quality.eq(&U256::MAX) {
240        U256::one()
241    } else {
242        let boundary = difficulty_to_boundary(&(pow_quality + U256::one()));
243        let (against_bound_u256, _) = boundary.overflowing_add(lower_bound);
244        against_bound_u256
245    };
246    BigEndianHash::from_uint(&hash_u256)
247}
248
249/// Convert boundary to its original difficulty. Basically just `f(x) = 2^256 /
250/// x`.
251pub fn boundary_to_difficulty(boundary: &U256) -> U256 {
252    assert!(!boundary.is_zero());
253    if boundary.eq(&U256::one()) {
254        U256::MAX
255    } else {
256        compute_inv_x_times_2_pow_256_floor(boundary)
257    }
258}
259
260/// Convert difficulty to the target boundary. Basically just `f(x) = 2^256 /
261/// x`.
262pub fn difficulty_to_boundary(difficulty: &U256) -> U256 {
263    assert!(!difficulty.is_zero());
264    if difficulty.eq(&U256::one()) {
265        ProofOfWorkProblem::NO_BOUNDARY
266    } else {
267        compute_inv_x_times_2_pow_256_floor(difficulty)
268    }
269}
270
271/// Compute [2^256 / x], where x >= 2 and x < 2^256.
272pub fn compute_inv_x_times_2_pow_256_floor(x: &U256) -> U256 {
273    let (div, modular) = U256::MAX.clone().div_mod(x.clone());
274    if &(modular + U256::one()) == x {
275        div + U256::one()
276    } else {
277        div
278    }
279}
280
281pub struct PowComputer {
282    use_octopus: bool,
283    cache_builder: CacheBuilder,
284}
285
286impl PowComputer {
287    pub fn new(use_octopus: bool) -> Self {
288        PowComputer {
289            use_octopus,
290            cache_builder: CacheBuilder::new(),
291        }
292    }
293
294    pub fn compute(
295        &self, nonce: &U256, block_hash: &H256, block_height: u64,
296    ) -> H256 {
297        if !self.use_octopus {
298            let mut buf = [0u8; 64];
299            for i in 0..32 {
300                buf[i] = block_hash[i];
301            }
302            buf[32..64].copy_from_slice(&nonce.to_little_endian());
303            let intermediate = keccak_hash(&buf[..]);
304            let mut tmp = [0u8; 32];
305            for i in 0..32 {
306                tmp[i] = intermediate[i] ^ block_hash[i]
307            }
308            keccak_hash(tmp)
309        } else {
310            let light = self.cache_builder.light(block_height);
311            light
312                .compute(block_hash.as_fixed_bytes(), nonce.low_u64())
313                .into()
314        }
315    }
316
317    pub fn validate(
318        &self, problem: &ProofOfWorkProblem, solution: &ProofOfWorkSolution,
319    ) -> bool {
320        let nonce = solution.nonce;
321        let hash =
322            self.compute(&nonce, &problem.block_hash, problem.block_height);
323        ProofOfWorkProblem::validate_hash_against_boundary(
324            &hash,
325            &nonce,
326            &problem.boundary,
327        )
328    }
329}
330
331#[test]
332fn test_octopus() {
333    let pow = PowComputer::new(true);
334
335    let block_hash =
336        "4d99d0b41c7eb0dd1a801c35aae2df28ae6b53bc7743f0818a34b6ec97f5b4ae"
337            .parse()
338            .unwrap();
339    let start_nonce = 0x2333333333u64 & (!0x1f);
340    pow.compute(&U256::from(start_nonce), &block_hash, 2);
341}