diff --git a/miner-stack/grafana/dashboards/quantus-node/overview.json b/miner-stack/grafana/dashboards/quantus-node/overview.json index 17b0a241..4fb11b21 100644 --- a/miner-stack/grafana/dashboards/quantus-node/overview.json +++ b/miner-stack/grafana/dashboards/quantus-node/overview.json @@ -118,11 +118,11 @@ "type": "prometheus", "uid": "prometheus" }, - "expr": "qpow_metrics{data_group=\"block_time_ema\", job=\"quantus-node\"}", + "expr": "qpow_metrics{data_group=\"last_block_duration\", job=\"quantus-node\"}", "refId": "A" } ], - "title": "Block Time (EMA)", + "title": "Last Block Duration", "type": "stat" }, { diff --git a/miner-stack/grafana/dashboards/quantus-node/quantus-business.json b/miner-stack/grafana/dashboards/quantus-node/quantus-business.json index 9c0ac493..d2895751 100644 --- a/miner-stack/grafana/dashboards/quantus-node/quantus-business.json +++ b/miner-stack/grafana/dashboards/quantus-node/quantus-business.json @@ -271,12 +271,12 @@ }, "targets": [ { - "expr": "qpow_metrics{data_group=\"block_time_ema\"}", + "expr": "qpow_metrics{data_group=\"last_block_duration\"}", "legendFormat": "{{instance}}", "refId": "A" } ], - "title": "Block Time EMA", + "title": "Last Block Duration", "type": "timeseries" }, { diff --git a/node/src/prometheus.rs b/node/src/prometheus.rs index d8e6273d..7613e33e 100644 --- a/node/src/prometheus.rs +++ b/node/src/prometheus.rs @@ -44,13 +44,6 @@ impl BusinessMetrics { C: ProvideRuntimeApi, C::Api: sp_consensus_qpow::QPoWApi, { - // Get values via the runtime API - we'll handle potential errors gracefully - let block_time_ema = - client.runtime_api().get_block_time_ema(block_hash).unwrap_or_else(|e| { - log::warn!("Failed to get median_block_time: {:?}", e); - 0 - }); - let difficulty = client.runtime_api().get_difficulty(block_hash).unwrap_or_else(|e| { log::warn!("Failed to get difficulty: {:?}", e); U512::zero() @@ -73,9 +66,7 @@ impl BusinessMetrics { 0 }); - // Update the metrics with the values we retrieved gauge.with_label_values(&["chain_height"]).set(chain_height as f64); - gauge.with_label_values(&["block_time_ema"]).set(block_time_ema as f64); gauge.with_label_values(&["difficulty"]).set(Self::pack_u512_to_f64(difficulty)); gauge.with_label_values(&["last_block_time"]).set(last_block_time as f64); gauge diff --git a/pallets/qpow/src/benchmarking.rs b/pallets/qpow/src/benchmarking.rs index 4216fdaa..2e0cdf8c 100644 --- a/pallets/qpow/src/benchmarking.rs +++ b/pallets/qpow/src/benchmarking.rs @@ -15,7 +15,7 @@ use sp_runtime::traits::Get; mod benchmarks { use super::*; - /// Benchmark for the on_finalize hook which performs EMA-based difficulty adjustment. + /// Benchmark for the on_finalize hook which performs difficulty adjustment. #[benchmark] fn on_finalize() { // Setup state with typical block for difficulty adjustment @@ -32,9 +32,6 @@ mod benchmarks { pallet_timestamp::Pallet::::set_timestamp(now); >::put(now.saturating_sub(T::TargetBlockTime::get())); - // Initialize EMA - >::put(T::TargetBlockTime::get()); - #[block] { QPoW::::on_finalize(block_number); diff --git a/pallets/qpow/src/lib.rs b/pallets/qpow/src/lib.rs index fd62ef9a..9ac058d4 100644 --- a/pallets/qpow/src/lib.rs +++ b/pallets/qpow/src/lib.rs @@ -22,12 +22,11 @@ pub mod pallet { use core::ops::Shr; use frame_support::{ pallet_prelude::*, - sp_runtime::{traits::One, SaturatedConversion, Saturating}, + sp_runtime::{traits::One, SaturatedConversion}, traits::{BuildGenesisConfig, Time}, }; use frame_system::pallet_prelude::BlockNumberFor; use qpow_math::{achieved_difficulty_from_hash, get_nonce_hash, is_valid_nonce}; - use sp_arithmetic::FixedU128; use sp_core::U512; pub type NonceType = [u8; 64]; @@ -48,26 +47,14 @@ pub mod pallet { #[pallet::storage] pub type CurrentDifficulty = StorageValue<_, Difficulty, ValueQuery>; - // Exponential Moving Average of block times (in milliseconds) - #[pallet::storage] - pub type BlockTimeEma = StorageValue<_, BlockDuration, ValueQuery>; - #[pallet::config] pub trait Config: frame_system::Config + pallet_timestamp::Config { - /// Pallet's weight info #[pallet::constant] type InitialDifficulty: Get; - #[pallet::constant] - type DifficultyAdjustPercentClamp: Get; - #[pallet::constant] type TargetBlockTime: Get; - /// EMA smoothing factor (0-1000, where 1000 = 1.0) - #[pallet::constant] - type EmaAlpha: Get; - #[pallet::constant] type MaxReorgDepth: Get; @@ -92,14 +79,10 @@ pub mod pallet { fn build(&self) { let initial_difficulty = T::InitialDifficulty::get(); - // Set current difficulty for the genesis block >::put(initial_difficulty); log::info!(target: "qpow", "Genesis: Set initial difficulty to {:x}", initial_difficulty.low_u64()); - - // Initialize EMA with target block time - >::put(T::TargetBlockTime::get()); } } @@ -124,8 +107,7 @@ pub mod pallet { ::WeightInfo::on_finalize() } - /// Called at the end of each block to adjust mining difficulty based on - /// observed block times using Exponential Moving Average (EMA). + /// Called at the end of each block to adjust mining difficulty. fn on_finalize(block_number: BlockNumberFor) { let current_difficulty = >::get(); log::debug!(target: "qpow", @@ -139,36 +121,6 @@ pub mod pallet { } impl Pallet { - fn update_block_time_ema(block_time: u64) { - let current_ema = >::get(); - let alpha = T::EmaAlpha::get(); - - // Initialize EMA with target block time if this is the first block - if current_ema == 0 { - >::put(T::TargetBlockTime::get()); - return; - } - - // Calculate EMA: new_ema = alpha * block_time + (1 - alpha) * current_ema - // Alpha is scaled by 1000, so we divide by 1000 - let alpha_scaled = alpha as u64; - let one_minus_alpha = 1000u64.saturating_sub(alpha_scaled); - - let weighted_current = block_time.saturating_mul(alpha_scaled); - let weighted_ema = current_ema.saturating_mul(one_minus_alpha); - let new_ema = (weighted_current.saturating_add(weighted_ema)) / 1000; - - >::put(new_ema); - - log::debug!(target: "qpow", - "📊 Updated EMA: {}ms -> {}ms (new block: {}ms, alpha: {})", - current_ema, - new_ema, - block_time, - alpha_scaled - ); - } - fn percentage_change(big_a: U512, big_b: U512) -> (U512, bool) { let a = big_a.shr(10); let b = big_b.shr(10); @@ -183,120 +135,120 @@ pub mod pallet { } fn adjust_difficulty() { - // Get current time let now = pallet_timestamp::Pallet::::now().saturated_into::(); let last_time = >::get(); let current_difficulty = >::get(); let current_block_number = >::block_number(); - // Only calculate block time if we're past the genesis block - if current_block_number > One::one() { - let block_time = now.saturating_sub(last_time); + // 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, - block_time + duration ); - // Store the actual block duration - >::put(block_time); + >::put(duration); - Self::update_block_time_ema(block_time); - } + duration + } else { + T::TargetBlockTime::get() + }; - // Add last block time for the next calculations >::put(now); - let observed_block_time = >::get(); let target_time = T::TargetBlockTime::get(); - let new_difficulty = - Self::calculate_difficulty(current_difficulty, observed_block_time, target_time); + Self::calculate_difficulty(current_difficulty, block_time, target_time); - // Save new difficulty >::put(new_difficulty); log::debug!(target: "qpow", "Stored new difficulty: {}", new_difficulty.low_u128()); - // Propagate new Event Self::deposit_event(Event::DifficultyAdjusted { old_difficulty: current_difficulty, new_difficulty, - observed_block_time, + 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} (observed block time: {}ms, target: {}ms) ", + "🟢 Adjusted mining difficulty {}{}%: {:x} -> {:x} (block time: {}ms, target: {}ms) ", if is_positive {"+"} else {"-"}, pct_change, current_difficulty.low_u64(), new_difficulty.low_u64(), - observed_block_time, + 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( - current_difficulty: U512, - observed_block_time: u64, - target_block_time: u64, + parent_difficulty: U512, + block_time_ms: u64, + target_time_ms: u64, ) -> U512 { log::debug!(target: "qpow", "📊 Calculating new difficulty ---------------------------------------------"); - let observed_block_time = observed_block_time.max(1); - let clamp = T::DifficultyAdjustPercentClamp::get(); // 10% - let one = FixedU128::one(); - let ratio = - FixedU128::from_rational(target_block_time as u128, observed_block_time as u128) - .min(one.saturating_add(clamp)) - .max(one.saturating_sub(clamp)); - log::debug!(target: "qpow", "💧 Clamped block_time ratio as FixedU128: {} ", ratio); - - let ratio_512 = U512::from(ratio.into_inner()); - let max_difficulty = Self::get_max_difficulty(); - // For Bitcoin-style difficulty adjustment: - // If observed_time > target_time (slow blocks), difficulty should decrease - // If observed_time < target_time (fast blocks), difficulty should increase - // new_difficulty = current_difficulty * target_time / observed_time - let mut adjusted = match current_difficulty.checked_mul(ratio_512) { - Some(numerator) => { - // unchecked division, we know the denominator is not zero - let result = numerator / U512::from(one.into_inner()); - log::debug!(target: "qpow", - "Difficulty calculation: current={:x}, target_time={}, observed_time={}, new={:x}", - current_difficulty.low_u32() as u16, target_block_time, observed_block_time, result.low_u32() as u16); - result - }, - None => { - log::error!("Multiplication overflow in difficulty calculation"); - return max_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(); - if adjusted < 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()); - adjusted = min_difficulty; - } else if adjusted > max_difficulty { + min_difficulty + } else if new_difficulty > max_difficulty { log::warn!("Max difficulty achieved, clipping to: {:x}", max_difficulty.low_u64()); - adjusted = max_difficulty; - } + max_difficulty + } else { + new_difficulty + }; log::debug!(target: "qpow", "🟢 Current Difficulty: {:x}", - current_difficulty.low_u64() + parent_difficulty.low_u64() ); - log::debug!(target: "qpow", "🟢 Next Difficulty: {:x}", adjusted.low_u64()); - log::debug!(target: "qpow", "🕒 Observed Block Time Sum: {}ms", observed_block_time); - log::debug!(target: "qpow", "🎯 Target Block Time Sum: {target_block_time}ms"); + log::debug!(target: "qpow", "🟢 Next Difficulty: {:x}", bounded.low_u64()); + log::debug!(target: "qpow", "🕒 Block Time: {}ms", block_time_ms); - adjusted + bounded } } @@ -387,20 +339,14 @@ pub mod pallet { } pub fn get_min_difficulty() -> Difficulty { - // This value is related to clamp value, - // ie, if clamp is 10% this value must be at least 10 - // 1000 is safe for clamp values >= 0.01% - U512::from(1000u64) + // 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_block_time_ema() -> u64 { - >::get() - } - pub fn get_last_block_time() -> Timestamp { >::get() } diff --git a/pallets/qpow/src/mock.rs b/pallets/qpow/src/mock.rs index 2022f124..bb9e7bf9 100644 --- a/pallets/qpow/src/mock.rs +++ b/pallets/qpow/src/mock.rs @@ -8,7 +8,7 @@ use primitive_types::U512; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, FixedU128, + BuildStorage, }; type Block = frame_system::mocking::MockBlock; @@ -70,14 +70,11 @@ impl pallet_timestamp::Config for Test { parameter_types! { pub const TestInitialDifficulty: U512 = U512([1000000, 0, 0, 0, 0, 0, 0, 0]); - pub const TestDifficultyAdjustPercentClamp: FixedU128 = FixedU128::from_rational(10, 100); } impl pallet_qpow::Config for Test { type WeightInfo = (); - type EmaAlpha = ConstU32<500>; type InitialDifficulty = TestInitialDifficulty; - type DifficultyAdjustPercentClamp = TestDifficultyAdjustPercentClamp; type TargetBlockTime = ConstU64<1000>; type MaxReorgDepth = ConstU32<10>; } diff --git a/pallets/qpow/src/tests.rs b/pallets/qpow/src/tests.rs index e2c2c142..2ce75e54 100644 --- a/pallets/qpow/src/tests.rs +++ b/pallets/qpow/src/tests.rs @@ -110,7 +110,7 @@ fn test_difficulty_bounds() { let max_difficulty = QPow::get_max_difficulty(); let initial_difficulty = QPow::initial_difficulty(); - assert_eq!(min_difficulty, U512::from(1000u64)); + assert_eq!(min_difficulty, U512::from(131_072u64)); assert!(max_difficulty > initial_difficulty); assert!(initial_difficulty > min_difficulty); }); @@ -177,22 +177,6 @@ fn test_difficulty_storage_and_retrieval() { }); } -#[test] -fn test_ema_block_time_tracking() { - new_test_ext().execute_with(|| { - // Initial EMA should be target block time - let target_time = ::TargetBlockTime::get(); - let initial_ema = QPow::get_block_time_ema(); - assert_eq!(initial_ema, target_time); - - // Run blocks and check EMA updates - run_to_block(2); - let updated_ema = QPow::get_block_time_ema(); - // EMA should still exist (exact value depends on timing) - assert!(updated_ema > 0); - }); -} - #[test] fn test_difficulty_calculation() { new_test_ext().execute_with(|| { @@ -346,8 +330,8 @@ fn test_difficulty_recovers_after_sleep() { } let recovered = QPow::get_difficulty(); - // EMA smoothing limits the spike, but alpha=500 is aggressive so recovery - // takes many blocks. 20 normal blocks bring difficulty to ~18% of pre-sleep. + // Ethereum-style adjustment decreases difficulty by up to 99/2048 per block during sleep, + // then increases slowly during recovery. 20 normal blocks bring difficulty back partially. assert!( recovered > pre_sleep / 10, "Difficulty should stay above 10% after sleep. Pre: {}, Post: {}", @@ -372,7 +356,7 @@ fn test_zero_observed_block_time() { #[test] fn test_min_difficulty_derived_from_clamp() { new_test_ext().execute_with(|| { - assert_eq!(QPow::get_min_difficulty(), U512::from(1000u64)); + assert_eq!(QPow::get_min_difficulty(), U512::from(131_072u64)); }); } @@ -380,7 +364,7 @@ fn test_min_difficulty_derived_from_clamp() { fn test_min_difficulty_can_increase() { new_test_ext().execute_with(|| { let min_diff = QPow::get_min_difficulty(); - // Fast blocks → ratio clamped to 1.1 → floor(1000 * 1.1) = 1100 + // Fast blocks → positive adjustment → difficulty increases by 1/2048 let result = QPow::calculate_difficulty(min_diff, 1, 1000); assert!( result > min_diff, @@ -395,7 +379,7 @@ fn test_min_difficulty_can_increase() { fn test_min_difficulty_floors_on_slow_blocks() { new_test_ext().execute_with(|| { let min_diff = QPow::get_min_difficulty(); - // Slow blocks → ratio clamped to 0.9 → floor(1000 * 0.9) = 900, clips to 1000 + // Slow blocks → negative adjustment, but clips to min difficulty let result = QPow::calculate_difficulty(min_diff, 100_000, 1000); assert_eq!(result, min_diff); }); diff --git a/pallets/qpow/src/weights.rs b/pallets/qpow/src/weights.rs index 545e68a1..04fbc845 100644 --- a/pallets/qpow/src/weights.rs +++ b/pallets/qpow/src/weights.rs @@ -63,8 +63,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) /// Storage: `QPoW::LastBlockTime` (r:1 w:1) /// Proof: `QPoW::LastBlockTime` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `QPoW::BlockTimeEma` (r:1 w:1) - /// Proof: `QPoW::BlockTimeEma` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) /// Storage: `QPoW::LastBlockDuration` (r:0 w:1) /// Proof: `QPoW::LastBlockDuration` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) fn on_finalize() -> Weight { @@ -73,8 +71,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `1549` // Minimum execution time: 106_000_000 picoseconds. Weight::from_parts(109_000_000, 1549) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } } @@ -86,8 +84,6 @@ impl WeightInfo for () { /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) /// Storage: `QPoW::LastBlockTime` (r:1 w:1) /// Proof: `QPoW::LastBlockTime` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `QPoW::BlockTimeEma` (r:1 w:1) - /// Proof: `QPoW::BlockTimeEma` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) /// Storage: `QPoW::LastBlockDuration` (r:0 w:1) /// Proof: `QPoW::LastBlockDuration` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) fn on_finalize() -> Weight { @@ -96,7 +92,7 @@ impl WeightInfo for () { // Estimated: `1549` // Minimum execution time: 106_000_000 picoseconds. Weight::from_parts(109_000_000, 1549) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } } diff --git a/pallets/reversible-transfers/src/lib.rs b/pallets/reversible-transfers/src/lib.rs index 697ff4cf..bf06eccd 100644 --- a/pallets/reversible-transfers/src/lib.rs +++ b/pallets/reversible-transfers/src/lib.rs @@ -26,7 +26,6 @@ pub use weights::WeightInfo; use alloc::vec::Vec; use frame_support::{ - defensive, pallet_prelude::*, traits::tokens::{fungibles::MutateHold as AssetsHold, Fortitude, Restriction}, }; diff --git a/primitives/consensus/qpow/src/lib.rs b/primitives/consensus/qpow/src/lib.rs index 544ddc66..6e38f6b8 100644 --- a/primitives/consensus/qpow/src/lib.rs +++ b/primitives/consensus/qpow/src/lib.rs @@ -19,17 +19,18 @@ sp_api::decl_runtime_apis! { /// Get the current difficulty (max_distance / distance_threshold) fn get_difficulty() -> U512; - /// Get block ema - fn get_block_time_ema() -> u64; - /// Get last block timestamp fn get_last_block_time() -> u64; - // Get last block mining time + /// Get last block mining time fn get_last_block_duration() -> u64; + fn get_chain_height() -> u32; + fn verify_nonce_on_import_block(block_hash: [u8; 32], nonce: [u8; 64]) -> bool; + fn verify_nonce_local_mining(block_hash: [u8; 32], nonce: [u8; 64]) -> bool; + fn verify_and_get_achieved_difficulty(block_hash: [u8; 32], nonce: [u8; 64]) -> (bool, U512); } } diff --git a/runtime/src/apis.rs b/runtime/src/apis.rs index ab088a7f..d4e0f33f 100644 --- a/runtime/src/apis.rs +++ b/runtime/src/apis.rs @@ -146,10 +146,6 @@ impl_runtime_apis! { pallet_qpow::Pallet::::get_difficulty() } - fn get_block_time_ema() -> u64 { - pallet_qpow::Pallet::::get_block_time_ema() - } - fn get_last_block_time() -> u64 { pallet_qpow::Pallet::::get_last_block_time() } diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index 272a26e6..f38ef13c 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -55,7 +55,7 @@ use smallvec::smallvec; use qp_scheduler::BlockNumberOrTimestamp; use sp_runtime::{ traits::{BlakeTwo256, One}, - AccountId32, FixedU128, Perbill, Permill, + AccountId32, Perbill, Permill, }; use sp_version::RuntimeVersion; @@ -150,21 +150,15 @@ parameter_types! { /// Target block time ms pub const TargetBlockTime: u64 = TARGET_BLOCK_TIME_MS; pub const TimestampBucketSize: u64 = 2 * TARGET_BLOCK_TIME_MS; // Nyquist frequency - /// Initial mining difficulty - low value for development - pub const QPoWInitialDifficulty: U512 = U512([1189189, 0, 0, 0, 0, 0, 0, 0]); - /// Difficulty adjustment percent clamp - pub const DifficultyAdjustPercentClamp: FixedU128 = FixedU128::from_rational(10, 100); + /// Initial mining difficulty + pub const QPoWInitialDifficulty: U512 = U512([4_000_000, 0, 0, 0, 0, 0, 0, 0]); } impl pallet_qpow::Config for Runtime { - // Starting difficulty - should be challenging enough to require some work but not too high type InitialDifficulty = QPoWInitialDifficulty; - type DifficultyAdjustPercentClamp = DifficultyAdjustPercentClamp; type TargetBlockTime = TargetBlockTime; type MaxReorgDepth = ConstU32<180>; - type WeightInfo = (); - type EmaAlpha = ConstU32<100>; // out of 1000, last_block_time * alpha + (previous_ema * (1 - alpha)) on moving average } parameter_types! {