Proposal to increase MaxEntries
from 7 to 100
- Terraform Labs
- October 12, 2020
TL;DR
- Increase
MaxEntries
, the maximum number of undelegation entries that can exist at the same time per delegator-to-validator combination (UnbondingDelegation
), from 7 to 100. - This is required to maintain fungibility and redemption properties of bLuna, a staking derivatives asset (bAsset) of Luna.
Introduction
Blockchains built on Cosmos SDK, including Terra, have limitations on undelegation requests in order to ensure the security of its underlying Proof-of-Stake consensus layer. This is because the number of staked tokens directly controls voting power of a validator, and allowing undelegations to freely happen will have negative implications on Proof-of-Stake voting processes in general.
As documented with the offical Terra documentation, delegators must wait 21 days in order to undelegate Luna from a validator. During this unbonding period, Luna subject to this limitation do not generate rewards and cannot be transferred to another account.
More specifically, Cosmos SDK defines the following limitations on undelegations:
- While tokens are staked, they exist in a
Delegation
data structure, defined as
type Delegation struct {
DelegatorAddr sdk.AccAddress
ValidatorAddr sdk.ValAddress
Shares sdk.Dec // delegation shares received
}
- When staked tokens are undelegated, they exist as an
UnbondingDelegation
duringUnbondingTime
.UnbondingDelegation
s represent a delegator-to-validator combination in which undelegating requests are present, and stores anUnbondingDelegationEntry
array that lists all unbonding entries under that delegator-to-validator combination. A separateUnbondingDelegationEntry
object exists for every undelegation request, which stores aCompletionTime
when tokens will be returned to the delegator. Under current parameters on Terra,UnbondingTime
is set to 21 days.
type UnbondingDelegation struct {
DelegatorAddr sdk.AccAddress // delegator
ValidatorAddr sdk.ValAddress // validator unbonding from operator addr
Entries []UnbondingDelegationEntry // unbonding delegation entries
}
type UnbondingDelegationEntry struct {
CreationHeight int64 // height which the unbonding took place
CompletionTime time.Time // unix time for unbonding completion
InitialBalance sdk.Coin // luna initially scheduled to receive at completion
Balance sdk.Coin // luna to receive at completion
}
- There notably exists a limitation where the number of
UnbondingDelegationEntry
’s on anUnbondingDelegation
queue cannot exceed the number set underKeyMaxEntries
. Currently on Terra (and also on the Cosmos Hub), this parameter is set to 7:
$ curl -X GET "https://lcd.terra.dev/staking/parameters" -H "accept: application/json" | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 168 100 168 0 0 2470 0 --:--:-- --:--:-- --:--:-- 2470
{
"height": "114735",
"result": {
"unbonding_time": "1814400000000000",
"max_validators": 100,
"max_entries": 7,
"historical_entries": 0,
"bond_denom": "uluna"
}
}
- This means that for a unique delegator-to-validator pair, the number of undelegation requests on an unbonding state (
UnbondingDelegationEntries
- i.e. did not finish the 21 day unbonding period) at any given time cannot exceed 7. Simply put, if an account tries to make more than 7 undelegation requests from the same validator without waiting for at least one of them to finish the 21 day unbonding period, the request will fail. - This rule also applies for redelegations - i.e. even though redelegations do not require the 21 day unbonding period and are instant, having more than 7 redelegation requests per a unique (
DelegatorAddr
,ValidatorSrcAddr
,ValidatorDstAddr
) combination each within a 21-day queue will also result in a new redelegation request failing. Although undelegations and redelegations are counted separately, they refer to the sameMaxEntries
parameter as follows:
// HasMaxRedelegationEntries - redelegation has maximum number of entries
func (k Keeper) HasMaxRedelegationEntries(ctx sdk.Context,
delegatorAddr sdk.AccAddress, validatorSrcAddr,
validatorDstAddr sdk.ValAddress) bool {
red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr)
if !found {
return false
}
return len(red.Entries) >= int(k.MaxEntries(ctx))
}
// HasMaxUnbondingDelegationEntries - check if unbonding delegation has maximum number of entries
func (k Keeper) HasMaxUnbondingDelegationEntries(ctx sdk.Context,
delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) bool {
ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr)
if !found {
return false
}
return len(ubd.Entries) >= int(k.MaxEntries(ctx))
}
Problem
One of the key components of Anchor are bAssets, which are liquid staking derivatives that generate yield from its underlying staked asset while still being transferrable. A detailed paper on the concept of bAssets is available here.
bLuna is a staking derivative asset (bAsset) of Luna, Terra’s native staking token. Unlike other bAssets being supported on Anchor, bLuna (i) does not require interchain operations, as it is a Terra-native asset; and (ii) returns staking yield directly in Terra stablecoin.
As bLuna needs to meet all fungibility and redemption properties as defined with the bAsset whitepaper, Luna staked through the bLuna contract must always be 1:1 redeemable on request. As anyone can request redemption (i.e. undelegation) of bLuna back to Luna, undelegations on the bLuna contract must be batch processed in order to (i) prevent system congestion, and (ii) avoid hitting undelegation limitations as described above.
The issue is that even if undelegations were batch processed, the MaxEntries
parameter will be a problem as all undelegation requests go through the same bLuna contract address. If UnbondingTime
is set to 21 days and MaxEntries
is set to 7, the minimum pooling period for submitting a batched undelegation entry would roughly be 3 days on average. This not only harms user experience (by increasing waiting time for undelegations), but also complicates bLuna implementation by clogging state - being required to pool all undelegation requests for at least 3 days.
Solution
As a solution to the problems listed above, we propose to increase the value of MaxEntries
from 7 to 100. This solves issues with bLuna undelegations by:
- reducing the minimum pooling period from 3 days to 0.21 (5 hours and 144 seconds). this significantly reduces state overhead, and only adds a few hours to the 21-day undelegation period - largely aligning user experience with native staking.
- bLuna contracts will have its pooling period given as 6 hours for redundancy. if the
MaxEntries
parameter changes in the future, we will manually update the contract’s parameters and/or migrate the entire contract.
This is because we do not have bindings to directly query staking/parameters
within the CosmWasm smart contract layer, and adding this will require initiating another chain upgrade.
Risk Mitigation
One of the primary reasons for enforcing limitations on undelegations is to prevent potential attacks on the consensus layer. The MaxEntries
parameter (aka the “7-stacks rule”) is one of these limitations put in place to protect Proof-of-Stake consensus from potential attacks.
However, we believe this proposal will not have a significant impact on security. Going through a few possible attack factors:
- Transaction DoS attacks - this can be done with any other transaction type, and not relevant to types of transactions being made on the blockchain.
- Attacks on consensus and governance with undelegations - the 21-day undelegation period will remain unchanged. As voting power depends on the number of tokens staked, an increase in allowed undelegation requests will not significantly impact security in this regard.
-
Attacks on consensus and governance with redelegations - this governance proposal also affects Luna redelegations, as both undelegations and redelegations refer to the same
MaxEntries
parameter (although counted separately). While redelegations are instant unlike undelegations, we believe redelegations will not affect validator voting power as Cosmos SDK also puts limitations on serial redelegations. When a user has redelegated from validator A to B, the user cannot redelegate from validator B to another validator for another 21 days. This is checked with:
// check if validator is receiving a redelegation
func (k Keeper) HasReceivingRedelegation(ctx sdk.Context,
delAddr sdk.AccAddress, valDstAddr sdk.ValAddress) bool {
store := ctx.KVStore(k.storeKey)
prefix := types.GetREDsByDelToValDstIndexKey(delAddr, valDstAddr)
iterator := sdk.KVStorePrefixIterator(store, prefix)
defer iterator.Close()
return iterator.Valid()
}
Thus, this effectively puts similar restrictions on redelegations in terms of voting power.
Changes
[
{
"key": "KeyMaxEntries",
"value": 100,
"subspace": "staking"
}
]