1mod 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 * 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
212pub 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
236pub 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
249pub 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
260pub 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
271pub 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}