Skip to content

Blobs Fees Adjustments #149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions sugondat-chain/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use sp_runtime::{
MultiSignature,
};

// Maximum Length of the Block in bytes
pub const MAXIMUM_BLOCK_LENGTH: u32 = 5 * 1024 * 1024;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs docs (btw, this is 5* 1024 * 1024 because it's the maximum allowed PoV size).

Copy link
Contributor Author

@gabriele-0201 gabriele-0201 Dec 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, RuntimeBlockLength was defined like this:

pub const RuntimeBlockLength: BlockLength = BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);

I decided to make the value a constant because I needed it inside the BlobsLengthToFee logic. I will add docs to describe what the constant represents.

I'm not sure why 5 * 1024 * 1024 was used as the maximum block size. I have an idea, but I'm not entirely sure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What size is used in Substrate as the default maximum block size? I am 80% sure it's 4 MiB.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both in substrate node template and cumulus node template is used 5 * 1024 * 1024 as BlockLength


/// An index to a block.
pub type BlockNumber = u32;

Expand Down
4 changes: 3 additions & 1 deletion sugondat-chain/runtimes/sugondat-kusama/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -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]
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions sugondat-chain/runtimes/sugondat-kusama/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
202 changes: 194 additions & 8 deletions sugondat-chain/runtimes/sugondat-kusama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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,
};
Expand All @@ -45,16 +49,19 @@ 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};

#[cfg(any(feature = "std", test))]
pub use sp_runtime::BuildStorage;

// Polkadot imports
use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate};
use polkadot_runtime_common::BlockHashCount;

use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight};

Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -267,14 +274,193 @@ impl pallet_balances::Config for Runtime {
parameter_types! {
/// Relay Chain `TransactionByteFee` / 10
pub const TransactionByteFee: Balance = MILLICENTS;

// parameters used by BlobsFeeAdjustment
// to update NextFeeMultiplier and NextLengthMultiplier
//
// 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();


pub storage NextLengthMultiplier: Multiplier = Multiplier::saturating_from_integer(1);
pub storage TargetBlockSize: Perquintill = Perquintill::from_percent(16); // 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();
}

/// Currently pallet_transaction_payment use the following formula:
///
/// ```ignore
/// 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:
///
/// ```ignore
/// 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 `LengthToFee`
/// 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 `LengthToFee`.
///
/// 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 `LengthToFee`
pub struct BlobsFeeAdjustment<T: frame_system::Config>(core::marker::PhantomData<T>);

impl<T: frame_system::Config> Convert<Multiplier, Multiplier> for BlobsFeeAdjustment<T>
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 NextLengthMultiplier
fn convert(previous_fee_multiplier: Multiplier) -> Multiplier {
// 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 length 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 length 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();
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) = (
<frame_system::Pallet<T>>::all_extrinsics_len(),
MAXIMUM_BLOCK_LENGTH as u64,
);

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 `LengthToWeight`
TargetedFeeAdjustment::<
T,
TargetBlockFullness,
AdjustmentVariableBlockFullness,
MinimumMultiplierBlockFullness,
MaximumMultiplierBlockFullness,
>::convert(previous_fee_multiplier)
}
}

impl<T: frame_system::Config> MultiplierUpdate for BlobsFeeAdjustment<T> {
fn min() -> Multiplier {
MinimumMultiplierBlockFullness::get()
}
fn max() -> Multiplier {
MaximumMultiplierBlockFullness::get()
}
fn target() -> Perquintill {
TargetBlockFullness::get()
}
fn variability() -> Multiplier {
AdjustmentVariableBlockFullness::get()
}
}

pub struct BlobsLengthToFee<T: frame_system::Config>(core::marker::PhantomData<T>);

impl<T: frame_system::Config> sp_weights::WeightToFee for BlobsLengthToFee<T> {
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<Balances, ()>;
type WeightToFee = WeightToFee;
type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
type FeeMultiplierUpdate = SlowAdjustingFeeUpdate<Self>;
type LengthToFee = BlobsLengthToFee<Self>;
type FeeMultiplierUpdate = BlobsFeeAdjustment<Self>;
type OperationalFeeMultiplier = ConstU8<5>;
}

Expand Down
Loading