Skip to content

Commit

Permalink
add validator weight cap (#1032)
Browse files Browse the repository at this point in the history
  • Loading branch information
sampocs authored Jan 2, 2024
1 parent 8f37be3 commit 162a839
Show file tree
Hide file tree
Showing 18 changed files with 934 additions and 330 deletions.
1 change: 1 addition & 0 deletions proto/stride/stakeibc/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ message Params {
uint64 default_max_redemption_rate_threshold = 15;
uint64 ibc_transfer_timeout_nanos = 16;
uint64 validator_slash_query_threshold = 19;
uint64 validator_weight_cap = 20;

reserved 8, 17, 18;
}
15 changes: 9 additions & 6 deletions proto/stride/stakeibc/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ service Msg {
rpc RebalanceValidators(MsgRebalanceValidators)
returns (MsgRebalanceValidatorsResponse);
rpc AddValidators(MsgAddValidators) returns (MsgAddValidatorsResponse);
rpc ChangeValidatorWeight(MsgChangeValidatorWeight)
returns (MsgChangeValidatorWeightResponse);
rpc ChangeValidatorWeight(MsgChangeValidatorWeights)
returns (MsgChangeValidatorWeightsResponse);
rpc DeleteValidator(MsgDeleteValidator) returns (MsgDeleteValidatorResponse);
rpc RestoreInterchainAccount(MsgRestoreInterchainAccount)
returns (MsgRestoreInterchainAccountResponse);
Expand Down Expand Up @@ -158,13 +158,16 @@ message MsgAddValidators {
}
message MsgAddValidatorsResponse {}

message MsgChangeValidatorWeight {
message ValidatorWeight {
string address = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
uint64 weight = 2;
}
message MsgChangeValidatorWeights {
string creator = 1;
string host_zone = 2;
string val_addr = 3 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
uint64 weight = 4;
repeated ValidatorWeight validator_weights = 3;
}
message MsgChangeValidatorWeightResponse {}
message MsgChangeValidatorWeightsResponse {}

message MsgDeleteValidator {
string creator = 1;
Expand Down
2 changes: 1 addition & 1 deletion utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func Int64ToCoinString(amount int64, denom string) string {

func ValidateAdminAddress(address string) error {
if !Admins[address] {
return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, fmt.Sprintf("invalid creator address (%s)", address))
return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, fmt.Sprintf("address (%s) is not an admin", address))
}
return nil
}
Expand Down
1 change: 1 addition & 0 deletions x/ratelimit/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
)

// NewMessageHandler returns ratelimit module messages
// TODO: Remove - no longer used since sdk 47
func NewMessageHandler(k keeper.Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
_ = ctx.WithEventManager(sdk.NewEventManager())
Expand Down
1 change: 1 addition & 0 deletions x/stakeibc/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func GetTxCmd() *cobra.Command {
cmd.AddCommand(CmdRebalanceValidators())
cmd.AddCommand(CmdAddValidators())
cmd.AddCommand(CmdChangeValidatorWeight())
cmd.AddCommand(CmdChangeMultipleValidatorWeight())
cmd.AddCommand(CmdDeleteValidator())
cmd.AddCommand(CmdRestoreInterchainAccount())
cmd.AddCommand(CmdUpdateValidatorSharesExchRate())
Expand Down
109 changes: 99 additions & 10 deletions x/stakeibc/client/cli/tx_change_validator_weight.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cli

import (
"strconv"
"encoding/json"
"os"
"strings"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
Expand All @@ -12,35 +14,122 @@ import (
"github.com/Stride-Labs/stride/v16/x/stakeibc/types"
)

var _ = strconv.Itoa(0)
type ValidatorWeightList struct {
ValidatorWeights []*types.ValidatorWeight `json:"validator_weights,omitempty"`
}

// Parse a JSON with a list of validators in the format
//
// {
// "validator_weights": [
// {"address": "cosmosXXX", "weight": 1},
// {"address": "cosmosXXX", "weight": 2}
// ]
// }
func parseChangeValidatorWeightsFile(validatorsFile string) (weights []*types.ValidatorWeight, err error) {
fileContents, err := os.ReadFile(validatorsFile)
if err != nil {
return weights, err
}

var weightsList ValidatorWeightList
if err = json.Unmarshal(fileContents, &weightsList); err != nil {
return weights, err
}

return weightsList.ValidatorWeights, nil
}

// Updates the weight for a single validator
func CmdChangeValidatorWeight() *cobra.Command {
cmd := &cobra.Command{
Use: "change-validator-weight [host-zone] [address] [weight]",
Short: "Broadcast message change-validator-weight",
Short: "Broadcast message change-validator-weight to update the weight for a single validator",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) (err error) {
argHostZone := args[0]
argName := args[1]
argWeight, err := cast.ToUint64E(args[2])
hostZone := args[0]
valAddress := args[1]
weight, err := cast.ToUint64E(args[2])
if err != nil {
return err
}
weights := []*types.ValidatorWeight{
{
Address: valAddress,
Weight: weight,
},
}

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

msg := types.NewMsgChangeValidatorWeights(
clientCtx.GetFromAddress().String(),
hostZone,
weights,
)
if err := msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}

// Updates the weight for multiple validators
//
// Accepts a file in the following format:
//
// {
// "validator_weights": [
// {"address": "cosmosXXX", "weight": 1},
// {"address": "cosmosXXX", "weight": 2}
// ]
// }
func CmdChangeMultipleValidatorWeight() *cobra.Command {
cmd := &cobra.Command{
Use: "change-validator-weights [host-zone] [validator-weight-file]",
Short: "Broadcast message change-validator-weights to update the weights for multiple validators",
Long: strings.TrimSpace(
`Changes multiple validator weights at once, using a JSON file in the following format
{
"validator_weights": [
{"address": "cosmosXXX", "weight": 1},
{"address": "cosmosXXX", "weight": 2}
]
}
`),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) (err error) {
hostZone := args[0]
validatorWeightChangeFile := args[1]

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

msg := types.NewMsgChangeValidatorWeight(
weights, err := parseChangeValidatorWeightsFile(validatorWeightChangeFile)
if err != nil {
return err
}

msg := types.NewMsgChangeValidatorWeights(
clientCtx.GetFromAddress().String(),
argHostZone,
argName,
argWeight,
hostZone,
weights,
)
if err := msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
Expand Down
3 changes: 2 additions & 1 deletion x/stakeibc/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
)

// Handles stakeibc transactions
// TODO: Remove - no longer used since sdk 47
func NewMessageHandler(k keeper.Keeper) sdk.Handler {
msgServer := keeper.NewMsgServerImpl(k)

Expand Down Expand Up @@ -46,7 +47,7 @@ func NewMessageHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgAddValidators:
res, err := msgServer.AddValidators(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgChangeValidatorWeight:
case *types.MsgChangeValidatorWeights:
res, err := msgServer.ChangeValidatorWeight(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgDeleteValidator:
Expand Down
46 changes: 46 additions & 0 deletions x/stakeibc/keeper/host_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import (
"github.com/Stride-Labs/stride/v16/x/stakeibc/types"
)

const (
MinValidatorsBeforeWeightCapCheck = 10
)

// SetHostZone set a specific hostZone in the store
func (k Keeper) SetHostZone(ctx sdk.Context, hostZone types.HostZone) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.HostZoneKey))
Expand Down Expand Up @@ -272,6 +276,11 @@ func (k Keeper) AddValidatorToHostZone(ctx sdk.Context, chainId string, validato
SlashQueryCheckpoint: checkpoint,
})

// Finally, confirm none of the validator's exceed the weight cap
if err := k.CheckValidatorWeightsBelowCap(ctx, hostZone.Validators); err != nil {
return err
}

k.SetHostZone(ctx, hostZone)

return nil
Expand Down Expand Up @@ -313,6 +322,43 @@ func (k Keeper) RemoveValidatorFromHostZone(ctx sdk.Context, chainId string, val
return errorsmod.Wrapf(types.ErrValidatorNotFound, errMsg)
}

// Checks if any validator's portion of the weight is greater than the cap
func (k Keeper) CheckValidatorWeightsBelowCap(ctx sdk.Context, validators []*types.Validator) error {
// If there's only a few validators, don't enforce this yet
if len(validators) < MinValidatorsBeforeWeightCapCheck {
return nil
}

// The weight cap in params is an int representing a percentage (e.g. 10 is 10%)
params := k.GetParams(ctx)
validatorWeightCap := float64(params.ValidatorWeightCap)

// Store a map of each validator weight, as well as the total
totalWeight := float64(0)
weightsByValidator := map[string]float64{}
for _, validator := range validators {
weightsByValidator[validator.Address] = float64(validator.Weight)
totalWeight += float64(validator.Weight)
}

// If the total validator weights are 0, exit prematurely
if totalWeight == 0 {
return nil
}

// Check if any validator exceeds the cap
for _, address := range utils.StringMapKeys[float64](weightsByValidator) {
weightPercentage := weightsByValidator[address] / totalWeight * 100
if weightPercentage > validatorWeightCap {
return errorsmod.Wrapf(types.ErrValidatorExceedsWeightCap,
"validator %s exceeds weight cap, has %v%% of the total weight when the cap is %v%%",
address, weightPercentage, validatorWeightCap)
}
}

return nil
}

// Get a validator and its index from a list of validators, by address
func GetValidatorFromAddress(validators []*types.Validator, address string) (val types.Validator, index int64, found bool) {
for i, v := range validators {
Expand Down
Loading

0 comments on commit 162a839

Please sign in to comment.