-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathlib.rs
More file actions
378 lines (314 loc) · 11.3 KB
/
Copy pathlib.rs
File metadata and controls
378 lines (314 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
pub use pallet::*;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
use weights::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use core::ops::Shr;
use frame_support::{
pallet_prelude::*,
sp_runtime::{traits::One, SaturatedConversion},
traits::{BuildGenesisConfig, Time},
};
use frame_system::pallet_prelude::BlockNumberFor;
use qpow_math::{get_nonce_hash, is_valid_nonce};
use sp_core::U512;
pub type NonceType = [u8; 64];
pub type Difficulty = U512;
pub type WorkValue = U512;
pub type Timestamp = u64;
pub type BlockDuration = u64;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub type LastBlockTime<T: Config> = StorageValue<_, Timestamp, ValueQuery>;
#[pallet::storage]
pub type LastBlockDuration<T: Config> = StorageValue<_, BlockDuration, ValueQuery>;
#[pallet::storage]
pub type CurrentDifficulty<T: Config> = StorageValue<_, Difficulty, ValueQuery>;
#[pallet::config]
pub trait Config: frame_system::Config + pallet_timestamp::Config {
#[pallet::constant]
type InitialDifficulty: Get<U512>;
#[pallet::constant]
type TargetBlockTime: Get<BlockDuration>;
#[pallet::constant]
type MaxReorgDepth: Get<u32>;
type WeightInfo: WeightInfo;
}
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub initial_difficulty: Difficulty,
#[serde(skip)]
pub _phantom: PhantomData<T>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { initial_difficulty: T::InitialDifficulty::get(), _phantom: PhantomData }
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
// Use the genesis config value, not the runtime constant.
// This allows chain-spec overrides of initial difficulty.
<CurrentDifficulty<T>>::put(self.initial_difficulty);
log::info!(target: "qpow", "Genesis: Set initial difficulty to {:x}",
self.initial_difficulty.low_u64());
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
ProofSubmitted {
nonce: NonceType,
difficulty: U512,
hash_achieved: U512,
},
DifficultyAdjusted {
old_difficulty: Difficulty,
new_difficulty: Difficulty,
observed_block_time: BlockDuration,
},
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_block_number: BlockNumberFor<T>) -> Weight {
<T as crate::Config>::WeightInfo::on_finalize()
}
/// Called at the end of each block to adjust mining difficulty.
fn on_finalize(block_number: BlockNumberFor<T>) {
let current_difficulty = Self::get_difficulty();
log::debug!(target: "qpow",
"📢 QPoW: before submit at block {:?}, current_difficulty={:?}",
block_number,
current_difficulty.low_u64()
);
Self::adjust_difficulty();
}
}
impl<T: Config> Pallet<T> {
fn percentage_change(big_a: U512, big_b: U512) -> (U512, bool) {
let a = big_a.shr(10);
let b = big_b.shr(10);
let abs_diff = a.abs_diff(b);
let change = abs_diff
.saturating_mul(U512::from(100u64))
.checked_div(a)
.unwrap_or(U512::zero());
(change, b >= a)
}
fn adjust_difficulty() {
let now = pallet_timestamp::Pallet::<T>::now().saturated_into::<u64>();
let last_time = <LastBlockTime<T>>::get();
// Use get_difficulty() to handle zero/missing storage consistently with verification.
// This ensures we use InitialDifficulty as the base when storage is unset,
// rather than computing from zero which would clamp to min_difficulty.
let current_difficulty = Self::get_difficulty();
let current_block_number = <frame_system::Pallet<T>>::block_number();
// Calculate block time (use target for genesis block)
let block_time = if current_block_number > One::one() {
let duration = now.saturating_sub(last_time);
log::debug!(target: "qpow",
"Time calculation: now={}, last_time={}, diff={}ms",
now,
last_time,
duration
);
<LastBlockDuration<T>>::put(duration);
duration
} else {
T::TargetBlockTime::get()
};
<LastBlockTime<T>>::put(now);
let target_time = T::TargetBlockTime::get();
let new_difficulty =
Self::calculate_difficulty(current_difficulty, block_time, target_time);
<CurrentDifficulty<T>>::put(new_difficulty);
log::debug!(target: "qpow", "Stored new difficulty: {}",
new_difficulty.low_u128());
Self::deposit_event(Event::DifficultyAdjusted {
old_difficulty: current_difficulty,
new_difficulty,
observed_block_time: block_time,
});
let (pct_change, is_positive) =
Self::percentage_change(current_difficulty, new_difficulty);
log::debug!(target: "qpow",
"🟢 Adjusted mining difficulty {}{}%: {:x} -> {:x} (block time: {}ms, target: {}ms) ",
if is_positive {"+"} else {"-"},
pct_change,
current_difficulty.low_u64(),
new_difficulty.low_u64(),
block_time,
target_time
);
}
/// Calculate new difficulty based on block time.
/// Uses the same formula as Ethereum PoW:
/// diff = parent_diff + (parent_diff / 2048) * max(1 - block_time / divisor, -99)
///
/// The divisor is 8 seconds for a 12s target (scales proportionally).
/// This creates these zones:
/// - < divisor: difficulty increases by 1/2048 (~0.05%)
/// - divisor to 2*divisor: no change
/// - 2*divisor to 3*divisor: difficulty decreases by 1/2048
/// - etc, up to max decrease of 99/2048 (~4.8%)
pub fn calculate_difficulty(
parent_difficulty: U512,
block_time_ms: u64,
target_time_ms: u64,
) -> U512 {
log::debug!(target: "qpow", "📊 Calculating new difficulty ---------------------------------------------");
// Divisor scales with target: 8s divisor for 12s target
// divisor = target * 8 / 12 = target * 2 / 3
let divisor_ms = (target_time_ms * 2 / 3).max(1);
let time_factor = (block_time_ms / divisor_ms) as i64;
let adjustment = core::cmp::max(1i64 - time_factor, -99i64);
log::debug!(target: "qpow", "Block time: {}ms, divisor: {}ms, time_factor: {}, adjustment: {}",
block_time_ms, divisor_ms, time_factor, adjustment);
// Difficulty increment = parent_diff / 2048
let increment = parent_difficulty / U512::from(2048u64);
// Calculate new difficulty
let new_difficulty = if adjustment >= 0 {
parent_difficulty
.saturating_add(increment.saturating_mul(U512::from(adjustment as u64)))
} else {
let decrease = increment.saturating_mul(U512::from((-adjustment) as u64));
parent_difficulty.saturating_sub(decrease)
};
// Apply min/max bounds
let min_difficulty = Self::get_min_difficulty();
let max_difficulty = Self::get_max_difficulty();
let bounded = if new_difficulty < min_difficulty {
log::warn!("Min difficulty achieved, clipping to: {:x}", min_difficulty.low_u64());
min_difficulty
} else if new_difficulty > max_difficulty {
log::warn!("Max difficulty achieved, clipping to: {:x}", max_difficulty.low_u64());
max_difficulty
} else {
new_difficulty
};
log::debug!(target: "qpow",
"🟢 Current Difficulty: {:x}",
parent_difficulty.low_u64()
);
log::debug!(target: "qpow", "🟢 Next Difficulty: {:x}", bounded.low_u64());
log::debug!(target: "qpow", "🕒 Block Time: {}ms", block_time_ms);
bounded
}
}
impl<T: Config> Pallet<T> {
pub fn is_valid_nonce(
block_hash: [u8; 32],
nonce: NonceType,
difficulty: Difficulty,
) -> (bool, U512) {
is_valid_nonce(block_hash, nonce, difficulty)
}
pub fn get_nonce_hash(
block_hash: [u8; 32], // 256-bit block hash
nonce: NonceType, // 512-bit nonce
) -> U512 {
get_nonce_hash(block_hash, nonce)
}
// Shared verification logic
fn verify_nonce_internal(block_hash: [u8; 32], nonce: NonceType) -> (bool, U512, U512) {
if nonce == [0u8; 64] {
log::warn!(
"verify_nonce should not be called with 0 nonce, but was for block_hash: {:?}",
block_hash
);
return (false, U512::zero(), U512::zero());
}
let difficulty = Self::get_difficulty();
let (valid, hash_achieved) = Self::is_valid_nonce(block_hash, nonce, difficulty);
log::debug!(
"verify_nonce_internal: block_hash: {:?}, nonce: {:?}, valid: {:?}, difficulty: {:?}, hash_achieved: {:?}",
hex::encode(block_hash),
nonce,
valid,
difficulty,
hash_achieved
);
(valid, difficulty, hash_achieved)
}
// Block verification with event emission
pub fn verify_nonce_on_import_block(block_hash: [u8; 32], nonce: NonceType) -> bool {
let (valid, difficulty, hash_achieved) = Self::verify_nonce_internal(block_hash, nonce);
if valid {
Self::deposit_event(Event::ProofSubmitted { nonce, difficulty, hash_achieved });
}
valid
}
pub fn verify_nonce_local_mining(block_hash: [u8; 32], nonce: NonceType) -> bool {
let (verify, _, _) = Self::verify_nonce_internal(block_hash, nonce);
verify
}
/// Verify the nonce and return the block's work used for chain selection.
///
/// IMPORTANT: despite the legacy name, this returns the *target* difficulty the
/// block had to satisfy (the network difficulty at this height), NOT the achieved
/// difficulty derived from the winning hash. Target-based work matches Bitcoin
/// (`2^256/(target+1)`) and Ethereum PoW (sum of the `difficulty` field): every
/// block at a given difficulty contributes an identical, deterministic amount of
/// work, so cumulative chain work tracks expended hash power instead of being
/// dominated by a single lucky hash.
///
/// The runtime API name is intentionally left unchanged so this can ship as an
/// on-chain-only upgrade: because the metric is determined by the value this
/// returns (the client merely accumulates `parent_work + value`), upgrading the
/// on-chain Wasm flips the whole network to target-based work at the `set_code`
/// block, with no coordinated node-binary upgrade and no resync. Renaming the API
/// would break that compatibility, so defer the rename to a later release once all
/// nodes run a binary that expects the new name.
///
/// Note: This is called via runtime API from the client side. Runtime API
/// calls execute in a temporary context where state changes are discarded,
/// so we don't emit events here.
pub fn verify_and_get_achieved_difficulty(
block_hash: [u8; 32],
nonce: NonceType,
) -> (bool, U512) {
let (valid, difficulty, _) = Self::verify_nonce_internal(block_hash, nonce);
let block_work = if valid { difficulty } else { U512::zero() };
(valid, block_work)
}
pub fn initial_difficulty() -> Difficulty {
T::InitialDifficulty::get()
}
pub fn get_difficulty() -> Difficulty {
let stored = <CurrentDifficulty<T>>::get();
let initial = Self::initial_difficulty();
if stored == U512::zero() {
log::warn!(target: "qpow", "Stored difficulty is zero, using initial: {:x}", initial.low_u64());
return initial;
}
stored
}
pub fn get_min_difficulty() -> Difficulty {
// Minimum difficulty floor - same as Ethereum's minimum (2^17 = 131072)
U512::from(131_072u64)
}
pub fn get_max_difficulty() -> Difficulty {
U512::MAX
}
pub fn get_last_block_time() -> Timestamp {
<LastBlockTime<T>>::get()
}
pub fn get_last_block_duration() -> BlockDuration {
<LastBlockDuration<T>>::get()
}
pub fn get_max_reorg_depth() -> u32 {
T::MaxReorgDepth::get()
}
}
}