Filecoin — Gas Calculation

Trapdoor-Tech
5 min readSep 26, 2020

--

Filecoin just started Space Race and miners started to show what they are capable of. There seems to be quite a bit of issue with the official release since the version upgrades from 0.5.1 to 0.5.6 in just a few days. Well, it’s been a while since I read about Go program. Lately I have reviewed some logic about Gas calculation in Filecoin and thought it would be good to share. This article is about logic from 0.5.6, the last submission from Lotus as follows:

commit 606a58bc6bc035ec0b90c6b50488e29e90f4238f
Author: Aayush Rajasekaran <arajasek94@gmail.com>
Date: Sat Aug 29 00:56:24 2020 -0400

Lotus version 0.5.6

CHANGELOG in Lotus cleaerly records changes in Gas cost model: from limit/price to limit/premium/feecap. New Gas model refers to EIP-1559: send transaction, while the transaction fee does not exceed “feecap * limit”. Miners earns the transaction fee of “premium * limit”. In other words, feecap * limit is the upper bound for Gas fee, miners earns premium * limit. (feecap-premium)*limit is going to be burned for Gas cost.

How do we set feecap? Is it correct to set limit as high as we can?

Implementation for Gas cost calculation is in method GasEstimateMessageGas from node/impl/full/gas.go. Next let us take a close look at Base/Limit/Premium/FeeCap.

1. Base Fee

Every block has a baseFee. All transactions from that block burns corresponding baseFee. Note that even though “baseFee” has fee in its name, but it actually refers to a unit price, the actual fee for burning is baseFee*limit. baseFee is defined in build/params_shared_vals.go:

const BlockGasLimit = 10_000_000_000
const BlockGasTarget = BlockGasLimit / 2
const BaseFeeMaxChangeDenom = 8 // 12.5%
const InitialBaseFee = 100e6
const MinimumBaseFee = 100
const PackingEfficiencyNum = 4
const PackingEfficiencyDenom = 5

In initial block, baseFee is set as InitialBaseFee (10⁸). When generating next block from current block, next baseFee is depending on limit of current block. For details please check functions ComputeBaseFee and computeNextBaseFee from chain/store/basefee.go.

  • The minimum baseFee — MinimumBaseFee (100)
  • Block Gas Limit — Sum of Gas Limit from all transactions in the block. It gets 10% off when calculating baseFee (PackingEfficiencyNum/PackingEfficiencyDenom)
  • The “surplus” of Gas Limit — each block defines size for Gas Limit target — BlockGasTarget. The part that exceeds the BlockGasTarget is surplus. Notice that this surplus can be both positive and negative.
  • Update baseFee — baseFee of next block is adding 12.5% of the surplus (BaseFeeMaxChangeDenom) based on currect block baseFee. Here is how it’s calculated:
change := big.Mul(baseFee, big.NewInt(delta))
change = big.Div(change, big.NewInt(build.BlockGasTarget))
change = big.Div(change, big.NewInt(build.BaseFeeMaxChangeDenom))

In short, when current block Gas Limit exceeds BlockGasTarget, base Fee increases by 12.5% of the surplus. Following this logic you will see base Fee rises and drops rapidly when there are many transactions going on.

See latest 24 hours base Fee change in Firefox browser (https://filfox.info/zh).

2. GasLimit

GasLimit is the amount of gas willing to be consumed during execution of a transaction. GasLimit is usually fixed for a transaction. Refer to function GasEstimateGasLimit for its calculation. When a transaction needs to get its Gas Limit, it needs to “call” at current block level:

res, err := a.Stmgr.CallWithGas(ctx, &msg, priorMsgs, ts)

CallWithGas only gets the Gas consumed during execution, and it does not really change current state.

3. GasPremium

Gas Premium is the price of gas willing to be paid for during execution of a transaction. Gas fee equals to amount * price. GasPremium is usually fixed for a transaction as well. Obviously, higher premium leads to higher Gas fee, which results in higher income for miners and higher priority for them to pack the transaction. From the senders’ perspective, on the contrary, low premium is preferred. Lotus offers a way to calculate Gas Premiumin GasEstimateGasPremium function. It can break down into several steps:

  • Check all transactions in previous blocks (4 = 2*2) and sort Gas Premium from high to low
  • Calculate the “average” Gas Premium for all transactions. Average means to find out Gas Premium for half of the GasLimit:
at := build.BlockGasTarget * int64(blocks) / 2
prev1, prev2 := big.Zero(), big.Zero()
for _, price := range prices {
prev1, prev2 = price.price, prev1
at -= price.limit
if at > 0 {
continue
}
}
  • Add 0.005 randomness:
// mean 1, stddev 0.005 => 95% within +-1%
noise := 1 + rand.NormFloat64()*0.005
premium = types.BigMul(premium, types.NewInt(uint64(noise*(1<<precision))+1))
premium = types.BigDiv(premium, types.NewInt(1<<precision))

4. GasFeeCap

Besides Gas Premium, Base Fee is also required for a transaction. That is to say, a transaction fee normally includes (Gas Premium + Base Fee) * Gas Limit. There is an issue that Base Fee is a variable, and it could be too high for the sender to be willingly pay for. To resolve such issue there comes Gas Fee Cap (upper), the upper bound for Base Fee. See functioni GasEstimateFeeCap for more details.

  • It is very likely for a transaction not to be packed immediatly in the following block. Thus, it requires us to consider the change of Base Fee in next multiple blocks (10 blocks) for transaction to be packed:
parentBaseFee := ts.Blocks()[0].ParentBaseFee
increaseFactor := math.Pow(1.+1./float64(build.BaseFeeMaxChangeDenom), float64(maxqueueblks))
feeInFuture := types.BigMul(parentBaseFee, types.NewInt(uint64(increaseFactor*(1<<8))))
feeInFuture = types.BigDiv(feeInFuture, types.NewInt(1<<8))

Base Fee increases by 12.5% in each block.

Take 1% of current account balance as the upper bound for transaction fee:

maxAccepted := types.BigDiv(act.Balance, types.NewInt(MaxSpendOnFeeDenom))
  • The min value of above two situations is counted as Gas Fee Cap

5. GasLimit sets Penalty

It is well known that Ethereum allows Gas Limit to be configurd as a very large value. Normally the remainder of Gas fee will be returned, but note that it is not exaclly same case here in Filecoin. Since Gas Limit is involved in Base Fee and Gas Premium calculation, a valid realtime Gas Limit is required. If a transaction has been set an unreasonable Gas Limit, it triggers a punishment mechanism in Filecoin. The Overuse Gas will burn as well. Refer to function ComputeGasOverestimationBurn for implementation details.

  • Calculate the Overuse Gas while error is allowed in a specific range
const (
gasOveruseNum = 11
gasOveruseDenom = 10
)
over := gasLimit - (gasOveruseNum*gasUsed)/gasOveruseDenom

1.1 times of gasUsed is considered reasonable:

if over > gasUsed {
over = gasUsed
}

The upper bound for Overuse is gasUsed.

  • Set panelty according to the over/gasUsed ratio:
gasToBurn := big.NewInt(gasLimit - gasUsed)
gasToBurn = big.Mul(gasToBurn, big.NewInt(over))
gasToBurn = big.Div(gasToBurn, big.NewInt(gasUsed))

Panelty is the result of over/gasUsed. If value of overn exceeds gasUsed, everything will be taken as penalty.

Summary:

In Filecoin, Gas model uses the concept of BaseFee to adjust the congestion of transactions. In congested blocks or where not enough transactions happening, the adjustment will take place at 12.5% rate. Formula for calculating transaction fee: (Gas Premium + Base Fee) * Gas Limit. BaseFee will burn and Gas Premium as miners’ commission charge. Pay special attention to not setting GasLimit too high, or the over Gas Limit will burn.

--

--

Trapdoor-Tech
Trapdoor-Tech

Written by Trapdoor-Tech

Trapdoor-Tech tries to connect the world with zero-knowledge proof technologies. zk-SNARK/STARK solution and proving acceleration are our first small steps :)

No responses yet