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 = [0u8; 32];
217    nonce.to_big_endian(&mut buf[..]);
218    for i in 16..32 {
219        buf[i] = 0;
220    }
221    buf[0] = buf[0] & 0x7f;
222    // Note that U256::from assumes big_endian of the bytes
223    let lower_bound = U256::from(buf);
224    lower_bound
225}
226
227pub fn pow_hash_to_quality(hash: &H256, nonce: &U256) -> U256 {
228    let hash_as_uint = BigEndianHash::into_uint(hash);
229    let lower_bound = nonce_to_lower_bound(nonce);
230    let (against_bound_u256, _) = hash_as_uint.overflowing_sub(lower_bound);
231    if against_bound_u256.eq(&U256::MAX) {
232        U256::one()
233    } else {
234        boundary_to_difficulty(&(against_bound_u256 + U256::one()))
235    }
236}
237
238/// This should only be used in tests.
239pub fn pow_quality_to_hash(pow_quality: &U256, nonce: &U256) -> H256 {
240    let lower_bound = nonce_to_lower_bound(nonce);
241    let hash_u256 = if pow_quality.eq(&U256::MAX) {
242        U256::one()
243    } else {
244        let boundary = difficulty_to_boundary(&(pow_quality + U256::one()));
245        let (against_bound_u256, _) = boundary.overflowing_add(lower_bound);
246        against_bound_u256
247    };
248    BigEndianHash::from_uint(&hash_u256)
249}
250
251/// Convert boundary to its original difficulty. Basically just `f(x) = 2^256 /
252/// x`.
253pub fn boundary_to_difficulty(boundary: &U256) -> U256 {
254    assert!(!boundary.is_zero());
255    if boundary.eq(&U256::one()) {
256        U256::MAX
257    } else {
258        compute_inv_x_times_2_pow_256_floor(boundary)
259    }
260}
261
262/// Convert difficulty to the target boundary. Basically just `f(x) = 2^256 /
263/// x`.
264pub fn difficulty_to_boundary(difficulty: &U256) -> U256 {
265    assert!(!difficulty.is_zero());
266    if difficulty.eq(&U256::one()) {
267        ProofOfWorkProblem::NO_BOUNDARY
268    } else {
269        compute_inv_x_times_2_pow_256_floor(difficulty)
270    }
271}
272
273/// Compute [2^256 / x], where x >= 2 and x < 2^256.
274pub fn compute_inv_x_times_2_pow_256_floor(x: &U256) -> U256 {
275    let (div, modular) = U256::MAX.clone().div_mod(x.clone());
276    if &(modular + U256::one()) == x {
277        div + U256::one()
278    } else {
279        div
280    }
281}
282
283pub struct PowComputer {
284    use_octopus: bool,
285    cache_builder: CacheBuilder,
286}
287
288impl PowComputer {
289    pub fn new(use_octopus: bool) -> Self {
290        PowComputer {
291            use_octopus,
292            cache_builder: CacheBuilder::new(),
293        }
294    }
295
296    pub fn compute(
297        &self, nonce: &U256, block_hash: &H256, block_height: u64,
298    ) -> H256 {
299        if !self.use_octopus {
300            let mut buf = [0u8; 64];
301            for i in 0..32 {
302                buf[i] = block_hash[i];
303            }
304            nonce.to_little_endian(&mut buf[32..64]);
305            let intermediate = keccak_hash(&buf[..]);
306            let mut tmp = [0u8; 32];
307            for i in 0..32 {
308                tmp[i] = intermediate[i] ^ block_hash[i]
309            }
310            keccak_hash(tmp)
311        } else {
312            let light = self.cache_builder.light(block_height);
313            light
314                .compute(block_hash.as_fixed_bytes(), nonce.low_u64())
315                .into()
316        }
317    }
318
319    pub fn validate(
320        &self, problem: &ProofOfWorkProblem, solution: &ProofOfWorkSolution,
321    ) -> bool {
322        let nonce = solution.nonce;
323        let hash =
324            self.compute(&nonce, &problem.block_hash, problem.block_height);
325        ProofOfWorkProblem::validate_hash_against_boundary(
326            &hash,
327            &nonce,
328            &problem.boundary,
329        )
330    }
331}
332
333#[test]
334fn test_octopus() {
335    let pow = PowComputer::new(true);
336
337    let block_hash =
338        "4d99d0b41c7eb0dd1a801c35aae2df28ae6b53bc7743f0818a34b6ec97f5b4ae"
339            .parse()
340            .unwrap();
341    let start_nonce = 0x2333333333u64 & (!0x1f);
342    pow.compute(&U256::from(start_nonce), &block_hash, 2);
343}