Proposal to increase MaxEntries from 7 to 100

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 during UnbondingTime. UnbondingDelegations represent a delegator-to-validator combination in which undelegating requests are present, and stores an UnbondingDelegationEntry array that lists all unbonding entries under that delegator-to-validator combination. A separate UnbondingDelegationEntry object exists for every undelegation request, which stores a CompletionTime 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 an UnbondingDelegation queue cannot exceed the number set under KeyMaxEntries. 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 same MaxEntries 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"
  }
]
1 Like

100 feels excessive here.

One of the reasons the 7 exists (although I believe it was a largely arbitrary number), is that all unbondings and redelegations need to be iterated over, accessed from state and compared against the current time, every block. Increasing the potential compute required here by 14x, this would have the potential to have a notable impact on block times.

Understanding the impact on block production and validation here therefore is critical, and I would advise this needs to be tested thoroughly (a simulation can probably be created to study the affect, as we’ll want to know the behaviour at different magnitudes of users) before a proposal should be made / accepted.

2 Likes