KIP 82: A new GC reward structure due to abolition of the Gini coefficient
Author | Yeri, Daniel, Aidan, Ollie, Sam, Uno, Eddie |
---|---|
Status | Draft |
Type | Standards Track |
Category | Core |
Created | 2022-09-21 |
Simple Summary
A renewal of the Governance Council(GC) reward structure due to abolition of the Gini coefficient.
Abstract
In addition to traditional enterprises, Klaytn is expanding the Governance Council (GC) by bringing DAOs in response to the growth of nontraditional entities. Such a change in the GC ecosystem will restructure the entire Klaytn governance structure. Abolishing the current block proposal selection method based on the Gini coefficient and staking quantity, the new reward program alleviates the problem of providing insufficient compensation for the network contribution and block verification due to lack of staking. The instability of certain nodes with high staking amounts can result in poor network stability as a whole. The renewed structure provides 20% of GC rewards to block proposers and the rest to the stakers. This form is to reward the participants for providing network stability with node operation and financial contribution.
Motivation
Klaytn has been compensating GC with block proposal rewards based on the quantity of staking and the Gini coefficient. This included rewards incurred by minting and gas fee generated by transactions. As mentioned in the Klaytn 2.0 Lightpaper, Klaytn is abolishing the Gini coefficient since this measure demotivates members from staking. Discontinuation of the Gini coefficient increases the risk to network stability due to the expanding reliance on certain nodes. This change ultimately calls for the revision of block proposer selection and reward structure. In an effort to enhance individual voting power and promote ultimate decentralization, we aim to accomplish the following changes:
- Ameliorate the risk that block creation depends on larger staker nodes
- Associate GC reward with the growth of the Klaytn ecosystem
Specification
Block generation is rewarded for the node operations that are serving as public goods in the Klaytn network. CNs and PNs are in charge of block proposal and propagation while ENs are in charge of blockchain data inquiry based on an individual’s needs. The infrastructure cost remains similar, enabling some fixed benefits. Therefore, fixed compensation will be equivalent to the sum of the transaction fee and an additional block compensation, which can be both inflationary and deflationary.
The block proposal method will be based on equal selection regardless of the staking amount. The current model provides a block generation reward based on staking amount and the Gini-coefficient. Until now, the Gini coefficient was utilized to alleviate the chance of an entity with more staking amount being selected as a block proposer. As this model provided a fairly equal chance to every participant, each node was promised a certain amount of rewards.
In the new method, the reward for node operation is required since the staking amount is not considered in the block proposer selection. Therefore, a certain percentage of the total reward amount will be placed as a node operation reward.
Node operators will also be compensated with the staking for enhancing network stability from an economic standpoint. The opportunity cost incurred from staking is rewarded based on inflation. In this system, inflation provides a tax-like common effect. Public users can also participate and receive rewards through the GC-run public staking program.
As of September 21st, 2022, of the 300 million KLAY that is minted annually, only 100 million KLAY goes to GC. The renewal separates GC reward into two components: block proposer reward and staking reward. The block proposer reward is compensating for node operation by providing 20% of the GC reward and 100% of gas fee resources from transactions. The staking reward is from 80% of the GC reward and is distributed based on GC staking amounts.
The relative ratio of 20% and 80% is a tentative value to be used in the Cypress mainnet. However, this ratio is defined as a governance paramter, so GC members can change the ratio through the governance process.
After the Magma hard fork, Klaytn has been burning the first half of the gas fee based on the KIP-71 proposal while the second half is distributed. The second half is distributed into GC reward, KGF and KIR.
In addition to changing the reward structure, this renewal will include burning the gas fee up to a threshold which is the size of block proposer reward. If the gas fee exceeds the threshold, remaining amount is rewarded to the block proposer. The KGF and KIR portion will be excluded from the gas fee.
Klaytn node update
The Klaytn node will be updated according to the new reward policy. The CN reward is further split into proposer reward and staking reward. The staking reward is distributed among CNs proportionally to their staking amounts minus the minimum staking amount.
A new governance parameter is introduced to tune the new reward distribution algorithm. The initial value below will be used in the Cypress mainnet when KIP-82 is first applied. The parameter can be updated using the existing governance mechanism.
Name | Type | Meaning | Initial Value |
---|---|---|---|
reward.kip82ratio |
string | The relative ratio between proposer and staking rewards | “20/80” |
During integer arithmetics, minuscule remaining amounts may be emitted as by-products. The proposer gets the remainder from the staking share disribution, and KGF gets the the remainder from the distribution among proposer, stakers, KIR, KGF.
Below pseudocode illustrates the new reward distribution algorithm. Note: //
is round down integer division.
from collections import defaultdict
MAGMA_BLOCK_NUMBER = 99841497
KORE_BLOCK_NUMBER = 120000000 # TBD
# Block header. Only relevant fields are shown here.
class Header:
Number: int = 0 # Block number
GasUsed: int = 0 # Total gas spent in the block
BaseFee: int = 0 # Base fee per gas at the block
RewardBase: string = "" # Reward recipient address of the block proposer
# Network configurations related to reward distribution.
# Corresponds to Klaytn's reward.rewardConfig struct.
class RewardConfig:
# "istanbul.policy" parameter
RoundRobin = 0
Sticky = 1
WeightedRandom = 2
ProposerPolicy: int = WeightedRandom
UnitPrice: int = 25000000000 # "governance.unitprice" parameter (in peb)
MintingAmount: int = 9600000000000000000 # "reward.mintingamount" parameter (in peb)
MinimumStake: int = 5000000 # "reward.minimumstake" parameter (in KLAY)
DeferredTxFee: bool = True # "reward.deferredtxfee" parameter
# "reward.ratio" parameter (e.g. "50/40/10")
CnRatio: int = 50
KgfRatio: int = 40
KirRatio: int = 10
TotalRatio: int = CnRatio + KgfRatio + KirRatio
# "reward.kip82ratio" parameter (e.g. "20/80") (new)
CnProposerRatio: int = 20
CnStakingRatio: int = 80
CnTotalRatio: int = CnProposerRatio + CnStakingRatio
# CN staking status and KGF/KIR addresses.
# Corresponds to Klaytn's reward.StakingInfo struct.
# Can be obtained from reward.GetStakingInfo(blockNum) function.
class StakingInfo:
KIRAddr: string = ""
KGFAddr: string = ""
Nodes: List[ConsolidatedNode] = []
# Staking information merged under the same CN.
# Sometimes a node would register multiple NodeAddrs
# in which each entry has a different StakingAddr and the same RewardAddr.
# We treat those entries with common RewardAddr as one node.
# Corresponds to Klaytn's reward.ConsolidatedNode struct.
class ConsolidatedNode:
NodeAddrs: List[string] = []
StakingAddrs: List[string] = []
RewardAddr: string = "" # The common reward address of the CN
StakingAmount: int = 0 # Total staking amount across StakingAddrs (in KLAY)
# Reward distribution details.
class RewardSpec:
Minted: int = 0 # The amount newly minted
TotalFee: int = 0 # Total tx fee spent
BurntFee: int = 0 # The amount burnt
Proposer: int = 0 # The amount allocated to the block proposer
Stakers: int = 0 # Total amount allocated to stakers
Kgf: int = 0 # The amount allocated to KGF
Kir: int = 0 # The amount allocated to KIR
Rewards: Dict[string, int] = {} # Mapping from reward recipient to amounts
# Perform post-transaction state modifications such as block rewards.
# Corresponds to Klaytn's consensus/istanbul/backend.Finalize()
#
# - state is the StateDB to apply the rewards.
# - header is a Header instance.
# - config is a RewardConfig instance.
def finalize_block(state, header, config):
if config.ProposerPolicy in [RoundRobin, Sticky]:
spec = calc_deferred_reward_simple(header, config)
else:
spec = calc_deferred_reward(header, config)
for addr, amount in spec.Rewards:
state.AddBalance(addr, amount)
header.Root = state.Root()
def calc_deferred_reward_simple(header, config):
minted = config.MintingAmount
total_fee = get_total_fee(header)
if header.Number >= KORE_BLOCK_NUMBER and not config.DeferredTxFee:
total_fee = 0
reward_fee = total_fee
burnt_fee = 0
if header.Number >= MAGMA_BLOCK_NUMBER:
burn_amount = get_burn_amount_magma(reward_fee)
reward_fee -= burn_amount
burnt_fee += burn_amount
proposer = minted + reward_fee
spec = RewardSpec()
spec.Minted = minted
spec.TotalFee = total_fee
spec.BurntFee = burnt_fee
spec.Proposer = proposer
spec.Rewards = {header.RewardBase: proposer}
return spec
# Calculates the deferred rewards, which are determined at the end of block processing.
# Used in reward distribution.
# Returns a RewardSpec.
def calc_deferred_reward(header, config):
staking_info = GetStakingInfo(header.Number)
minted = config.MintingAmount
total_fee, reward_fee, burnt_fee = calc_deferred_fee(header, config)
proposer, stakers, kgf, kir, split_rem = calc_split(header, config, minted, reward_fee)
shares, share_rem = calc_shares(config, staking_info, stakers)
# Remainder from (CN, KGF, KIR) split goes to KGF
kgf += split_rem
# Remainder from staker shares goes to Proposer
proposer += share_rem
# If KGF or KIR address is not set, proposer gets the portion.
if staking_info.KGFAddr is None:
proposer += kgf
kgf = 0
if staking_info.KIRAddr is None:
proposer += kir
kir = 0
spec = RewardSpec()
spec.Minted = minted
spec.TotalFee = total_fee
spec.BurntFee = burnt_fee
spec.Proposer = proposer
spec.Stakers = stakers
spec.Kgf = kgf
spec.Kir = kir
spec.Rewards = defaultdict(int)
spec.Rewards[header.RewardBase] += proposer
if staking_info.KGFAddr is not None:
spec.Rewards[staking_info.KGFAddr] += kgf
if staking_info.KIRAddr is not None:
spec.Rewards[staking_info.KIRAddr] += kir
for reward_addr, reward_amount in shares:
spec.Rewards[reward_addr] += reward_amount
return spec
# Returns (total_fee, reward_fee, burnt_fee)
def calc_deferred_fee(header, config):
# If not DeferredTxFee, fees are already added to the proposer during TX execution.
# Therefore, there are no fees to distribute here at the end of block processing.
# However, the fees must be compensated to calculate actual rewards paid.
if not config.DeferredTxFee:
return (0, 0, 0)
# Start with the total block gas fee
total_fee = get_total_fee(header)
reward_fee = total_fee
burnt_fee = 0
# Since Magma, burn half of gas
if header.number >= MAGMA_BLOCK_NUMBER:
burn_amount = get_burn_amount_magma(reward_fee)
reward_fee -= burn_amount
burnt_fee += burn_amount
# If KIP-82 is enabled, burn fees up to proposer's minted reward
if header.number >= KORE_BLOCK_NUMBER:
burn_amount = get_burn_amount_kip82(config, reward_fee)
reward_fee -= burn_amount
burnt_fee += burn_amount
return (total_fee, reward_fee, burnt_fee)
def get_total_fee(header):
if header.number >= MAGMA_BLOCK_NUMBER:
return header.GasUsed * header.BaseFee
else:
return header.GasUsed * config.UnitPrice
def get_burn_amount_magma(fee):
return fee / 2
def get_burn_amount_kip82(config, fee):
cn, _, _ = split_by_ratio(config, config.MintingAmount)
proposer, _ = split_by_kip82_ratio(config, cn)
if fee >= proposer:
return proposer
else:
return fee
# Returns (proposer, stakers, kgf, kir, remaining) amounts
# The sum of output must be equal to (minted + reward_fee).
def calc_split(header, config, minted, reward_fee):
total_resource = minted + reward_fee
if header.number >= KORE_BLOCK_NUMBER:
cn, kgf, kir = split_by_ratio(config, minted)
proposer, stakers = split_by_kip82_ratio(config, cn)
proposer += reward_fee
remaining = total_resource - kgf - kir - proposer - stakers
return (proposer, stakers, kgf, kir, remaining)
else:
cn, kgf, kir = split_by_ratio(config, minted + reward_fee)
remaining = total_resource - kgf - kir - cn
return (cn, 0, kgf, kir, remaining)
# Split by `ratio`. Ignore remaining amounts.
def split_by_ratio(config, source):
cn = source * config.CnRatio // config.TotalRatio
kgf = source * config.KgfRatio // config.TotalRatio
kir = source * config.KirRatio // config.TotalRatio
return (cn, kgf, kir)
# Split by `kip82ratio`. Ignore remaining amounts.
def split_by_kip82_ratio(config, source):
proposer = source * config.CnProposerRatio // config.CnTotalRatio
stakers = source * config.CnStakingRatio // config.CnTotalRatio
return (proposer, stakers)
# Distribute stake_reward among staked CNs
# Returns a mapping from each reward address to their reward shares,
# and the remaining amount.
def calc_shares(config, staking_info, stake_reward):
min_stake = config.MinimumStake
total_stakes = 0
for node in staking_info.Nodes:
if node.StakingAmount > min_stake:
total_stakes += (node.StakingAmount - min_stake)
shares = {}
remaining = stake_reward
for node in staking_info.Nodes:
if node.StakingAmount > min_stake:
effective_stake = node.StakingAmount - min_stake
reward_amount = stake_reward * effective_stake // total_stakes
remaining -= reward_amount
shares[node.RewardAddr] = reward_amount
return (shares, remaining)
The updated algorithm increases the balance of every GC member’s reward account at every block. The performance impact should be reasonable since the number of GC members is significantly smaller than Klaytn’s transaction processing capability.
Reward JSON-RPC API
A new JSON-RPC method should be added to provide historic reward distribution details.
- Name:
klay_getRewards
- Description: Returns allocation details of reward distribution at the specified block. If the parameter is not set, returns a breakdown of reward distribution at the latest block.
- Parameters
QUANTITY | TAG
- (optional) integer or hexadecimal block number, or the string “earlist” or “latest”.
- Returns
DATA
minted
: The amount mintedtotalFee
: Total tx fee spentburntFee
: The amount burntproposer
: The amount for the block proposerstakers
: Total amount for stakerskgf
: The amount for KGFkir
: The amount for KIRrewards
: A mapping from reward recipient addresses to reward amounts
- Example
// Request curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0", "method":"klay_getRewards", "params":["0x64fe7e0"],"id":1}' https://api.baobab.klaytn.net:8651 // Response { "jsonrpc":"2.0", "id":1, "result":{ "minted": "6400000000000000000", "totalFee": "1075975000000000", "burntFee": "537987500000000", "proposer": "3200268993750000000", "stakers": "0", "kgf": "2560215195000000000", "kir": "640053798750000000" "rewards": { "0xa86fd667c6a340c53cc5d796ba84dbe1f29cb2f7": "3200268993750000000", "0x2bcf9d3e4a846015e7e3152a614c684de16f37c6": "2560215195000000000", "0x716f89d9bc333286c79db4ebb05516897c8d208a": "640053798750000000" } } }
Expected Effect
The proposed GC reward mechanism is expected to produce the following changes:
- GC members increase individual staking amount.
- KLAY holders receive profit based on the staking amount of KLAY.
- Total Value Locked (TVL) of Klaytn increases.
- The total circulation reduces.
Backward Compatibility
Klaytn nodes must be upgraded before KORE_BLOCK_NUMBER
to correctly produce or verify blocks.
Reference
n/a
Copyright
Copyright and related rights waived via CC0.