From ad834674d8372caaacf1e65876a942fd706eba65 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Fri, 19 Sep 2025 02:09:47 +0800 Subject: [PATCH 1/3] Add quantus prediction market pallet and refactor qpow pallet --- Cargo.lock | 17 ++ Cargo.toml | 1 + pallets/qpm/Cargo.toml | 51 ++++++ pallets/qpm/src/lib.rs | 239 +++++++++++++++++++++++++++ pallets/qpm/src/mock.rs | 125 ++++++++++++++ pallets/qpm/src/tests.rs | 48 ++++++ pallets/qpow/src/benchmarking.rs | 2 +- pallets/qpow/src/lib.rs | 97 +++-------- pallets/qpow/src/tests.rs | 119 ++++++------- primitives/consensus/qpow/src/lib.rs | 12 ++ runtime/Cargo.toml | 1 + runtime/src/apis.rs | 2 +- runtime/src/configs/mod.rs | 5 + 13 files changed, 584 insertions(+), 135 deletions(-) create mode 100644 pallets/qpm/Cargo.toml create mode 100644 pallets/qpm/src/lib.rs create mode 100644 pallets/qpm/src/mock.rs create mode 100644 pallets/qpm/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 056241b4..a0ec2299 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6691,6 +6691,22 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-qpm" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-balances 40.0.1", + "parity-scale-codec", + "scale-info", + "sp-consensus-qpow", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-qpow" version = "0.1.0" @@ -8444,6 +8460,7 @@ dependencies = [ "pallet-merkle-airdrop", "pallet-mining-rewards", "pallet-preimage", + "pallet-qpm", "pallet-qpow", "pallet-ranked-collective", "pallet-recovery", diff --git a/Cargo.toml b/Cargo.toml index b2f7cd8b..b27020c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,7 @@ pallet-balances = { path = "./pallets/balances", default-features = false } pallet-merkle-airdrop = { path = "./pallets/merkle-airdrop", default-features = false } pallet-mining-rewards = { path = "./pallets/mining-rewards", default-features = false } pallet-qpow = { path = "./pallets/qpow", default-features = false } +pallet-qpm = { path = "./pallets/qpm", default-features = false } pallet-reversible-transfers = { path = "./pallets/reversible-transfers", default-features = false } pallet-scheduler = { path = "./pallets/scheduler", default-features = false } pallet-wormhole = { path = "./pallets/wormhole", default-features = false } diff --git a/pallets/qpm/Cargo.toml b/pallets/qpm/Cargo.toml new file mode 100644 index 00000000..1bf01904 --- /dev/null +++ b/pallets/qpm/Cargo.toml @@ -0,0 +1,51 @@ +[package] +authors.workspace = true +description = "Quantus prediction markets" +edition.workspace = true +homepage.workspace = true +license = "Apache-2.0" +name = "pallet-qpm" +publish = false +repository.workspace = true +version = "0.1.0" + +[package.metadata.docs.rs] +targets = [ + "aarch64-apple-darwin", + "wasm32-unknown-unknown", + "x86_64-unknown-linux-gnu", +] + +[dependencies] +codec = { workspace = true, default-features = false, features = ["derive"] } +frame-support.workspace = true +frame-system.workspace = true +log.workspace = true +scale-info = { workspace = true, default-features = false, features = ["derive"] } +sp-runtime.workspace = true +sp-consensus-qpow.workspace = true + +[dev-dependencies] +pallet-balances.features = ["std"] +pallet-balances.workspace = true +sp-core.workspace = true +sp-io.workspace = true + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-consensus-qpow/std", + "sp-runtime/std", +] +try-runtime = [ + "frame-support/try-runtime", +] diff --git a/pallets/qpm/src/lib.rs b/pallets/qpm/src/lib.rs new file mode 100644 index 00000000..6a161d1e --- /dev/null +++ b/pallets/qpm/src/lib.rs @@ -0,0 +1,239 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +//! Quantus Prediction Markets (QPM) pallet +//! This pallet provides the functionality for creating and managing prediction markets. +//! It allows users to create markets, place bets, and resolve markets based on certain conditions. +//! The pallet also provides a mechanism for reporting and disputing market outcomes. + +// Re-export all pallet parts, this is needed to properly import the pallet into the runtime. +extern crate alloc; + +use codec::{Decode, Encode}; +use frame_support::{ + pallet_prelude::*, + traits::{ + fungible::{Inspect, Mutate}, + Get, + }, +}; +use frame_system::pallet_prelude::*; +use sp_consensus_qpow::BlockInfo; +use sp_runtime::traits::UniqueSaturatedInto; + +pub use pallet::*; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +/// A single prediction +#[derive( + Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, DecodeWithMemTracking, +)] +pub struct CompactPrediction { + /// Prediction moment + moment: Moment, + /// Prediction account + account: AccountId, +} + +/// Bounded sorted vector - single storage entry +type PredictionList = + BoundedVec, MaxPredictions>; +/// Balance type +type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use sp_runtime::traits::{AtLeast32Bit, BlockNumberProvider, Saturating, Scale}; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Block number provider + type BlockNumberProvider: BlockNumberProvider>; + + /// Get block information + type BlockTimeInfo: sp_consensus_qpow::BlockInfo, Self::Moment>; + + /// Currency type + type Currency: Mutate; + + /// Type that represents the moment in time + type Moment: Parameter + + Default + + AtLeast32Bit + + Scale, Output = Self::Moment> + + Copy + + MaxEncodedLen + + scale_info::StaticTypeInfo; + + /// Prediction deposit amount, flat + #[pallet::constant] + type PredictionDepositAmount: Get>; + + /// Block buffer time. How many blocks in the future can predictions be made for? + /// + /// This value determines the minimum number of blocks in the future for which predictions can be made. + #[pallet::constant] + type BlockBufferTime: Get>; + + /// Constant address for pool + #[pallet::constant] + type PoolAddress: Get; + + /// Max predictions for a block + #[pallet::constant] + type MaxPredictions: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); + + /// Predictions for the block + #[pallet::storage] + pub type Predictions = StorageMap< + _, + Blake2_128Concat, + BlockNumberFor, + PredictionList, + ValueQuery, + >; + + #[pallet::error] + pub enum Error { + /// Prediction too early + PredictionTooEarly, + /// Exceeded max predictions for a block + TooManyPredictions, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Prediction made + PredictionMade { + block_number: BlockNumberFor, + prediction: CompactPrediction, + }, + /// Prediction resolved + PredictionResolved { + block_number: BlockNumberFor, + prediction: CompactPrediction, + }, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(10_000)] + pub fn predict( + origin: OriginFor, + block_number: BlockNumberFor, + timestamp: T::Moment, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // ensure block number is in the future + let current_block = T::BlockNumberProvider::current_block_number(); + + ensure!( + current_block.saturating_add(T::BlockBufferTime::get()) <= block_number, + Error::::PredictionTooEarly + ); + + // Transfer funds from the user to the pool address + T::Currency::transfer( + &who, + &T::PoolAddress::get(), + T::PredictionDepositAmount::get(), + frame_support::traits::tokens::Preservation::Preserve, + )?; + + Predictions::::try_mutate(block_number, |predictions| -> DispatchResult { + predictions + .try_push(CompactPrediction { moment: timestamp, account: who }) + .map_err(|_| Error::::TooManyPredictions)?; + + Ok(()) + })?; + + Ok(()) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_now: BlockNumberFor) -> Weight { + Weight::from_parts(10_000, 0) + } + + fn on_finalize(now: BlockNumberFor) { + if let Some((prediction, total_predictions)) = Self::resolve_predictions(now) { + if let Ok(_) = T::Currency::transfer( + &T::PoolAddress::get(), + &prediction.account, + T::PredictionDepositAmount::get().saturating_mul( + total_predictions.saturating_sub(One::one()).unique_saturated_into(), + ), + frame_support::traits::tokens::Preservation::Preserve, + ) { + Pallet::::deposit_event(Event::PredictionResolved { + block_number: now, + prediction, + }); + } else { + log::error!(target: "qpm", "Failed to transfer prediction deposit"); + } + } + } + } +} + +impl Pallet { + fn resolve_predictions( + block_number: BlockNumberFor, + ) -> Option<(CompactPrediction, u32)> { + let predictions = Predictions::::get(block_number); + + if predictions.is_empty() { + return None; + } + + let total_predictions = predictions.len() as u32; + let actual_block_time = T::BlockTimeInfo::block_time(block_number); + + let insert_index = predictions.binary_search_by_key(&actual_block_time, |pred| pred.moment); + + match insert_index { + Ok(exact_index) => Some((predictions[exact_index].clone(), total_predictions)), + Err(insert_index) => { + // get two neighbors and compare + let prev_prediction = predictions.get(insert_index.checked_sub(1)?).cloned(); + let next_prediction = predictions.get(insert_index).cloned(); + + match (prev_prediction, next_prediction) { + (Some(prev), Some(next)) => { + let prev_moment: u128 = prev.moment.unique_saturated_into(); + let next_moment: u128 = next.moment.unique_saturated_into(); + let prev_distance = + prev_moment.abs_diff(actual_block_time.unique_saturated_into()); + let next_distance = + next_moment.abs_diff(actual_block_time.unique_saturated_into()); + + if prev_distance < next_distance { + Some((prev, total_predictions)) + } else { + Some((next, total_predictions)) + } + }, + (Some(prev), None) => Some((prev, total_predictions)), + (None, Some(next)) => Some((next, total_predictions)), + (None, None) => None, + } + }, + } + } +} diff --git a/pallets/qpm/src/mock.rs b/pallets/qpm/src/mock.rs new file mode 100644 index 00000000..76e1d9eb --- /dev/null +++ b/pallets/qpm/src/mock.rs @@ -0,0 +1,125 @@ +use core::marker::PhantomData; + +use crate as pallet_qpm; +use alloc::collections::BTreeMap; +use frame_support::derive_impl; +use pallet_balances::AccountData; +use sp_consensus_qpow::BlockInfo; +use sp_core::{parameter_types, ConstU128}; +use sp_runtime::BuildStorage; + +type Block = frame_system::mocking::MockBlock; + +#[frame_support::runtime] +mod runtime { + // The main runtime + #[runtime::runtime] + // Runtime Types to be generated + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask, + RuntimeViewFunction + )] + pub struct Test; + + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet; + + #[runtime::pallet_index(1)] + pub type QPM = pallet_qpm::Pallet; + + #[runtime::pallet_index(2)] + pub type Balances = pallet_balances::Pallet; +} + +pub type AccountId = u64; +type Balance = u128; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountId = AccountId; + type AccountData = AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<0>; + type AccountStore = frame_system::Pallet; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; +} + +// Thread-local storage for block times +thread_local! { + pub static MOCK_BLOCK_TIMES: core::cell::RefCell> = core::cell::RefCell::new(BTreeMap::new()); +} + +pub fn set_mock_block_time(block: u64, moment: u64) { + MOCK_BLOCK_TIMES.with(|m| { + m.borrow_mut().insert(block, moment); + }); +} + +pub struct MockBlockTimes(PhantomData<(B, M)>); + +impl< + Moment: Copy + Default + Ord + From + Into, + BlockNumber: Copy + Default + Ord + From + Into, + > BlockInfo for MockBlockTimes +{ + fn average_block_time() -> BlockNumber { + // For mock, just return a constant + 10.into() + } + + fn block_time(block_number: BlockNumber) -> Moment { + // Use a static instance for tests + MOCK_BLOCK_TIMES + .with(|m| m.borrow_mut().get(&block_number.into()).copied().unwrap_or_default()) + .into() + } + + fn last_block_time() -> Moment { + MOCK_BLOCK_TIMES.with(|m| { + m.borrow_mut() + .iter() + .next_back() + .map(|(_, &moment)| moment) + .unwrap_or_default() + .into() + }) + } +} + +parameter_types! { + pub const PredictionDepositAmount: u128 = 100; + pub const BlockBufferTime: u32 = 5; + pub PoolAddress: AccountId = 123456; + pub const MaxPredictions: u32 = 256; +} + +impl pallet_qpm::Config for Test { + type BlockNumberProvider = System; + type Moment = u64; + type BlockTimeInfo = MockBlockTimes; + type Currency = Balances; + type PredictionDepositAmount = PredictionDepositAmount; + type BlockBufferTime = BlockBufferTime; + type PoolAddress = PoolAddress; + type MaxPredictions = MaxPredictions; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/pallets/qpm/src/tests.rs b/pallets/qpm/src/tests.rs new file mode 100644 index 00000000..d5f41a5d --- /dev/null +++ b/pallets/qpm/src/tests.rs @@ -0,0 +1,48 @@ +use super::*; +use crate::{CompactPrediction, Pallet, PredictionList, Predictions}; +use mock::*; + +#[test] +fn block_time_lookup_works() { + new_test_ext().execute_with(|| { + set_mock_block_time(1, 1000); + set_mock_block_time(2, 2000); + + assert_eq!( as BlockInfo>::block_time(1), 1000); + assert_eq!( as BlockInfo>::block_time(2), 2000); + assert_eq!( as BlockInfo>::block_time(3), 0); + }); +} + +#[test] +fn closest_prediction_logic_works() { + new_test_ext().execute_with(|| { + let block_number = 10u64; + let preds: PredictionList = vec![ + CompactPrediction { moment: 1000, account: 1 }, + CompactPrediction { moment: 2000, account: 2 }, + CompactPrediction { moment: 3000, account: 3 }, + ] + .try_into() + .expect("msg"); + + Predictions::::insert(block_number, preds); + + set_mock_block_time(block_number, 2100); + + let (winner, total) = Pallet::::resolve_predictions(block_number).unwrap(); + assert_eq!(total, 3); + assert_eq!(winner.account, 2); + assert_eq!(winner.moment, 2000); + + set_mock_block_time(block_number, 2900); + let (winner, _) = Pallet::::resolve_predictions(block_number).unwrap(); + assert_eq!(winner.account, 3); + assert_eq!(winner.moment, 3000); + + set_mock_block_time(block_number, 1000); + let (winner, _) = Pallet::::resolve_predictions(block_number).unwrap(); + assert_eq!(winner.account, 1); + assert_eq!(winner.moment, 1000); + }); +} diff --git a/pallets/qpow/src/benchmarking.rs b/pallets/qpow/src/benchmarking.rs index 8ca51db9..92ccbd09 100644 --- a/pallets/qpow/src/benchmarking.rs +++ b/pallets/qpow/src/benchmarking.rs @@ -22,7 +22,7 @@ mod benchmarks { let block_number = BlockNumberFor::::from(1000u32); frame_system::Pallet::::set_block_number(block_number); - let initial_distance_threshold = get_initial_distance_threshold::(); + let initial_distance_threshold = initial_distance_threshold::(); let max_history = T::BlockTimeHistorySize::get(); let adjustment_period = T::AdjustmentPeriod::get(); diff --git a/pallets/qpow/src/lib.rs b/pallets/qpow/src/lib.rs index 6cd98bc6..3ed5854f 100644 --- a/pallets/qpow/src/lib.rs +++ b/pallets/qpow/src/lib.rs @@ -4,15 +4,12 @@ extern crate alloc; pub use pallet::*; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; #[cfg(test)] mod mock; - #[cfg(test)] mod tests; - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; - pub mod weights; use weights::*; @@ -30,7 +27,7 @@ pub mod pallet { traits::{BuildGenesisConfig, Time}, }; use frame_system::pallet_prelude::BlockNumberFor; - use qpow_math::{get_nonce_distance, get_random_rsa, hash_to_group_bigint, is_valid_nonce}; + use qpow_math::is_valid_nonce; use sp_arithmetic::FixedU128; use sp_core::U512; @@ -50,6 +47,7 @@ pub mod pallet { pub struct Pallet(_); #[pallet::storage] + #[pallet::getter(fn block_distance_thresholds)] pub type BlockDistanceThresholds = StorageMap<_, Twox64Concat, BlockNumberFor, DistanceThreshold, ValueQuery>; @@ -63,6 +61,7 @@ pub mod pallet { pub type CurrentDistanceThreshold = StorageValue<_, DistanceThreshold, ValueQuery>; #[pallet::storage] + #[pallet::getter(fn total_work)] pub type TotalWork = StorageValue<_, WorkValue, ValueQuery>; #[pallet::storage] @@ -72,7 +71,7 @@ pub mod pallet { pub type BlockTimeHistory = StorageMap<_, Twox64Concat, HistoryIndexType, BlockDuration, ValueQuery>; - // Index for current position in ring buffer + /// Index for current position in ring buffer #[pallet::storage] pub type HistoryIndex = StorageValue<_, HistoryIndexType, ValueQuery>; @@ -133,7 +132,7 @@ pub mod pallet { #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - let initial_distance_threshold = get_initial_distance_threshold::(); + let initial_distance_threshold = initial_distance_threshold::(); // Set current distance_threshold for the genesis block >::put(initial_distance_threshold); @@ -168,7 +167,7 @@ pub mod pallet { ArithmeticOverflow, } - pub fn get_initial_distance_threshold() -> DistanceThreshold { + pub fn initial_distance_threshold() -> DistanceThreshold { DistanceThreshold::one().shl(T::InitialDistanceThresholdExponent::get()) } @@ -184,7 +183,7 @@ pub mod pallet { >::put( pallet_timestamp::Pallet::::now().saturated_into::(), ); - let initial_distance_threshold: U512 = get_initial_distance_threshold::(); + let initial_distance_threshold: U512 = initial_distance_threshold::(); >::put(initial_distance_threshold); } Weight::zero() @@ -311,7 +310,7 @@ pub mod pallet { // Update TotalWork let old_total_work = >::get(); - let current_work = Self::get_difficulty(); + let current_work = Self::difficulty(); let new_total_work = old_total_work.saturating_add(current_work); >::put(new_total_work); log::debug!(target: "qpow", @@ -426,11 +425,11 @@ pub mod pallet { } }; - let min_distance = Self::get_min_distance(); + let min_distance = DistanceThreshold::one(); if adjusted < min_distance { adjusted = min_distance; } else { - let max_distance = Self::get_max_distance(); + let max_distance = Self::max_distance(); if adjusted > max_distance { adjusted = max_distance; } @@ -449,29 +448,6 @@ pub mod pallet { } impl Pallet { - pub fn is_valid_nonce( - block_hash: [u8; 32], - nonce: NonceType, - threshold: DistanceThreshold, - ) -> (bool, U512) { - is_valid_nonce(block_hash, nonce, threshold) - } - - pub fn get_nonce_distance( - block_hash: [u8; 32], // 256-bit block hash - nonce: NonceType, // 512-bit nonce - ) -> U512 { - get_nonce_distance(block_hash, nonce) - } - - pub fn get_random_rsa(block_hash: &[u8; 32]) -> (U512, U512) { - get_random_rsa(block_hash) - } - - pub fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U512 { - hash_to_group_bigint(h, m, n, solution) - } - // Function used to verify a block that's already in the chain pub fn verify_historical_block( block_hash: [u8; 32], @@ -479,7 +455,7 @@ pub mod pallet { block_number: BlockNumberFor, ) -> bool { // Get the stored distance_threshold for this specific block - let block_distance_threshold = Self::get_distance_threshold_at_block(block_number); + let block_distance_threshold = Self::block_distance_thresholds(block_number); if block_distance_threshold == U512::zero() { // No stored distance_threshold - cannot verify @@ -487,7 +463,7 @@ pub mod pallet { } // Verify with historical distance_threshold - let (valid, _) = Self::is_valid_nonce(block_hash, nonce, block_distance_threshold); + let (valid, _) = is_valid_nonce(block_hash, nonce, block_distance_threshold); valid } @@ -501,10 +477,9 @@ pub mod pallet { ); return (false, U512::zero(), U512::zero()); } - let distance_threshold = Self::get_distance_threshold(); - let (valid, distance_achieved) = - Self::is_valid_nonce(block_hash, nonce, distance_threshold); - let difficulty = Self::get_difficulty(); + let distance_threshold = Self::distance_threshold(); + let (valid, distance_achieved) = is_valid_nonce(block_hash, nonce, distance_threshold); + let difficulty = Self::difficulty(); (valid, difficulty, distance_achieved) } @@ -525,46 +500,20 @@ pub mod pallet { verify } - pub fn get_distance_threshold() -> DistanceThreshold { + pub fn distance_threshold() -> DistanceThreshold { let stored = >::get(); if stored == U512::zero() { - return get_initial_distance_threshold::(); + return initial_distance_threshold::(); } stored } - pub fn get_min_distance() -> DistanceThreshold { - DistanceThreshold::one() - } - - pub fn get_max_distance() -> DistanceThreshold { - get_initial_distance_threshold::().shl(T::MaxDistanceMultiplier::get()) - } - - pub fn get_difficulty() -> U512 { - Self::get_max_distance() / Self::get_distance_threshold() - } - - pub fn get_distance_threshold_at_block( - block_number: BlockNumberFor, - ) -> DistanceThreshold { - >::get(block_number) - } - - pub fn get_total_work() -> WorkValue { - >::get() - } - - pub fn get_last_block_time() -> Timestamp { - >::get() - } - - pub fn get_last_block_duration() -> BlockDuration { - >::get() + pub fn max_distance() -> DistanceThreshold { + initial_distance_threshold::().shl(T::MaxDistanceMultiplier::get()) } - pub fn get_max_reorg_depth() -> u32 { - T::MaxReorgDepth::get() + pub fn difficulty() -> U512 { + Self::max_distance() / Self::distance_threshold() } } } diff --git a/pallets/qpow/src/tests.rs b/pallets/qpow/src/tests.rs index 59881f6a..1e473230 100644 --- a/pallets/qpow/src/tests.rs +++ b/pallets/qpow/src/tests.rs @@ -2,7 +2,8 @@ use crate::{mock::*, BlockTimeHistory, Config, HistoryIndex, HistorySize}; use frame_support::{pallet_prelude::TypedGet, traits::Hooks}; use primitive_types::U512; use qpow_math::{ - get_random_rsa, hash_to_group_bigint_sha, is_coprime, is_prime, mod_pow, sha3_512, + get_nonce_distance, get_random_rsa, hash_to_group_bigint_sha, is_coprime, is_prime, + is_valid_nonce, mod_pow, sha3_512, }; use std::ops::Shl; @@ -13,8 +14,8 @@ fn test_submit_valid_proof() { let block_hash = [1u8; 32]; // Get current distance_threshold - let distance_threshold = QPow::get_distance_threshold_at_block(0); - let max_distance = QPow::get_max_distance(); + let distance_threshold = QPow::block_distance_thresholds(0); + let max_distance = QPow::max_distance(); println!("Current distance_threshold: {}", distance_threshold); // We need to find valid and invalid nonces for our test @@ -27,8 +28,8 @@ fn test_submit_valid_proof() { invalid_nonce[63] = i; valid_nonce[63] = i + 1; - let invalid_distance = QPow::get_nonce_distance(block_hash, invalid_nonce); - let valid_distance = QPow::get_nonce_distance(block_hash, valid_nonce); + let invalid_distance = get_nonce_distance(block_hash, invalid_nonce); + let valid_distance = get_nonce_distance(block_hash, valid_nonce); // Check if we found a pair where one is valid and one is invalid if invalid_distance > distance_threshold && valid_distance <= distance_threshold { @@ -56,7 +57,7 @@ fn test_submit_valid_proof() { assert!( !valid, "Nonce should be invalid with distance {} > threshold {}", - QPow::get_nonce_distance(block_hash, invalid_nonce), + get_nonce_distance(block_hash, invalid_nonce), max_distance - distance_threshold ); @@ -65,7 +66,7 @@ fn test_submit_valid_proof() { assert!( valid, "Nonce should be valid with distance {} <= threshold {}", - QPow::get_nonce_distance(block_hash, valid_nonce), + get_nonce_distance(block_hash, valid_nonce), max_distance - distance_threshold ); @@ -75,7 +76,7 @@ fn test_submit_valid_proof() { for i in valid_nonce[63] + 1..255 { second_valid[63] = i; - let distance = QPow::get_nonce_distance(block_hash, second_valid); + let distance = get_nonce_distance(block_hash, second_valid); if distance <= max_distance - distance_threshold { println!("Found second valid nonce: {}", i); found_second = true; @@ -102,7 +103,7 @@ fn test_verify_nonce() { let block_hash = [1u8; 32]; // Get current distance_threshold to understand what we need to target - let distance_threshold = QPow::get_distance_threshold(); + let distance_threshold = QPow::distance_threshold(); println!("Current distance_threshold: {}", distance_threshold); // Find a nonce that will be valid for the current distance_threshold @@ -112,7 +113,7 @@ fn test_verify_nonce() { // Try various values until we find one that works for i in 1..255 { valid_nonce[63] = i; - let distance = QPow::get_nonce_distance(block_hash, valid_nonce); + let distance = get_nonce_distance(block_hash, valid_nonce); if distance <= distance_threshold { println!( @@ -142,8 +143,8 @@ fn test_verify_historical_block() { let block_hash = [1u8; 32]; // Get the genesis block distance_threshold - let max_distance = QPow::get_max_distance(); - let genesis_distance_threshold = QPow::get_distance_threshold_at_block(0); + let max_distance = QPow::max_distance(); + let genesis_distance_threshold = QPow::block_distance_thresholds(0); println!("Genesis distance_threshold: {}", genesis_distance_threshold); // Use a nonce that we know works better with our test distance_threshold @@ -151,7 +152,7 @@ fn test_verify_historical_block() { nonce[63] = 186; // This seemed to work in other tests // Check if this nonce is valid for genesis distance_threshold - let distance = QPow::get_nonce_distance(block_hash, nonce); + let distance = get_nonce_distance(block_hash, nonce); let threshold = genesis_distance_threshold; println!("Nonce distance: {}, Threshold: {}", distance, threshold); @@ -165,7 +166,7 @@ fn test_verify_historical_block() { let mut found_valid = false; for byte_value in 1..=255 { nonce[63] = byte_value; - let distance = QPow::get_nonce_distance(block_hash, nonce); + let distance = get_nonce_distance(block_hash, nonce); if distance <= threshold { println!( "Found valid nonce with byte value {}: distance={}", @@ -194,7 +195,7 @@ fn test_verify_historical_block() { run_to_block(1); // Get the distance_threshold that was stored for block 1 - let block_1_distance_threshold = QPow::get_distance_threshold_at_block(1); + let block_1_distance_threshold = QPow::block_distance_thresholds(1); assert!( block_1_distance_threshold > U512::zero(), "Block 1 should have a stored distance_threshold" @@ -211,7 +212,7 @@ fn test_verify_historical_block() { } // Verify a nonce against block 1's distance_threshold with direct method - let (valid, _) = QPow::is_valid_nonce(block_hash, nonce, block_1_distance_threshold); + let (valid, _) = is_valid_nonce(block_hash, nonce, block_1_distance_threshold); assert!( valid, "Nonce with distance {} should be valid for block 1 threshold {}", @@ -235,7 +236,7 @@ fn test_verify_historical_block() { fn test_distance_threshold_storage_and_retrieval() { new_test_ext().execute_with(|| { // 1. Test genesis block distance_threshold - let genesis_distance_threshold = QPow::get_distance_threshold_at_block(0); + let genesis_distance_threshold = QPow::block_distance_thresholds(0); let initial_distance_threshold = U512::one().shl(::InitialDistanceThresholdExponent::get()); @@ -248,7 +249,7 @@ fn test_distance_threshold_storage_and_retrieval() { run_to_block(1); // 3. Check distance_threshold for block 1 - let block_1_distance_threshold = QPow::get_distance_threshold_at_block(1); + let block_1_distance_threshold = QPow::block_distance_thresholds(1); assert_eq!( block_1_distance_threshold, initial_distance_threshold, "Block 1 should have same distance_threshold as initial" @@ -259,7 +260,7 @@ fn test_distance_threshold_storage_and_retrieval() { run_to_block(adjustment_period + 1); // 5. Verify historical blocks maintain their distance_threshold - let block_1_distance_threshold_after = QPow::get_distance_threshold_at_block(1); + let block_1_distance_threshold_after = QPow::block_distance_thresholds(1); assert_eq!( block_1_distance_threshold_after, block_1_distance_threshold, "Historical block distance_threshold should not change" @@ -269,7 +270,7 @@ fn test_distance_threshold_storage_and_retrieval() { let latest_block = System::block_number(); let future_block = latest_block + 1000; assert_eq!( - QPow::get_distance_threshold_at_block(future_block), + QPow::block_distance_thresholds(future_block), U512::zero(), "Future block distance_threshold should be 0" ); @@ -283,15 +284,15 @@ fn test_total_distance_threshold_initialization() { new_test_ext().execute_with(|| { // Initially, total distance_threshold should be as genesis distance_threshold let initial_work = U512::one(); - assert_eq!(QPow::get_total_work(), initial_work, "Initial TotalWork should be 0"); + assert_eq!(QPow::total_work(), initial_work, "Initial TotalWork should be 0"); // After the first btest_total_distance_threshold_increases_with_each_blocklock, TotalWork // should equal block 1's distance_threshold run_to_block(1); - let block_1_distance_threshold = QPow::get_distance_threshold_at_block(1); - let max_distance = QPow::get_max_distance(); + let block_1_distance_threshold = QPow::block_distance_thresholds(1); + let max_distance = QPow::max_distance(); let current_work = max_distance / block_1_distance_threshold; - let total_work = QPow::get_total_work(); + let total_work = QPow::total_work(); assert_eq!( total_work, initial_work + current_work, @@ -305,13 +306,13 @@ fn test_total_distance_threshold_accumulation() { new_test_ext().execute_with(|| { // Generate consecutive blocks and check distance_threshold accumulation let mut expected_total = U512::one(); - let max_distance = QPow::get_max_distance(); + let max_distance = QPow::max_distance(); for i in 1..=10 { run_to_block(i); - let block_distance_threshold = QPow::get_distance_threshold_at_block(i as u64); + let block_distance_threshold = QPow::block_distance_thresholds(i as u64); expected_total = expected_total.saturating_add(max_distance / block_distance_threshold); - let stored_total = QPow::get_total_work(); + let stored_total = QPow::total_work(); assert_eq!( stored_total, expected_total, "TotalDifficulty after block {} should be the sum of all blocks' difficulties", @@ -327,12 +328,12 @@ fn test_total_distance_threshold_after_adjustment() { // Advance to the point where distance_threshold gets adjusted let adjustment_period = ::AdjustmentPeriod::get(); run_to_block(adjustment_period + 1); - let max_distance = QPow::get_max_distance(); + let max_distance = QPow::max_distance(); // Check if distance_threshold has changed let initial_distance_threshold = U512::one().shl(::InitialDistanceThresholdExponent::get()); let new_distance_threshold = - QPow::get_distance_threshold_at_block((adjustment_period + 1) as u64); + QPow::block_distance_thresholds((adjustment_period + 1) as u64); // We assume distance_threshold may have changed println!( @@ -343,12 +344,12 @@ fn test_total_distance_threshold_after_adjustment() { // Calculate expected cumulative distance_threshold let mut expected_total = U512::one(); for i in 1..=(adjustment_period + 1) { - let block_diff = QPow::get_distance_threshold_at_block(i as u64); + let block_diff = QPow::block_distance_thresholds(i as u64); expected_total += max_distance / block_diff; } // Compare with stored value - let stored_total = QPow::get_total_work(); + let stored_total = QPow::total_work(); assert_eq!( stored_total, expected_total, "TotalDifficulty should correctly account for distance_threshold changes" @@ -360,11 +361,11 @@ fn test_total_distance_threshold_after_adjustment() { fn test_total_distance_threshold_increases_with_each_block() { new_test_ext().execute_with(|| { // Check initial value - let initial_total = QPow::get_total_work(); + let initial_total = QPow::total_work(); // Run to block 1 and check the increase run_to_block(1); - let total_after_block_1 = QPow::get_total_work(); + let total_after_block_1 = QPow::total_work(); assert!( total_after_block_1 > initial_total, "TotalDifficulty should increase after a new block" @@ -372,17 +373,17 @@ fn test_total_distance_threshold_increases_with_each_block() { // Run to block 2 and check the increase again run_to_block(2); - let total_after_block_2 = QPow::get_total_work(); + let total_after_block_2 = QPow::total_work(); assert!( total_after_block_2 > total_after_block_1, "TotalDifficulty should increase after each new block" ); - let max_distance = QPow::get_max_distance(); + let max_distance = QPow::max_distance(); // Verify that the increase matches the distance_threshold of block 2 let block_2_diff = total_after_block_2 - total_after_block_1; assert_eq!( block_2_diff, - max_distance / QPow::get_distance_threshold_at_block(2), + max_distance / QPow::block_distance_thresholds(2), "TotalDifficulty increase should match the distance_threshold of the new block" ); }); @@ -395,7 +396,7 @@ fn test_integrated_verification_flow() { let block_hash = [1u8; 32]; // Get the current distance_threshold - let distance_threshold = QPow::get_distance_threshold_at_block(0); + let distance_threshold = QPow::block_distance_thresholds(0); println!("Current distance_threshold: {}", distance_threshold); // Use a nonce that we know works for our tests @@ -403,7 +404,7 @@ fn test_integrated_verification_flow() { nonce[63] = 38; // This worked in your previous tests // Make sure it's actually valid - let distance = QPow::get_nonce_distance(block_hash, nonce); + let distance = get_nonce_distance(block_hash, nonce); println!("Nonce distance: {}, Threshold: {}", distance, distance_threshold); if distance > distance_threshold { @@ -429,14 +430,14 @@ fn test_mining_import_flow() { let block_hash = [1u8; 32]; // Find a valid nonce for testing - let distance_threshold = QPow::get_distance_threshold(); + let distance_threshold = QPow::distance_threshold(); let mut valid_nonce = [0u8; 64]; let mut found_valid = false; for i in 1..1000u16 { valid_nonce[62] = (i >> 8) as u8; valid_nonce[63] = (i & 0xff) as u8; - let distance = QPow::get_nonce_distance(block_hash, valid_nonce); + let distance = get_nonce_distance(block_hash, valid_nonce); if distance <= distance_threshold { found_valid = true; break; @@ -466,7 +467,7 @@ fn test_mining_always_importable() { println!("Testing block_hash {}: {:?}", i, hex::encode(block_hash)); // Find a valid nonce - let distance_threshold = QPow::get_distance_threshold(); + let distance_threshold = QPow::distance_threshold(); let mut valid_nonce = [0u8; 64]; let mut found_valid = false; @@ -474,7 +475,7 @@ fn test_mining_always_importable() { valid_nonce[62] = (j >> 8) as u8; valid_nonce[63] = (j & 0xff) as u8; - let distance = QPow::get_nonce_distance(*block_hash, valid_nonce); + let distance = get_nonce_distance(*block_hash, valid_nonce); if distance <= distance_threshold { found_valid = true; break; @@ -519,14 +520,14 @@ fn test_metadata_apis_correctness() { println!("Testing {}: {:?}", description, hex::encode(block_hash)); // Find and verify a valid nonce - let distance_threshold = QPow::get_distance_threshold(); + let distance_threshold = QPow::distance_threshold(); let mut valid_nonce = [0u8; 64]; let mut found_valid = false; for i in 1..1000u16 { valid_nonce[62] = (i >> 8) as u8; valid_nonce[63] = (i & 0xff) as u8; - let distance = QPow::get_nonce_distance(*block_hash, valid_nonce); + let distance = get_nonce_distance(*block_hash, valid_nonce); if distance <= distance_threshold { found_valid = true; break; @@ -549,7 +550,7 @@ fn test_metadata_apis_correctness() { fn test_invalid_nonce_no_metadata_storage() { new_test_ext().execute_with(|| { let block_hash = [1u8; 32]; - let distance_threshold = QPow::get_distance_threshold(); + let distance_threshold = QPow::distance_threshold(); // Find an invalid nonce (distance > threshold) let mut invalid_nonce = [0u8; 64]; @@ -559,7 +560,7 @@ fn test_invalid_nonce_no_metadata_storage() { invalid_nonce[62] = (i >> 8) as u8; invalid_nonce[63] = (i & 0xff) as u8; - let distance = QPow::get_nonce_distance(block_hash, invalid_nonce); + let distance = get_nonce_distance(block_hash, invalid_nonce); if distance > distance_threshold { found_invalid = true; break; @@ -593,7 +594,7 @@ fn test_event_emission_on_import_vs_mining() { let block_hash = [1u8; 32]; // Get current distance_threshold - let distance_threshold = QPow::get_distance_threshold(); + let distance_threshold = QPow::distance_threshold(); println!("Current distance_threshold: {}", distance_threshold); // Find a valid nonce for testing @@ -604,7 +605,7 @@ fn test_event_emission_on_import_vs_mining() { valid_nonce[63] = (i % 256) as u8; valid_nonce[62] = (i / 256) as u8; - let distance = QPow::get_nonce_distance(block_hash, valid_nonce); + let distance = get_nonce_distance(block_hash, valid_nonce); if distance <= distance_threshold { found_valid = true; println!("Found valid nonce: {:?}", valid_nonce); @@ -845,7 +846,7 @@ fn test_distance_threshold_adjustment_boundaries() { "When adjustment would put distance_threshold below minimum, it should be clamped to minimum"); // 2. Test maximum distance_threshold boundary - let max_distance = QPow::get_max_distance(); + let max_distance = QPow::max_distance(); // A. If initial distance_threshold is already at maximum, it should stay there let current_distance_threshold = max_distance; // Above Maximum @@ -879,7 +880,7 @@ fn test_distance_threshold_adjustment_boundaries() { fn test_calculate_distance_threshold_normal_adjustment() { new_test_ext().execute_with(|| { // Start with a medium distance_threshold - let current_distance_threshold = QPow::get_distance_threshold_at_block(0); + let current_distance_threshold = QPow::block_distance_thresholds(0); let target_time = 1000; // 1000ms target // Test slight deviation (10% slower) @@ -913,8 +914,8 @@ fn test_calculate_distance_threshold_normal_adjustment() { #[test] fn test_calculate_distance_threshold_consecutive_adjustments() { new_test_ext().execute_with(|| { - let mut current_distance_threshold = QPow::get_distance_threshold_at_block(0); - let initial_distance_threshold = QPow::get_distance_threshold_at_block(0); + let mut current_distance_threshold = QPow::block_distance_thresholds(0); + let initial_distance_threshold = QPow::block_distance_thresholds(0); let target_time = 1000; // First, measure the effect of a single adjustment @@ -928,7 +929,7 @@ fn test_calculate_distance_threshold_consecutive_adjustments() { println!("Single adjustment increase: {:.2}%", single_adjustment_increase); // Reset and simulate 5 consecutive periods - current_distance_threshold = QPow::get_distance_threshold_at_block(0); + current_distance_threshold = QPow::block_distance_thresholds(0); for i in 0..5 { let new_distance_threshold = QPow::calculate_distance_threshold( current_distance_threshold, @@ -962,7 +963,7 @@ fn test_calculate_distance_threshold_consecutive_adjustments() { #[test] fn test_calculate_distance_threshold_oscillation_damping() { new_test_ext().execute_with(|| { - let initial_distance_threshold = QPow::get_distance_threshold_at_block(0); + let initial_distance_threshold = QPow::block_distance_thresholds(0); let target_time = 1000; // Start with current distance_threshold @@ -1019,7 +1020,7 @@ fn pack_u512_to_f64(value: U512) -> f64 { #[test] fn test_calculate_distance_threshold_stability_over_time() { new_test_ext().execute_with(|| { - let initial_distance_threshold = QPow::get_distance_threshold_at_block(0); + let initial_distance_threshold = QPow::block_distance_thresholds(0); let target_time = 1000; let mut current_distance_threshold = initial_distance_threshold; @@ -1180,7 +1181,7 @@ fn test_median_block_time_ring_buffer() { fn test_block_distance_threshold_storage_and_retrieval() { new_test_ext().execute_with(|| { // 1. Test that genesis block distance_threshold is properly set - let genesis_distance_threshold = QPow::get_distance_threshold_at_block(0); + let genesis_distance_threshold = QPow::block_distance_thresholds(0); let initial_distance_threshold = U512::one().shl(::InitialDistanceThresholdExponent::get()); assert_eq!( @@ -1190,7 +1191,7 @@ fn test_block_distance_threshold_storage_and_retrieval() { // 2. Simulate block production and distance_threshold adjustment run_to_block(1); - let block_1_distance_threshold = QPow::get_distance_threshold_at_block(1); + let block_1_distance_threshold = QPow::block_distance_thresholds(1); assert_eq!( block_1_distance_threshold, initial_distance_threshold, "Block 1 should have same distance_threshold as initial" @@ -1201,7 +1202,7 @@ fn test_block_distance_threshold_storage_and_retrieval() { run_to_block(adjustment_period + 1); // 4. Check that distance_threshold for early blocks hasn't changed - let block_1_distance_threshold_after = QPow::get_distance_threshold_at_block(1); + let block_1_distance_threshold_after = QPow::block_distance_thresholds(1); assert_eq!( block_1_distance_threshold_after, block_1_distance_threshold, "Historical block distance_threshold should not change" @@ -1210,7 +1211,7 @@ fn test_block_distance_threshold_storage_and_retrieval() { // 5. Test non-existent block (future block) let latest_block = System::block_number(); let future_block = latest_block + 1000; - let future_distance_threshold = QPow::get_distance_threshold_at_block(future_block); + let future_distance_threshold = QPow::block_distance_thresholds(future_block); assert_eq!( future_distance_threshold, U512::zero(), diff --git a/primitives/consensus/qpow/src/lib.rs b/primitives/consensus/qpow/src/lib.rs index 2c62a507..99e643be 100644 --- a/primitives/consensus/qpow/src/lib.rs +++ b/primitives/consensus/qpow/src/lib.rs @@ -72,3 +72,15 @@ pub enum Error { /// Other error occurred Other(Vec), } + +/// Block information trait. +pub trait BlockInfo { + /// Returns average block time in milliseconds. + fn average_block_time() -> BlockNumber; + + /// Latest block time recorded + fn last_block_time() -> Timestamp; + + /// Return the block time for the block number. + fn block_time(block_number: BlockNumber) -> Timestamp; +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 3583d87a..544c8278 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -29,6 +29,7 @@ pallet-conviction-voting.workspace = true pallet-merkle-airdrop.workspace = true pallet-mining-rewards.workspace = true pallet-preimage.workspace = true +pallet-qpm.workspace = true pallet-qpow.workspace = true pallet-ranked-collective.workspace = true pallet-recovery.workspace = true diff --git a/runtime/src/apis.rs b/runtime/src/apis.rs index 13b0e8ff..bb50d2fd 100644 --- a/runtime/src/apis.rs +++ b/runtime/src/apis.rs @@ -160,7 +160,7 @@ impl_runtime_apis! { } fn get_total_work() -> U512 { - pallet_qpow::Pallet::::get_total_work() + pallet_qpow::Pallet::::total_work() } fn get_block_time_sum() -> u64 { diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index 3b046911..1095783a 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -605,3 +605,8 @@ impl TryFrom for pallet_balances::Call { } } } + +impl pallet_qpm::Config for Runtime { + type BlockTimeInfo = QPow; + type Timestamp = Moment; +} From 37ec03d5b092e426a95621bb820e4fd625b753b6 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:43:19 +0800 Subject: [PATCH 2/3] fmts --- Cargo.toml | 2 +- pallets/qpm/Cargo.toml | 2 +- pallets/qpm/src/lib.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b27020c6..3d5be136 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,8 +102,8 @@ zeroize = { version = "1.7.0", default-features = false } pallet-balances = { path = "./pallets/balances", default-features = false } pallet-merkle-airdrop = { path = "./pallets/merkle-airdrop", default-features = false } pallet-mining-rewards = { path = "./pallets/mining-rewards", default-features = false } -pallet-qpow = { path = "./pallets/qpow", default-features = false } pallet-qpm = { path = "./pallets/qpm", default-features = false } +pallet-qpow = { path = "./pallets/qpow", default-features = false } pallet-reversible-transfers = { path = "./pallets/reversible-transfers", default-features = false } pallet-scheduler = { path = "./pallets/scheduler", default-features = false } pallet-wormhole = { path = "./pallets/wormhole", default-features = false } diff --git a/pallets/qpm/Cargo.toml b/pallets/qpm/Cargo.toml index 1bf01904..60822404 100644 --- a/pallets/qpm/Cargo.toml +++ b/pallets/qpm/Cargo.toml @@ -22,8 +22,8 @@ frame-support.workspace = true frame-system.workspace = true log.workspace = true scale-info = { workspace = true, default-features = false, features = ["derive"] } -sp-runtime.workspace = true sp-consensus-qpow.workspace = true +sp-runtime.workspace = true [dev-dependencies] pallet-balances.features = ["std"] diff --git a/pallets/qpm/src/lib.rs b/pallets/qpm/src/lib.rs index 6a161d1e..fe3f06dd 100644 --- a/pallets/qpm/src/lib.rs +++ b/pallets/qpm/src/lib.rs @@ -76,7 +76,8 @@ pub mod pallet { /// Block buffer time. How many blocks in the future can predictions be made for? /// - /// This value determines the minimum number of blocks in the future for which predictions can be made. + /// This value determines the minimum number of blocks in the future for which predictions + /// can be made. #[pallet::constant] type BlockBufferTime: Get>; From d068f56924f14a72112c1f2711da3f60b9e1ff14 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:48:06 +0800 Subject: [PATCH 3/3] Refactor qpow pallet --- Cargo.lock | 1 + pallets/qpow/Cargo.toml | 2 ++ pallets/qpow/src/impls.rs | 17 ++++++++++ pallets/qpow/src/lib.rs | 50 ++++++++++++++++++++++------ pallets/qpow/src/mock.rs | 2 ++ pallets/qpow/src/tests.rs | 14 ++++---- primitives/consensus/qpow/src/lib.rs | 2 +- runtime/src/apis.rs | 21 ++++++------ runtime/src/configs/mod.rs | 19 ++++++++--- runtime/src/lib.rs | 3 ++ 10 files changed, 98 insertions(+), 33 deletions(-) create mode 100644 pallets/qpow/src/impls.rs diff --git a/Cargo.lock b/Cargo.lock index a0ec2299..37b4adc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6723,6 +6723,7 @@ dependencies = [ "qpow-math", "scale-info", "sp-arithmetic", + "sp-consensus-qpow", "sp-core", "sp-io", "sp-runtime", diff --git a/pallets/qpow/Cargo.toml b/pallets/qpow/Cargo.toml index 39467820..7272825c 100644 --- a/pallets/qpow/Cargo.toml +++ b/pallets/qpow/Cargo.toml @@ -28,6 +28,7 @@ pallet-timestamp.workspace = true qpow-math.workspace = true scale-info = { workspace = true, default-features = false, features = ["derive"] } sp-arithmetic.workspace = true +sp-consensus-qpow.workspace = true sp-core.workspace = true sp-io.workspace = true sp-runtime.workspace = true @@ -55,6 +56,7 @@ std = [ "qpow-math/std", "scale-info/std", "sp-arithmetic/std", + "sp-consensus-qpow/std", "sp-core/std", "sp-io/std", "sp-runtime/std", diff --git a/pallets/qpow/src/impls.rs b/pallets/qpow/src/impls.rs new file mode 100644 index 00000000..9a7973fd --- /dev/null +++ b/pallets/qpow/src/impls.rs @@ -0,0 +1,17 @@ +//! impls for pallet_qpow + +use crate::{Config, Pallet}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_consensus_qpow::BlockInfo; + +impl BlockInfo, T::Moment> for Pallet { + fn average_block_time() -> T::Moment { + Pallet::::median_block_time() + } + + fn block_time(block_number: BlockNumberFor) -> T::Moment { + Pallet::::block + } + + fn last_block_time() -> T::Moment {} +} diff --git a/pallets/qpow/src/lib.rs b/pallets/qpow/src/lib.rs index 3ed5854f..8f69ecf8 100644 --- a/pallets/qpow/src/lib.rs +++ b/pallets/qpow/src/lib.rs @@ -6,6 +6,7 @@ pub use pallet::*; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +mod impls; #[cfg(test)] mod mock; #[cfg(test)] @@ -27,15 +28,15 @@ pub mod pallet { traits::{BuildGenesisConfig, Time}, }; use frame_system::pallet_prelude::BlockNumberFor; - use qpow_math::is_valid_nonce; + use qpow_math::{get_nonce_distance, get_random_rsa, hash_to_group_bigint, is_valid_nonce}; use sp_arithmetic::FixedU128; use sp_core::U512; + use sp_runtime::traits::{AtLeast32Bit, Scale, UniqueSaturatedInto}; /// Type definitions for QPoW pallet pub type NonceType = [u8; 64]; pub type DistanceThreshold = U512; pub type WorkValue = U512; - pub type Timestamp = u64; pub type BlockDuration = u64; pub type PeriodCount = u32; pub type HistoryIndexType = u32; @@ -52,9 +53,11 @@ pub mod pallet { StorageMap<_, Twox64Concat, BlockNumberFor, DistanceThreshold, ValueQuery>; #[pallet::storage] - pub type LastBlockTime = StorageValue<_, Timestamp, ValueQuery>; + #[pallet::getter(fn last_block_time)] + pub type LastBlockTime = StorageValue<_, T::Moment, ValueQuery>; #[pallet::storage] + #[pallet::getter(fn last_block_duration)] pub type LastBlockDuration = StorageValue<_, BlockDuration, ValueQuery>; #[pallet::storage] @@ -80,7 +83,7 @@ pub mod pallet { pub type HistorySize = StorageValue<_, HistorySizeType, ValueQuery>; #[pallet::config] - pub trait Config: frame_system::Config + pallet_timestamp::Config { + pub trait Config: frame_system::Config { /// Pallet's weight info #[pallet::constant] type InitialDistanceThresholdExponent: Get; @@ -110,6 +113,17 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Type that represents the moment in time + type Moment: Parameter + + Default + + AtLeast32Bit + + Scale, Output = Self::Moment> + + Copy + + MaxEncodedLen + + scale_info::StaticTypeInfo; + + type Time: Time; } #[pallet::genesis_config] @@ -180,9 +194,7 @@ pub mod pallet { /// Called when there is remaining weight at the end of the block. fn on_idle(_block_number: BlockNumberFor, _remaining_weight: Weight) -> Weight { if >::get() == 0 { - >::put( - pallet_timestamp::Pallet::::now().saturated_into::(), - ); + >::put(T::Time::now()); let initial_distance_threshold: U512 = initial_distance_threshold::(); >::put(initial_distance_threshold); } @@ -231,7 +243,7 @@ pub mod pallet { } // Sum of block times - pub fn get_block_time_sum() -> u64 { + pub fn block_time_sum() -> u64 { let size = >::get(); if size == 0 { @@ -254,7 +266,7 @@ pub mod pallet { } // Median calculation - pub fn get_median_block_time() -> u64 { + pub fn median_block_time() -> u64 { let size = >::get(); if size == 0 { @@ -299,7 +311,7 @@ pub mod pallet { fn adjust_distance_threshold() { // Get current time - let now = pallet_timestamp::Pallet::::now().saturated_into::(); + let now = T::Time::now(); let last_time = >::get(); let blocks = >::get(); let current_distance_threshold = >::get(); @@ -348,7 +360,7 @@ pub mod pallet { if blocks >= T::AdjustmentPeriod::get() { let history_size = >::get(); if history_size > 0 { - let observed_block_time = Self::get_block_time_sum(); + let observed_block_time = Self::block_time_sum(); let target_time = T::TargetBlockTime::get().saturating_mul(history_size as u64); let new_distance_threshold = Self::calculate_distance_threshold( @@ -515,5 +527,21 @@ pub mod pallet { pub fn difficulty() -> U512 { Self::max_distance() / Self::distance_threshold() } + + /// Re-export `qpow_math` functions for runtime API + pub fn random_rsa(block_hash: &[u8; 32]) -> (U512, U512) { + get_random_rsa(block_hash) + } + + pub fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U512 { + hash_to_group_bigint(h, m, n, solution) + } + + pub fn nonce_distance( + block_hash: [u8; 32], // 256-bit block hash + nonce: NonceType, // 512-bit nonce + ) -> U512 { + get_nonce_distance(block_hash, nonce) + } } } diff --git a/pallets/qpow/src/mock.rs b/pallets/qpow/src/mock.rs index 7c5391eb..e9c6e8fc 100644 --- a/pallets/qpow/src/mock.rs +++ b/pallets/qpow/src/mock.rs @@ -79,6 +79,8 @@ impl pallet_qpow::Config for Test { type MaxReorgDepth = ConstU32<10>; type FixedU128Scale = ConstU128<1_000_000_000_000_000_000>; type MaxDistanceMultiplier = ConstU32<2>; + type Moment = u64; + type Time = Timestamp; } // Build genesis storage according to the mock runtime diff --git a/pallets/qpow/src/tests.rs b/pallets/qpow/src/tests.rs index 1e473230..8fa50695 100644 --- a/pallets/qpow/src/tests.rs +++ b/pallets/qpow/src/tests.rs @@ -1054,7 +1054,7 @@ fn test_median_block_time_empty_history() { new_test_ext().execute_with(|| { // When history is empty, we should get TargetBlockTime let target_block_time = ::TargetBlockTime::get(); - let median = QPow::get_median_block_time(); + let median = QPow::median_block_time(); assert_eq!(median, target_block_time, "Empty history should return target block time"); }); } @@ -1069,7 +1069,7 @@ fn test_median_block_time_single_value() { >::insert(0, block_time); // Median of a single value is that value - let median = QPow::get_median_block_time(); + let median = QPow::median_block_time(); assert_eq!(median, block_time, "Median of a single value should be that value"); }); } @@ -1090,7 +1090,7 @@ fn test_median_block_time_odd_count() { // Median of sorted values [1000, 2000, 3000, 4000, 5000] is 3000 let expected_median = 3000; - let median = QPow::get_median_block_time(); + let median = QPow::median_block_time(); assert_eq!(median, expected_median, "Median of odd count should be the middle value"); }); } @@ -1111,7 +1111,7 @@ fn test_median_block_time_even_count() { // Median of sorted values [1000, 2000, 3000, 4000] is (2000 + 3000) / 2 = 2500 let expected_median = 2500; - let median = QPow::get_median_block_time(); + let median = QPow::median_block_time(); assert_eq!( median, expected_median, "Median of even count should be average of two middle values" @@ -1135,7 +1135,7 @@ fn test_median_block_time_with_duplicates() { // Median of sorted values [1000, 2000, 2000, 2000, 3000] is 2000 let expected_median = 2000; - let median = QPow::get_median_block_time(); + let median = QPow::median_block_time(); assert_eq!( median, expected_median, "Median with duplicates should be correctly calculated" @@ -1161,7 +1161,7 @@ fn test_median_block_time_ring_buffer() { } // Initial median - let initial_median = QPow::get_median_block_time(); + let initial_median = QPow::median_block_time(); assert_eq!(initial_median, 3000, "Initial median should be 3000"); // Simulate record_block_time for new values @@ -1172,7 +1172,7 @@ fn test_median_block_time_ring_buffer() { >::insert(1, 7000); // New median from [3000, 4000, 5000, 6000, 7000] - let new_median = QPow::get_median_block_time(); + let new_median = QPow::median_block_time(); assert_eq!(new_median, 5000, "New median should be calculated from updated ring buffer"); }); } diff --git a/primitives/consensus/qpow/src/lib.rs b/primitives/consensus/qpow/src/lib.rs index 99e643be..1fed56a0 100644 --- a/primitives/consensus/qpow/src/lib.rs +++ b/primitives/consensus/qpow/src/lib.rs @@ -76,7 +76,7 @@ pub enum Error { /// Block information trait. pub trait BlockInfo { /// Returns average block time in milliseconds. - fn average_block_time() -> BlockNumber; + fn average_block_time() -> Timestamp; /// Latest block time recorded fn last_block_time() -> Timestamp; diff --git a/runtime/src/apis.rs b/runtime/src/apis.rs index bb50d2fd..c23a3253 100644 --- a/runtime/src/apis.rs +++ b/runtime/src/apis.rs @@ -146,17 +146,17 @@ impl_runtime_apis! { } fn get_difficulty() -> U512 { - pallet_qpow::Pallet::::get_difficulty() + pallet_qpow::Pallet::::difficulty() } fn get_distance_threshold() -> U512 { - pallet_qpow::Pallet::::get_distance_threshold() + pallet_qpow::Pallet::::distance_threshold() } fn get_distance_threshold_at_block(block_number: u32) -> U512 { // Convert u32 to the appropriate BlockNumber type used by your runtime let block_number_param = block_number; - pallet_qpow::Pallet::::get_distance_threshold_at_block(block_number_param) + pallet_qpow::Pallet::::block_distance_thresholds(block_number_param) } fn get_total_work() -> U512 { @@ -164,19 +164,19 @@ impl_runtime_apis! { } fn get_block_time_sum() -> u64 { - pallet_qpow::Pallet::::get_block_time_sum() + pallet_qpow::Pallet::::block_time_sum() } fn get_median_block_time() -> u64 { - pallet_qpow::Pallet::::get_median_block_time() + pallet_qpow::Pallet::::median_block_time() } fn get_last_block_time() -> u64 { - pallet_qpow::Pallet::::get_last_block_time() + pallet_qpow::Pallet::::last_block_time() } fn get_last_block_duration() -> u64 { - pallet_qpow::Pallet::::get_last_block_duration() + pallet_qpow::Pallet::::last_block_duration() } fn get_chain_height() -> u32 { @@ -184,20 +184,21 @@ impl_runtime_apis! { } fn get_random_rsa(block_hash: &[u8; 32]) -> (U512, U512) { - pallet_qpow::Pallet::::get_random_rsa(block_hash) + pallet_qpow::Pallet::::random_rsa(block_hash) } fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U512{ pallet_qpow::Pallet::::hash_to_group_bigint(h,m,n,solution) } fn get_max_distance() -> U512 { - pallet_qpow::Pallet::::get_max_distance() + pallet_qpow::Pallet::::max_distance() } + fn get_nonce_distance( block_hash: [u8; 32], nonce: [u8; 64] ) -> U512 { - pallet_qpow::Pallet::::get_nonce_distance(block_hash, nonce) + pallet_qpow::Pallet::::nonce_distance(block_hash, nonce) } } diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index 1095783a..744feb2b 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -33,7 +33,7 @@ use crate::{ }, pallet_custom_origins, Spender, }, - MILLI_UNIT, + QPoW, MILLI_UNIT, }; use frame_support::{ derive_impl, parameter_types, @@ -56,7 +56,7 @@ use pallet_transaction_payment::{ConstFeeMultiplier, FungibleAdapter, Multiplier use qp_poseidon::PoseidonHasher; use qp_scheduler::BlockNumberOrTimestamp; use sp_runtime::{ - traits::{ConvertInto, One}, + traits::{AccountIdConversion, ConvertInto, One}, Perbill, Permill, }; use sp_version::RuntimeVersion; @@ -606,7 +606,18 @@ impl TryFrom for pallet_balances::Call { } } +parameter_types! { + pub QpmPalletId: PalletId = PalletId(*b"qpm-pall"); + pub PoolAddress: AccountId = QpmPalletId::get().into_account_truncating(); +} + impl pallet_qpm::Config for Runtime { - type BlockTimeInfo = QPow; - type Timestamp = Moment; + type BlockTimeInfo = QPoW; + type Moment = Moment; + type BlockBufferTime = ConstU32<5>; + type MaxPredictions = ConstU32<256>; + type BlockNumberProvider = System; + type Currency = Balances; + type PredictionDepositAmount = ConstU128; + type PoolAddress = PoolAddress; } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a3b5a929..cb1d8589 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -270,4 +270,7 @@ mod runtime { #[runtime::pallet_index(20)] pub type Recovery = pallet_recovery; + + #[runtime::pallet_index(21)] + pub type QPM = pallet_qpm; }