Transaction Fee

Overview

When a block author constructs a block, it must limit the block's execution time. A block body consists of a series of extrinsics. To be economically sustainable and to limit spam, some transactions --- primarily those dispatched by users --- require a fee prior to transaction execution

Description

Fee Calculation

inclusion_fee = base_fee + length_fee + [targeted_fee_adjustment * weight_fee] final_fee = inclusion_fee + tip
  • The base_fee derived from the base weight covers inclusion overhead like signature verification. base_fee = 1250000000000 (wei)
  • The length_fee is set to length _fee per byte multiplied by the length of the encoded extrinsic. In which: length_fee per byte= 10^12 (Dimension: wei/byte)
  • The targeted_fee_adjustment is a multiplier that can tune the final fee based on the congestion of the network. A targeted adjustment is implemented to define a target saturation level of block weight. If the previous block is more saturated, then the fees are slightly increased. Similarly, if the previous block has fewer transactions than the target, fees are decreased by a small amount.
→ targeted_fee_adjusment= 1
  • The weight_fee is calculated using two parameters:
    The ExtrinsicBaseWeight that is declared in the runtime and applies to all extrinsics.
    The #[pallet::weight] annotation that accounts for an extrinsic's complexity.
→ weight_fee = 10^4 * weight tx (Dimension: wei)

Additional Fees

Inclusion fees must be computable prior to execution, and therefore can only represent fixed logic. Some transactions uses other strategies to warrant limiting resources. For example,
Bonds: A bond is a type of fee that will either be returned or slashed after some on-chain event. For example, runtime developers may want to implement a bond in order to participate in a vote; in this example the bond could be returned at the end of the referendum or slashed if the voter tried anything malicious.
Deposits: Deposits are fees that may be returned later. For example, users may be required to pay a deposit in order to execute an operation that uses storage; if a subsequent operation frees that storage, the user's deposit could be returned.
Limits: Runtime developers are free to enforce constant or configurable limits on certain operations. For example, the default Staking pallet only allows nominators to nominate 16 validators in order to limit the complexity of the validator election process.
Note that if you query the chain for a transaction fee, it will only return the inclusion fee.

Targeted_fee_adjustment

A struct to update the weight multiplier per block. It implements `Convert<Multiplier, Multiplier>`, meaning that it can convert the previous multiplier to the next one. This should be called on on_finalize of a block, prior to potentially cleaning the weight data from the system module.
given:
s = previous block weight
s'= ideal block weight
m = maximum block weight
diff = (s - s')/m v = 0.00001
t1 = (v * diff)
t2 = (v * diff)^2 / 2
then:
next_multiplier = prev_multiplier * (1 + t1 + t2)
Where (s', v) must be given as the Get implementation of the T generic type. Moreover, M must provide the minimum allowed value for the multiplier. Note that a runtime should ensure with tests that the combination of this M and V is not such that the multiplier can drop to zero and never recover.
Note that s' is interpreted as a portion in the normal transaction capacity of the block. For example, given s' == 0.25 and AvailableBlockRatio = 0.75, then the target fullness is 0.25 of the normal capacity and 0.1875 of the entire block.
This implementation implies the bound:
- v ≤ p / k * (s − s')
- or, solving for p: p >= v * k * (s - s')
where p is the amount of change over k blocks.
Hence:
- in a fully congested chain: p >= v * k * (1 - s').
- in an empty chain: p >= v * k * (-s').
For example, when all blocks are full and there are 28800 blocks per day (default in substrate-node) and v == 0.00001, s' == 0.1875, we'd have:
p >= 0.00001 * 28800 * 0.8125
p >= 0.234