diff --git a/Cargo.lock b/Cargo.lock index c7f9ceb2..be335ea6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13810,6 +13810,7 @@ dependencies = [ "sp-tracing 10.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?branch=release-polkadot-v1.4.0)", "sp-transaction-pool", "sp-version", + "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", diff --git a/sugondat-chain/primitives/src/lib.rs b/sugondat-chain/primitives/src/lib.rs index 2dc8f5fa..f80d30eb 100644 --- a/sugondat-chain/primitives/src/lib.rs +++ b/sugondat-chain/primitives/src/lib.rs @@ -5,6 +5,9 @@ use sp_runtime::{ MultiSignature, }; +// TODO: probably this could be moved into runtimes/kusama-runtime/src/constants.rs +pub const MAXIMUM_BLOCK_LENGTH: u32 = 5 * 1024 * 1024; + /// An index to a block. pub type BlockNumber = u32; diff --git a/sugondat-chain/runtimes/sugondat-kusama/Cargo.toml b/sugondat-chain/runtimes/sugondat-kusama/Cargo.toml index 651f98ee..ff79c0e7 100644 --- a/sugondat-chain/runtimes/sugondat-kusama/Cargo.toml +++ b/sugondat-chain/runtimes/sugondat-kusama/Cargo.toml @@ -53,6 +53,8 @@ sp-session = { git = "https://github.com/paritytech/polkadot-sdk", default-featu sp-std = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" } sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" } sp-version = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"} +sp-weights = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"} # Polkadot pallet-xcm = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" } @@ -78,7 +80,6 @@ parachain-info = { package = "staging-parachain-info", git = "https://github.com parachains-common = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "release-polkadot-v1.4.0", default-features = false } [dev-dependencies] -sp-io = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"} sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"} [features] @@ -132,6 +133,7 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "sp-io/std", + "sp-weights/std", "substrate-wasm-builder", "xcm-builder/std", "xcm-executor/std", diff --git a/sugondat-chain/runtimes/sugondat-kusama/src/constants.rs b/sugondat-chain/runtimes/sugondat-kusama/src/constants.rs index f94ac76b..e2bdccee 100644 --- a/sugondat-chain/runtimes/sugondat-kusama/src/constants.rs +++ b/sugondat-chain/runtimes/sugondat-kusama/src/constants.rs @@ -25,6 +25,7 @@ pub mod consensus { // Time is measured by number of blocks. pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; /// We assume that ~5% of the block weight is consumed by `on_initialize` handlers. This is /// used to limit the maximal weight of a single extrinsic. diff --git a/sugondat-chain/runtimes/sugondat-kusama/src/lib.rs b/sugondat-chain/runtimes/sugondat-kusama/src/lib.rs index 016dde0c..ac21beb5 100644 --- a/sugondat-chain/runtimes/sugondat-kusama/src/lib.rs +++ b/sugondat-chain/runtimes/sugondat-kusama/src/lib.rs @@ -12,14 +12,18 @@ pub mod xcm_config; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use pallet_transaction_payment::{Multiplier, MultiplierUpdate}; use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, + traits::{ + AccountIdLookup, BlakeTwo256, Block as BlockT, Bounded, Convert, SaturatedConversion, + Saturating, + }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + ApplyExtrinsicResult, FixedPointNumber, Perquintill, }; use sp_std::prelude::*; @@ -34,7 +38,7 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, TransformOrigin}, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Get, TransformOrigin}, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -45,8 +49,11 @@ use frame_system::{ use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; -use sugondat_primitives::{AccountId, AuraId, Balance, BlockNumber, Nonce, Signature}; +use sugondat_primitives::{ + AccountId, AuraId, Balance, BlockNumber, Nonce, Signature, MAXIMUM_BLOCK_LENGTH, +}; +use pallet_transaction_payment::TargetedFeeAdjustment; pub use sp_runtime::{MultiAddress, Perbill, Permill}; use xcm_config::{KusamaLocation, XcmOriginToTransactDispatchOrigin}; @@ -54,7 +61,7 @@ use xcm_config::{KusamaLocation, XcmOriginToTransactDispatchOrigin}; pub use sp_runtime::BuildStorage; // Polkadot imports -use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use polkadot_runtime_common::BlockHashCount; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; @@ -142,7 +149,7 @@ parameter_types! { // `DeletionWeightLimit` and `DeletionQueueDepth` depend on those to parameterize // the lazy contract deletion. pub RuntimeBlockLength: BlockLength = - BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + BlockLength::max_with_normal_ratio(MAXIMUM_BLOCK_LENGTH, NORMAL_DISPATCH_RATIO); pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() .base_block(BlockExecutionWeight::get()) .for_class(DispatchClass::all(), |weights| { @@ -267,14 +274,244 @@ impl pallet_balances::Config for Runtime { parameter_types! { /// Relay Chain `TransactionByteFee` / 10 pub const TransactionByteFee: Balance = MILLICENTS; + + // Common constants used in all runtimes for SlowAdjustingFeeUpdate + + /// The portion of the `NORMAL_DISPATCH_RATIO` that we adjust the fees with. Blocks filled less + /// than this will decrease the weight and more will increase. + pub storage TargetBlockFullness: Perquintill = Perquintill::from_percent(25); + + /// The adjustment variable of the runtime. Higher values will cause `TargetBlockFullness` to + /// change the fees more rapidly. + pub AdjustmentVariableBlockFullness: Multiplier = Multiplier::saturating_from_rational(75, 1000_000); + /// that combined with `AdjustmentVariable`, we can recover from the minimum. + /// See `multiplier_can_grow_from_zero`. + pub MinimumMultiplierBlockFullness: Multiplier = Multiplier::saturating_from_rational(1, 10u128); + /// The maximum amount of the multiplier. + pub MaximumMultiplierBlockFullness: Multiplier = Bounded::max_value(); + + + // parameters used by BlobsFeeAdjustment + + pub storage NextLengthMultiplier: Multiplier = Multiplier::saturating_from_integer(1); + + pub storage TargetBlockSize: u32 = 820 * 1024; // 0.8MiB + // TODO: update those value accordingly with https://github.com/thrumdev/blobs/issues/16 + pub AdjustmentVariableBlockSize: Multiplier = Multiplier::saturating_from_rational(75, 1000_000); + pub MinimumMultiplierBlockSize: Multiplier = Multiplier::saturating_from_rational(1, 10u128); + pub MaximumMultiplierBlockSize: Multiplier = Bounded::max_value(); + + // A positive number represents the count of consecutive blocks that exceeded + // the TargetBlockSize, while a negative number represents the count of + // consecutive blocks that were below the TargetBlockSize - NegativeDeltaTargetBlockFullness. + pub storage BlockSizeTracker: u32 = 0; // 0.8MiB + + // The number of consecutive blocks after which the TargetBlockSize will increase + pub storage IncreaseDeltaBlocks: u32 = 10 * DAYS; + + // The number of bytes that will be added to TargetBlockSize each update + pub storage DeltaTargetBlockSize: u32 = 205 * 1024;// 0.2MiB + + //pub storage DecreaseDeltaBlocks: u32 = 10 * DAYS;, + //pub storage LowerBoundTargetBlockSize +} + +/// Parameterized slow adjusting fee updated based on +/// +//pub type SlowAdjustingFeeUpdate = TargetedFeeAdjustment< +// R, +// TargetBlockFullness, +// AdjustmentVariable, +// MinimumMultiplier, +// MaximumMultiplier, +//>; + +/// Currently pallet_transaction_payment use the following formula: +/// +/// ``` +/// inclusion_fee = base_fee + length_fee + [targeted_fee_adjustment * weight_fee]; +/// ``` +/// +/// Letting us able to update `targeted_fee_adjustment` at the end of each block +/// thanks to `FeeMultiplierUpdate`, this associated type is called inside the `on_finalize` +/// of the transaction_payment pallet with the aim of converting the before `targeted_fee_adjustment` +/// to a new one based on the congestion of the network +/// +/// What this struct does is this PLUS a side effect, the goal is to reach a different formula to +/// calculate fees: +/// +/// ``` +/// inclusion_fee = base_fee + [targeted_length_fee_adjustment * length_fee] + [targeted_weight_fee_adjustment * weight_fee]; +/// ``` +/// +/// As you can see `targeted_fee_adjustment` becomes `targeted_weight_fee_adjustment` but the behavior +/// remains the same, the side effect is the changing to the value `targeted_length_fee_adjustment`, +/// this formula is achievable because inside pallet_transaction_payment the function `compute_fee_raw` +/// that just computes the final fee associated with an extrinsic uses the associated type `LenghtToFee` +/// that converts the length of an extrinsic to a fee. +/// +/// By default the implementation is a constant multiplication but we want to achieve a dynamic formula +/// that can adapt based on the usage of the network, this can't solely be done by this struct but needs +/// to be bundled with a custom implementation of `LenghtToFee`. +/// +/// This struct ONLY provide a dynamic update of `targeted_length_fee_adjustment` and `targeted_weight_fee_adjustment` +/// based on the congestion and usage of the blocks, while the formula si effectively implemented like +/// explained above only thanks to `LenghtToFee` +pub struct BlobsFeeAdjustment(core::marker::PhantomData); + +impl Convert for BlobsFeeAdjustment +where + T: frame_system::Config, +{ + /// This function should be a pure function used to update NextFeeMultiplier + /// but will also has the side effect of update NextLenghtMultiplier + fn convert(previous_fee_multiplier: Multiplier) -> Multiplier { + // Update TargetBlockSize if needed + let all_extrinsic_len = >::all_extrinsics_len(); + + if all_extrinsic_len > TargetBlockSize::get() { + // Increase the tracker if needed + let tracker = BlockSizeTracker::get(); + + // If the used_block_size is larger than the TargetBlockSize + // for more than IncreaseDeltaBlocks consecutive, then the TargetBlockSize + // will be increased by DeltaTargetBlockSize. + if tracker + 1 >= IncreaseDeltaBlocks::get() { + let current_target_block_size = TargetBlockSize::get(); + TargetBlockSize::set(&(current_target_block_size + DeltaTargetBlockSize::get())); + BlockSizeTracker::set(&0); + } + + BlockSizeTracker::set(&(tracker + 1)); + } else { + // The logic for updating to a new TargetBlockSize currently requires IncreaseDeltaBlocks + // to be constantly larger than TargetBlockSize. However, it might be possible + // to implement a window where we reset the tracker only if the + // block size remains below the TargetBlockSize for a certain number of blocks + BlockSizeTracker::set(&0); + } + + // Update NextLengthMultiplier + + // To update the value will be used the same formula as TargetedFeeAdjustment, + // described here: https://research.web3.foundation/Polkadot/overview/token-economics#2-slow-adjusting-mechanism + // + // so this is mainly a copy paste of that function because it works on normalized mesurments, + // so if it is ref_time, proof_size or lenght of the extrinsic the mutliplier will be evaluated properly. + // The main problem is that TargetedFeeAdjustment::convert uses directly a call to the storage to extract + // the weight of the current block so there is no way to pass the lenght as input argument, + // here I will copy paste all the needed part to update properly NextLengthMultiplier + + // Defensive only. The multiplier in storage should always be at most positive. Nonetheless + // we recover here in case of errors, because any value below this would be stale and can + // never change. + + let previous_len_multiplier = NextLengthMultiplier::get(); + let min_multiplier = MinimumMultiplierBlockSize::get(); + let max_multiplier = MaximumMultiplierBlockSize::get(); + // TODO: why? + let previous_len_multiplier = previous_len_multiplier.max(min_multiplier); + + // Pick the limiting dimension. (from TargetedFeeAdjustment::convert) + // + // In this case it is the length of all extrinsic, always + let (normal_limiting_dimension, max_limiting_dimension) = + (all_extrinsic_len, MAXIMUM_BLOCK_LENGTH); + + let target_block_size = TargetBlockSize::get(); + let adjustment_variable = AdjustmentVariableBlockSize::get(); + + let target_size = (target_block_size * max_limiting_dimension) as u128; + let block_size = normal_limiting_dimension as u128; + + // determines if the first_term is positive + let positive = block_size >= target_size; + let diff_abs = block_size.max(target_size) - block_size.min(target_size); + + // defensive only, a test case assures that the maximum weight diff can fit in Multiplier + // without any saturation. + let diff = Multiplier::saturating_from_rational(diff_abs, max_limiting_dimension.max(1)); + let diff_squared = diff.saturating_mul(diff); + + let v_squared_2 = adjustment_variable.saturating_mul(adjustment_variable) + / Multiplier::saturating_from_integer(2); + + let first_term = adjustment_variable.saturating_mul(diff); + let second_term = v_squared_2.saturating_mul(diff_squared); + + let new_len_multiplier = if positive { + let excess = first_term + .saturating_add(second_term) + .saturating_mul(previous_len_multiplier); + previous_len_multiplier + .saturating_add(excess) + .clamp(min_multiplier, max_multiplier) + } else { + // Defensive-only: first_term > second_term. Safe subtraction. + let negative = first_term + .saturating_sub(second_term) + .saturating_mul(previous_len_multiplier); + previous_len_multiplier + .saturating_sub(negative) + .clamp(min_multiplier, max_multiplier) + }; + + NextLengthMultiplier::set(&new_len_multiplier); + + // Update NextFeeMultiplier + // + // Here is the tricky part, this method return the new value associated with + // NextFeeMultiplier (in the old fashion) because weight dynamic adjustment is battle tested + // while previously have updated the `NextLengthMultiplier` used in `LenghtToWeight` + TargetedFeeAdjustment::< + T, + TargetBlockFullness, + AdjustmentVariableBlockFullness, + MinimumMultiplierBlockFullness, + MaximumMultiplierBlockFullness, + >::convert(previous_fee_multiplier) + } +} + +impl MultiplierUpdate for BlobsFeeAdjustment { + fn min() -> Multiplier { + MinimumMultiplierBlockFullness::get() + } + fn max() -> Multiplier { + MaximumMultiplierBlockFullness::get() + } + fn target() -> Perquintill { + TargetBlockFullness::get() + } + fn variability() -> Multiplier { + AdjustmentVariableBlockFullness::get() + } +} + +pub struct BlobsLengthToFee(core::marker::PhantomData); + +impl sp_weights::WeightToFee for BlobsLengthToFee { + type Balance = Balance; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + // really weird but weght.ref_time will contain the length of the extrinsic + let length_fee = Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(TransactionByteFee::get()); + let multiplier = NextLengthMultiplier::get(); + + // final adjusted length fee + multiplier.saturating_mul_int(length_fee) + } } impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; type WeightToFee = WeightToFee; - type LengthToFee = ConstantMultiplier; - type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; + //type LengthToFee = ConstantMultiplier; + type LengthToFee = BlobsLengthToFee; + //type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; + type FeeMultiplierUpdate = BlobsFeeAdjustment; type OperationalFeeMultiplier = ConstU8<5>; } diff --git a/sugondat-chain/runtimes/sugondat-kusama/tests/integration_test.rs b/sugondat-chain/runtimes/sugondat-kusama/tests/integration_test.rs index 7da6fb2f..6be508c0 100644 --- a/sugondat-chain/runtimes/sugondat-kusama/tests/integration_test.rs +++ b/sugondat-chain/runtimes/sugondat-kusama/tests/integration_test.rs @@ -1,17 +1,22 @@ -use frame_support::traits::tokens::Precision; +use frame_support::{ + dispatch::GetDispatchInfo, + traits::{fungible::Balanced, tokens::Precision, Hooks}, +}; +use pallet_transaction_payment::Multiplier; use sp_block_builder::runtime_decl_for_block_builder::BlockBuilderV6; use sp_runtime::{ generic::SignedPayload, + traits::Saturating, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, }, - BuildStorage, MultiSignature, + BuildStorage, FixedPointNumber, MultiSignature, }; use codec::Encode; -use frame_support::traits::fungible::Balanced; use sp_core::{crypto::Pair, sr25519}; use sp_transaction_pool::runtime_api::runtime_decl_for_tagged_transaction_queue::TaggedTransactionQueueV3; +use sp_weights::{Weight, WeightToFee}; use sugondat_kusama_runtime::{ Address, Hash, MaxBlobSize, MaxBlobs, MaxTotalBlobSize, Runtime, RuntimeCall, SignedExtra, UncheckedExtrinsic, @@ -157,3 +162,73 @@ fn test_pre_dispatch_max_total_blob_size_exceeded() { pallet_sugondat_blobs::TotalBlobSize::::put(MaxTotalBlobSize::get()) }); } + +#[test] +fn test_length_to_fee() { + // Test that inclusion fee is evaluated propertly + // following what done in BlobsLengthToFee + new_test_ext().execute_with(|| { + let len = 123; + let multiplier = Multiplier::saturating_from_integer(12); + NextLengthMultiplier::set(&multiplier); + + let length_fee = len * TransactionByteFee::get(); + let expected = multiplier.saturating_mul_int(length_fee); + + assert_eq!( + pallet_transaction_payment::Pallet::::length_to_fee(len as u32), + expected + ); + }); +} + +#[test] +fn test_inclusion_fee() { + // Test that inclusion fee is evaluated propertly + // following what done in BlobsLengthToFee + new_test_ext().execute_with(|| { + let call: RuntimeCall = pallet_sugondat_blobs::Call::submit_blob { + namespace_id: 0.into(), + blob: vec![0; 1], + } + .into(); + + NextLengthMultiplier::set(&Multiplier::saturating_from_rational(1, 12)); + + let inclusion_fee_zero_length = pallet_transaction_payment::Pallet::::compute_fee( + 0, + &call.get_dispatch_info(), + 0, + ); + + let inclusion_fee = pallet_transaction_payment::Pallet::::compute_fee( + call.size_hint() as u32, + &call.get_dispatch_info(), + 0, + ); + + let length_fee = inclusion_fee - inclusion_fee_zero_length; + + let expected_lenght_fee = BlobsLengthToFee::::weight_to_fee(&Weight::from_parts( + call.size_hint() as u64, + 0, + )); + + assert_eq!(length_fee, expected_lenght_fee); + }); +} + +#[test] +fn test_update_length_and_fee_multipliers() { + // TODO: check the correctness of the update + // + // Here I just check that the desired BlobsFeeAdjustment side effect + // happens, not yet the correctness + new_test_ext().execute_with(|| { + let multiplier = Multiplier::saturating_from_rational(1, 12); + NextLengthMultiplier::set(&multiplier); + TransactionPayment::on_finalize(System::block_number()); + let new_mutliplier = NextLengthMultiplier::get(); + assert!(multiplier != multiplier); + }); +}