Skip to content

Commit

Permalink
runtime-sdk: support dynamic min-gas-price
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrus committed Sep 27, 2023
1 parent 9e8c6e9 commit bb902bc
Showing 1 changed file with 129 additions and 15 deletions.
144 changes: 129 additions & 15 deletions runtime-sdk/src/modules/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ use crate::{
sender::SenderMeta,
storage::{self, current::TransactionResult, CurrentStore},
types::{
token,
token::{self, Denomination},
transaction::{
self, AddressSpec, AuthProof, Call, CallFormat, CallerAddress, UnverifiedTransaction,
},
},
Runtime,
};

use self::types::RuntimeInfoResponse;
use self::{state::DYNAMIC_MIN_GAS_PRICE, types::RuntimeInfoResponse};

#[cfg(test)]
mod test;
Expand Down Expand Up @@ -230,6 +230,33 @@ pub struct GasCosts {
pub callformat_x25519_deoxysii: u64,
}

/// Dynamic min gas price parameters.
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
pub struct DynamicMinGasPrice {
pub enabled: bool,

/// Target block gas usage indicates the desired block gas usage as a percentage of the total
/// block gas limit.
///
/// The min gas price will adjust up or down depending on whether the actual gas usage is above
/// or below this target.
pub target_block_gas_usage_percentage: u8,
/// Represents a constant value used to limit the rate at which the min price can change
/// between blocks.
///
/// For example, if `min_price_max_change_denominator` is set to 8, the maximum change in
/// min price is 12.5% between blocks.
pub min_price_max_change_denominator: u8,
}

/// Errors emitted during core parameter validation.
#[derive(Error, Debug)]
pub enum ParameterValidationError {
#[error("invalid dynamic target block gas usage percentage (<= 100)")]
InvalidTargetBlockGasUsagePercentage,
#[error("invalid min price max change denominator (<= 50)")]
InvalidMinPriceMaxChangeDenominator,
}
/// Parameters for the core module.
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
pub struct Parameters {
Expand All @@ -239,10 +266,25 @@ pub struct Parameters {
pub max_multisig_signers: u32,
pub gas_costs: GasCosts,
pub min_gas_price: BTreeMap<token::Denomination, u128>,
pub dynamic_min_gas_price: DynamicMinGasPrice,
}

impl module::Parameters for Parameters {
type Error = std::convert::Infallible;
type Error = ParameterValidationError;

fn validate_basic(&self) -> Result<(), Self::Error> {
// Validate dynamic min gas price parameters.
let dmgp = &self.dynamic_min_gas_price;
if dmgp.enabled {
if dmgp.target_block_gas_usage_percentage > 100 {
return Err(ParameterValidationError::InvalidTargetBlockGasUsagePercentage);
}
if dmgp.min_price_max_change_denominator > 50 {
return Err(ParameterValidationError::InvalidMinPriceMaxChangeDenominator);
}
}
Ok(())
}
}

pub trait API {
Expand All @@ -254,6 +296,8 @@ pub trait API {
/// increased.
fn use_batch_gas<C: Context>(ctx: &mut C, gas: u64) -> Result<(), Error>;

fn batch_gas_used<C: Context>(ctx: &mut C) -> u64;

/// Attempt to use gas. If the gas specified would cause either total used to exceed
/// its limit, fails with Error::OutOfGas or Error::BatchOutOfGas, and neither gas usage is
/// increased.
Expand All @@ -272,7 +316,7 @@ pub trait API {
fn max_batch_gas<C: Context>(ctx: &mut C) -> u64;

/// Configured minimum gas price.
fn min_gas_price<C: Context>(ctx: &mut C, denom: &token::Denomination) -> u128;
fn min_gas_price<C: Context>(ctx: &C, denom: &token::Denomination) -> Option<u128>;

/// Sets the transaction priority to the provided amount.
fn set_priority<C: Context>(ctx: &mut C, priority: u64);
Expand Down Expand Up @@ -331,6 +375,8 @@ pub mod state {
pub const MESSAGE_HANDLERS: &[u8] = &[0x02];
/// Last processed epoch for detecting epoch changes.
pub const LAST_EPOCH: &[u8] = &[0x03];
/// Dynamic min gas price.
pub const DYNAMIC_MIN_GAS_PRICE: &[u8] = &[0x04];
}

/// Module configuration.
Expand Down Expand Up @@ -400,6 +446,13 @@ impl<Cfg: Config> API for Module<Cfg> {
Ok(())
}

fn batch_gas_used<C: Context>(ctx: &mut C) -> u64 {
ctx.value::<u64>(CONTEXT_KEY_GAS_USED)
.get()
.cloned()
.unwrap_or_default()
}

fn use_tx_gas<C: TxContext>(ctx: &mut C, gas: u64) -> Result<(), Error> {
let gas_limit = ctx.tx_auth_info().fee.gas;
let gas_used = ctx.tx_value::<u64>(CONTEXT_KEY_GAS_USED).or_default();
Expand Down Expand Up @@ -441,12 +494,8 @@ impl<Cfg: Config> API for Module<Cfg> {
Self::params().max_batch_gas
}

fn min_gas_price<C: Context>(_ctx: &mut C, denom: &token::Denomination) -> u128 {
Self::params()
.min_gas_price
.get(denom)
.copied()
.unwrap_or_default()
fn min_gas_price<C: Context>(ctx: &C, denom: &token::Denomination) -> Option<u128> {
Self::min_gas_prices(ctx).get(denom).copied()
}

fn set_priority<C: Context>(ctx: &mut C, priority: u64) {
Expand Down Expand Up @@ -774,10 +823,9 @@ impl<Cfg: Config> Module<Cfg> {
ctx: &mut C,
_args: (),
) -> Result<BTreeMap<token::Denomination, u128>, Error> {
let params = Self::params();
let mut mgp = Self::min_gas_prices(ctx);

// Generate a combined view with local overrides.
let mut mgp = params.min_gas_price;
for (denom, price) in mgp.iter_mut() {
let local_mgp = Self::get_local_min_gas_price(ctx, denom);
if local_mgp > *price {
Expand Down Expand Up @@ -862,6 +910,21 @@ impl<Cfg: Config> Module<Cfg> {
}

impl<Cfg: Config> Module<Cfg> {
fn min_gas_prices<C: Context>(_ctx: &C) -> BTreeMap<Denomination, u128> {
let params = Self::params();
if params.dynamic_min_gas_price.enabled {
CurrentStore::with(|store| {
let store =
storage::TypedStore::new(storage::PrefixStore::new(store, &MODULE_NAME));
store
.get(DYNAMIC_MIN_GAS_PRICE)
.unwrap_or_else(|| params.min_gas_price)
})
} else {
params.min_gas_price
}
}

fn get_local_min_gas_price<C: Context>(ctx: &C, denom: &token::Denomination) -> u128 {
#[allow(clippy::borrow_interior_mutable_const)]
ctx.local_config(MODULE_NAME)
Expand All @@ -885,11 +948,10 @@ impl<Cfg: Config> Module<Cfg> {
return Ok(());
}

let params = Self::params();
let fee = ctx.tx_auth_info().fee.clone();
let denom = fee.amount.denomination();

match params.min_gas_price.get(denom) {
match Self::min_gas_price(ctx, denom) {
// If the denomination is not among the global set, reject.
None => return Err(Error::GasPriceTooLow),

Expand All @@ -904,7 +966,7 @@ impl<Cfg: Config> Module<Cfg> {
}
}

if &fee.gas_price() < min_gas_price {
if fee.gas_price() < min_gas_price {
return Err(Error::GasPriceTooLow);
}
}
Expand Down Expand Up @@ -1040,6 +1102,58 @@ impl<Cfg: Config> module::BlockHandler for Module<Cfg> {
.set(epoch != previous_epoch);
});
}

fn end_block<C: Context>(ctx: &mut C) {
let params = Self::params();
if !params.dynamic_min_gas_price.enabled {
return;
}

// Update dynamic min gas price for next block, inspired by EIP-1559.
//
// Adjust the min gas price for each block based on the gas used in the previous block and the desired target
// gas usage set by `targe_block_gas_usage_percentage`.
let gas_used = Self::batch_gas_used(ctx);
let max_batch_gas = Self::max_batch_gas(ctx);
let target_gas_used = max_batch_gas.saturating_mul(100)
/ (params
.dynamic_min_gas_price
.target_block_gas_usage_percentage as u64);

// Calculate the difference (as a fraction) between the actual gas used in the block and the target gas used.
let delta = if gas_used > target_gas_used {
(gas_used - target_gas_used) / target_gas_used
} else {
(target_gas_used - gas_used) / target_gas_used
};

// Compute new prices.
let mut mgp = Self::min_gas_prices(ctx);
mgp.iter_mut().for_each(|(_, price)| {
// Calculate the change in gas price, and divide by `min_price_max_change_denominator`.
let price_change = price.saturating_mul(delta as u128)
/ (params
.dynamic_min_gas_price
.min_price_max_change_denominator as u128);
// Calculate the new price.
let new_min_price = if gas_used > target_gas_used {
// If the actual gas used is greater than the target gas used, increase the minimum gas price.
price.saturating_add(price_change)
} else {
// If the actual gas used is less than the target gas used, decrease the minimum gas price.
price.saturating_sub(price_change)
};
*price = new_min_price;
});

// Update min prices.
CurrentStore::with(|store| {
// Load current gas price.
let mut store = storage::PrefixStore::new(store, &MODULE_NAME);
let mut tstore = storage::TypedStore::new(&mut store);
tstore.insert(state::DYNAMIC_MIN_GAS_PRICE, mgp);
});
}
}

impl<Cfg: Config> module::InvariantHandler for Module<Cfg> {}

0 comments on commit bb902bc

Please sign in to comment.