Technology

Dynamic gas fee pricing mechanism

With Klaytn v1.9.0, a dynamic gas fee mechanism will replace the existing fixed price policy. In this post, we explain the background as well as reason for this change.

TL;DR:

  • A dynamic gas price will be introduced with Klaytn v.1.9.0 hard fork:
  • Transactions from a single block will use the same baseFee to calculate transaction costs. Only the transactions with a gas fee higher than the block baseFee will be included in the block.
  • Block baseFee will automatically increase/decrease in accordance with the gas usage of the previous block with a maximum change rate of 5%
  • Block baseFee is 25 ~ 750 ston, and this range can be changed by Governance
  • Half of the transaction fee for each block is burned

–  When setting the gas fee for dApps and wallets, the following methods are recommended: 

  1. Use the return value from the API that recommends a gas price. For nodes, use  `klay_gasPrice` or `eth_gasPrice`. For caver, use the return value for `caver.rpc.klay.getGasPrice`.
  2. Use maximum gas fee. Maximum gas price can be found with `klay_upperBoundGasPrice` API. The current value is 750 ston.

Introduction

Klaytn has been guaranteeing 4,000 TPS and immediate finality. It has also maintained a low fixed gas price so that many users can benefit from using our blockchain.

But some problems started to surface with increasing cases of misuse. Some malicious actors were using the cheap gas price to overwhelm the network, causing average users great inconvenience by delaying their transactions. It also imposed an overload on the storage capacity, interfering with our commitment to provide a stable service.

In response to this issue, Klaytn Team analyzed the data from the network and came up at last with a dynamic gas price policy(KIP-71) to enable a stable service by preventing network abuse and storage overuse.

Dynamic Gas Price Policy

The cost for transactions is calculated by multiplying the gas used per transaction and base fee:

(Transaction cost) = (used gas) x (base fee)

Before Klaytn v1.9.0, the gas price was fixed as UnitPrice. After the change, the gas price will change according to the network situation.

There are seven parameters that affect the base fee.

  1. Previous base fee : Base fee of the previous block
  2. Previous gas fee : Gas used to process all transactions of the previous block
  3. Base gas : The gas amount that determines the increase/decrease of the base fee (30 million at the moment)
  4. Maximum gas : The maximum gas amount used to calculate the base fee (60 million at the moment)
  5. Adjustment value for fee range : Adjustment value for fee range of base fee (20 at the moment)
  6. Maximum base fee : The maximum value for base fee (750 ston at the moment)
  7. Minimum base fee : The minimum value for base fee (25 ston at the moment)

The basic idea of this algorithm is that the base fee would go up if the gas used exceeds the base gas, and go down if it’s lower. To prevent the fee from increasing or decreasing indefinitely, there is an upper and lower limit for the base fee. There is also a cap for the gas as well as an adjustment value for the fluctuation, to prevent abrupt changes in the base fee.

(Base fee change rate) = (Gas used for previous block – Base gas)(Adjusted base fee change rate) = (Base fee change rate) / (Base gas) / (Change rate adjustment value)(Base fee range) = (Previous base fee) * (Adjused base fee change rate)(Base fee) = (Base fee of the previous block) + (Base fee range)

The base fee is calculated for every block, there could be changes every second. With more gas used, the base fee would go up and vice versa. This is closely related to the number of transactions in the network and the gas used in the process.

(Example) Base gas : 100, Adjustment value : 20

Block number100101102
Gas used200300500
Base fee100105115

If block number 100 used 200 gas and the base fee is 100, the base fee for block number 101 can be calculated as shown below:

Base fee change rate = 200 - 100 = 100

Adjusted base fee change rate = 100 / 100 / 20 = 0.05 = 5%

Base fee change rate = 100 * 5% = 5

Base fee of block number 101 = 100 + 5 = 105

Following the same steps, the base fee for block number 102 is 115.

Important changes

Hard fork : With the introduction of the dynamic gas fee, a hard fork on the Klaytn network will take effect. The hard fork involves a change in block header data, as `BaseFee` field has been added to the block header. This change may not come across as so prominent as the existing APIs for parsing block headers already had a BaseFeePerGas field, but whereas this field always was always 0 by default, now it will hold an actual value.

Parameter change through the governance : You can change the parameters for calculating the base fee by governance votes. You can change the maximum and minimum base fee and thus the range, which will enable a flexible handling of issues that arise while operating a mainnet.

Transaction pool : The transaction pool, where transactions are stored temporarily, will also be changed. For example, transactions with a gas fee lower than the current base fee will be rejected by the network.

To maintain usability, Klaytn team recommends setting your gas fee as (base fee) x 2. The base fee for the latest block can be found using this API.

With the current algorithm, it takes about 14-15 blocks (14-15 seconds) for the current base fee to double, assuming that the network is under maximum capacity. This means that when you set the gas fee as recommended, the transaction will stay valid until the 14th block.

Another way is to set the maximum gas fee. If you set the gas price as the maximum base fee, you won’t have to retrieve the current base fee every time you are sending a transaction. Excess fees will be refunded to you.

NOTE : An important feature that sets Klaytn apart from Ethereum’s EIP-1559 is that it doesn’t have tips. To speed up transactions on Ethereum, you have to set a high tip (priority fee), causing cheaper transactions to be delayed. But Klaytn doesn’t have tips and follow the First Come First Served (FCFS) principle for its transactions.

Klaytn SDK v1.9.0

With the introduction of KIP-71 (Dynamic Gas Fee Pricing Mechanism) in Klaytn’s v1.9.0, Klaytn SDK (caver-js, caver-java) v1.9.0 has also been upgraded to support the dynamic gas fee mechanism.

Gas Price Setting Logic

When using Klaytn SDK, undefined `gasPrice` fields are filled in automatically when using the sign function, or using keystore by sending the transaction object to the node. Starting with Klaytn SDK v1.9.0, this feature has changed to accommodate the new Dynamic Gas Fee Pricing Mechanism. The `tx.suggestGasPrice` function, which returns the recommended gas price to the transaction methods, is also available.

After the Magma hard fork with the KIP-71, you need to set the `gasPrice` to `baseFee` x 2.

For the  `TxTypeEthereumDynamicFee` transaction, you have to use `baseFeePerGas` x 2 for the `maxFeePerGas` field, which is the same as the recommended value in the above paragraph. For `maxPriorityFeePerGas`, you can use the value returned from `caver.rpc.klay.getMaxPriorityFeePerGas`.

`caver.rpc.klay.getGasPrice` API returns the gas price proposed by the Magma hard fork. For versions prior to the Magma hard fork, it returns the fixed unit price, and after the Magma hard fork, it returns `baseFee` x 2. Klaytn SDK uses `caver.rpc.klay.getGasPrice` to get the value for `gasPrice` (or `maxFeePerGas`).

You can identify the hard fork by looking at whether the `baseFeePerGas` in the block header is larger than 0. If the return object for `caver.rpc.klay.getHeader` doesn’t have the `baseFeePerGas` field or is 0, it means that the Magma hard fork has not been applied yet. On the other hand, if the `baseFeePerGas` is larger than 0, the Magma hard fork has been applied.

If you didn’t define the gasPrice of the transaction for Klaytn SDK’s signing functions, you can use it without making any changes.

// caver-js: Using sign function without defining gasPrice
to: to.address, value: caver.utils.convertToPeb(1, 'KLAY'), gas: 30000 })
await caver.wallet.sign(keyring.address, tx) // Calling fillTransaction
// caver-java: Using sign function without defining gasPrice
ValueTransfer tx = caver.transaction.valueTransfer.create(
TxPropertyBuilder.valueTransfer()
.setFrom(keyring.getAddress())
.setTo(to.getAddress())
.setValue(caver.utils.convertToPeb(1, Utils.KlayUnit.KLAY))
.setGas(BigInteger.valueOf(30000))
);
caver.wallet.sign(keyring.getAddress(), tx); // Calling fillTransaction

If the return value for `caver.rpc.klay.getGasPrice` is assigned to the gasPrice field, you can use it without making any changes.

// caver-js: Creating a transaction using caver.rpc.klay.getGasPrice (Unusable transaction after the Magma hard fork)
const gasPrice = await caver.rpc.klay.getGasPrice('latest')
const tx = caver.transaction.valueTransfer.create({ from: keyring.address, to: to.address, value: caver.utils.convertToPeb(1, 'KLAY'), gas: 30000, gasPrice })
// caver-java: Creating a transaction using caver.rpc.klay.getGasPrice (Unusable transaction after the Magma hard fork)
BigInteger gasPrice = caver.rpc.klay.getGasPrice().send().getValue();
ValueTransfer tx = caver.transaction.valueTransfer.create(
    TxPropertyBuilder.valueTransfer()
        .setFrom(keyring.getAddress())
        .setTo(to.getAddress())
        .setValue(caver.utils.convertToPeb(1, Utils.KlayUnit.KLAY))
        .setGas(BigInteger.valueOf(30000))
        .setGasPrice(gasPrice)
    );

If you define the fixed gasPrice, you have to leave it empty or change the code to set the appropriate gasPrice according to the Dynamic Gas Fee Pricing Mechanism.

// caver-js: Creating a transaction using a fixed gasPrice

const tx = caver.transaction.valueTransfer.create({ from: keyring.address, to: to.address, value: caver.utils.convertToPeb(1, 'KLAY'), gas: 30000, gasPrice: caver.utils.convertToPeb(250, 'ston') })
// caver-java: Creating a transaction using a fixed gasPrice

ValueTransfer tx = caver.transaction.valueTransfer.create(
    TxPropertyBuilder.valueTransfer()
        .setFrom(keyring.getAddress())
        .setTo(to.getAddress())
        .setValue(caver.utils.convertToPeb(1, Utils.KlayUnit.KLAY))
        .setGas(BigInteger.valueOf(30000))
        .setGasPrice(caver.utils.convertToPeb(250, Utils.KlayUnit.ston))
    );

Below is an example that uses the `tx.suggestGasPrice` function which returns the appropriate gasPrice in accordance with the dynamic gas price mechanism. `suggestGasPrice` calls the `caver.rpc.klay.getGasPrice` API.

// caver-js: Using suggestGasPrice
const suggestGasPrice = await tx.suggestGasPrice()
tx.gasPrice = suggestGasPrice
// caver-java: Using suggestGasPrice
String suggestGasPrice = tx.suggestGasPrice();
tx.setGasPrice(suggestGasPrice);

The above examples showed transactions that used the `gasPrice` field. If you are using `TxTypeEthereumDynamicFee`, you can either assign the unit price * 2 before the Magma hard fork and baseFee * 2 after the Magma hard fork, which is recommended for `maxFeePerGas`.

Newly Added `effectiveGasPrice` field in transaction receipt

With the new KIP-71, the gasPrice defined by the user has become different from the actual gasPrice (baseFee in the block header) used for the transactions. The `effectiveGasPrice` field, which is the actual gasPrice used for the transaction, has been added to the return object for `caver.rpc.klay.getTransactionReceipt`.

So the fee paid by the initiator of the transaction (or the fee payer in case of fee delegation) must be calculated using `effectiveGasPrice` and not `gasPrice`, so that it looks like the `tx.gasUsed * tx.effectiveGasPrice`.

Below is an example on how to find `effectiveGasPrice` in Klaytn SDK.

// caver-js: Finding effectiveGasPrice in receipt
const receipt = await caver.rpc.klay.getTransactionReceipt('0x{transaction hash}')
const effectiveGasPrice = receipt.effectiveGasPrice
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);
TransactionReceipt.TransactionReceiptData receipt = receiptProcessor.waitForTransactionReceipt("0x{transaction hash}");
String effectiveGasPrice = receipt.getEffectiveGasPrice();

In this article, we explained the important changes happening with the Klaytn SDK v1.9.0 and the Magma hard fork. We hope this helps you use Klaytn’s KIP-71 with more ease.