Slow Misty Iguana
Medium
Some block reward tokens will be permanently locked in the module account
The HandleRewarding
function skips over the Finalized
blocks
- Some blocks did not enter the
Finalized
state whenHandleRewarding
.
The funds are locked in the module account and cannot be used.
HandleRewarding
is used to distribute reward, called when each block Endblock:
x/finality/abci.go/EndBlocker() -> HandleRewarding()
// HandleRewarding calls the reward to stakers if the block is finalized
func (k Keeper) HandleRewarding(ctx context.Context, targetHeight int64) {
......
// targetHeight = heightToExamine = HeaderInfo().Height - k.GetParams(ctx).FinalitySigTimeout
for height := nextHeightToReward; height <= uint64(targetHeight); height++ {
block, err := k.GetBlock(ctx, height)
if err != nil {
panic(err)
}
-> if !block.Finalized {
-> continue
}
k.rewardBTCStaking(ctx, height)
nextHeightToReward = height + 1
}
if nextHeightToReward != copiedNextHeightToReward {
k.SetNextHeightToReward(ctx, nextHeightToReward)
}
}
Therefore, no reward will be assigned to the Finalized
block,
The problem is that the reward for these blocks is already in the module account, and the proceeds that are not distributed will remain in the module account forever and cannot be used.
x/incentive/abci.go/BeginBlocker() -> HandleCoinsInFeeCollector():
// HandleCoinsInFeeCollector intercepts a portion of coins in fee collector, and distributes
// them to BTC staking gauge of the current height.
// It is invoked upon every `BeginBlock`.
// adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/x/distribution/keeper/allocation.go#L15-L26
func (k Keeper) HandleCoinsInFeeCollector(ctx context.Context) {
params := k.GetParams(ctx)
// find the fee collector account
feeCollector := k.accountKeeper.GetModuleAccount(ctx, k.feeCollectorName)
// get all balances in the fee collector account,
// where the balance includes minted tokens in the previous block
feesCollectedInt := k.bankKeeper.GetAllBalances(ctx, feeCollector.GetAddress())
// don't intercept if there is no fee in fee collector account
if !feesCollectedInt.IsAllPositive() {
return
}
// record BTC staking gauge for the current height, and transfer corresponding amount
// from fee collector account to incentive module account
// TODO: maybe we should not transfer reward to BTC staking gauge before BTC staking is activated
// this is tricky to implement since finality module will depend on incentive and incentive cannot
// depend on finality module due to cyclic dependency
btcStakingPortion := params.BTCStakingPortion()
btcStakingReward := types.GetCoinsPortion(feesCollectedInt, btcStakingPortion)
k.accumulateBTCStakingReward(ctx, btcStakingReward)
}
func (k Keeper) accumulateBTCStakingReward(ctx context.Context, btcStakingReward sdk.Coins) {
// update BTC staking gauge
height := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)
gauge := types.NewGauge(btcStakingReward...)
k.SetBTCStakingGauge(ctx, height, gauge)
// transfer the BTC staking reward from fee collector account to incentive module account
-> err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, btcStakingReward)
if err != nil {
// this can only be programming error and is unrecoverable
panic(err)
}
}
Added an administrator function to transfer locked funds.