cosmos-sdk/x/staking/types/validator.go

531 lines
15 KiB
Go

package types
import (
"bytes"
"fmt"
"sort"
"strings"
"time"
abci "github.com/tendermint/tendermint/abci/types"
tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto"
"gopkg.in/yaml.v2"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
const (
// TODO: Why can't we just have one string description which can be JSON by convention
MaxMonikerLength = 70
MaxIdentityLength = 3000
MaxWebsiteLength = 140
MaxSecurityContactLength = 140
MaxDetailsLength = 280
)
var (
BondStatusUnspecified = BondStatus_name[int32(Unspecified)]
BondStatusUnbonded = BondStatus_name[int32(Unbonded)]
BondStatusUnbonding = BondStatus_name[int32(Unbonding)]
BondStatusBonded = BondStatus_name[int32(Bonded)]
)
var _ ValidatorI = Validator{}
// NewValidator constructs a new Validator
//nolint:interfacer
func NewValidator(operator sdk.ValAddress, pubKey cryptotypes.PubKey, description Description) (Validator, error) {
pkAny, err := codectypes.NewAnyWithValue(pubKey)
if err != nil {
return Validator{}, err
}
return Validator{
OperatorAddress: operator.String(),
ConsensusPubkey: pkAny,
Jailed: false,
Status: Unbonded,
Tokens: sdk.ZeroInt(),
DelegatorShares: sdk.ZeroDec(),
Description: description,
UnbondingHeight: int64(0),
UnbondingTime: time.Unix(0, 0).UTC(),
Commission: NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
MinSelfDelegation: sdk.ZeroInt(),
}, nil
}
// String implements the Stringer interface for a Validator object.
func (v Validator) String() string {
out, _ := yaml.Marshal(v)
return string(out)
}
// Validators is a collection of Validator
type Validators []Validator
func (v Validators) String() (out string) {
for _, val := range v {
out += val.String() + "\n"
}
return strings.TrimSpace(out)
}
// ToSDKValidators - convenience function convert []Validator to []sdk.ValidatorI
func (v Validators) ToSDKValidators() (validators []ValidatorI) {
for _, val := range v {
validators = append(validators, val)
}
return validators
}
// Sort Validators sorts validator array in ascending operator address order
func (v Validators) Sort() {
sort.Sort(v)
}
// Implements sort interface
func (v Validators) Len() int {
return len(v)
}
// Implements sort interface
func (v Validators) Less(i, j int) bool {
return bytes.Compare(v[i].GetOperator().Bytes(), v[j].GetOperator().Bytes()) == -1
}
// Implements sort interface
func (v Validators) Swap(i, j int) {
v[i], v[j] = v[j], v[i]
}
// ValidatorsByVotingPower implements sort.Interface for []Validator based on
// the VotingPower and Address fields.
// The validators are sorted first by their voting power (descending). Secondary index - Address (ascending).
// Copied from tendermint/types/validator_set.go
type ValidatorsByVotingPower []Validator
func (valz ValidatorsByVotingPower) Len() int { return len(valz) }
func (valz ValidatorsByVotingPower) Less(i, j int, r sdk.Int) bool {
if valz[i].ConsensusPower(r) == valz[j].ConsensusPower(r) {
addrI, errI := valz[i].GetConsAddr()
addrJ, errJ := valz[j].GetConsAddr()
// If either returns error, then return false
if errI != nil || errJ != nil {
return false
}
return bytes.Compare(addrI, addrJ) == -1
}
return valz[i].ConsensusPower(r) > valz[j].ConsensusPower(r)
}
func (valz ValidatorsByVotingPower) Swap(i, j int) {
valz[i], valz[j] = valz[j], valz[i]
}
// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
func (v Validators) UnpackInterfaces(c codectypes.AnyUnpacker) error {
for i := range v {
if err := v[i].UnpackInterfaces(c); err != nil {
return err
}
}
return nil
}
// return the redelegation
func MustMarshalValidator(cdc codec.BinaryCodec, validator *Validator) []byte {
return cdc.MustMarshal(validator)
}
// unmarshal a redelegation from a store value
func MustUnmarshalValidator(cdc codec.BinaryCodec, value []byte) Validator {
validator, err := UnmarshalValidator(cdc, value)
if err != nil {
panic(err)
}
return validator
}
// unmarshal a redelegation from a store value
func UnmarshalValidator(cdc codec.BinaryCodec, value []byte) (v Validator, err error) {
err = cdc.Unmarshal(value, &v)
return v, err
}
// IsBonded checks if the validator status equals Bonded
func (v Validator) IsBonded() bool {
return v.GetStatus() == Bonded
}
// IsUnbonded checks if the validator status equals Unbonded
func (v Validator) IsUnbonded() bool {
return v.GetStatus() == Unbonded
}
// IsUnbonding checks if the validator status equals Unbonding
func (v Validator) IsUnbonding() bool {
return v.GetStatus() == Unbonding
}
// constant used in flags to indicate that description field should not be updated
const DoNotModifyDesc = "[do-not-modify]"
func NewDescription(moniker, identity, website, securityContact, details string) Description {
return Description{
Moniker: moniker,
Identity: identity,
Website: website,
SecurityContact: securityContact,
Details: details,
}
}
// String implements the Stringer interface for a Description object.
func (d Description) String() string {
out, _ := yaml.Marshal(d)
return string(out)
}
// UpdateDescription updates the fields of a given description. An error is
// returned if the resulting description contains an invalid length.
func (d Description) UpdateDescription(d2 Description) (Description, error) {
if d2.Moniker == DoNotModifyDesc {
d2.Moniker = d.Moniker
}
if d2.Identity == DoNotModifyDesc {
d2.Identity = d.Identity
}
if d2.Website == DoNotModifyDesc {
d2.Website = d.Website
}
if d2.SecurityContact == DoNotModifyDesc {
d2.SecurityContact = d.SecurityContact
}
if d2.Details == DoNotModifyDesc {
d2.Details = d.Details
}
return NewDescription(
d2.Moniker,
d2.Identity,
d2.Website,
d2.SecurityContact,
d2.Details,
).EnsureLength()
}
// EnsureLength ensures the length of a validator's description.
func (d Description) EnsureLength() (Description, error) {
if len(d.Moniker) > MaxMonikerLength {
return d, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid moniker length; got: %d, max: %d", len(d.Moniker), MaxMonikerLength)
}
if len(d.Identity) > MaxIdentityLength {
return d, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid identity length; got: %d, max: %d", len(d.Identity), MaxIdentityLength)
}
if len(d.Website) > MaxWebsiteLength {
return d, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid website length; got: %d, max: %d", len(d.Website), MaxWebsiteLength)
}
if len(d.SecurityContact) > MaxSecurityContactLength {
return d, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid security contact length; got: %d, max: %d", len(d.SecurityContact), MaxSecurityContactLength)
}
if len(d.Details) > MaxDetailsLength {
return d, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid details length; got: %d, max: %d", len(d.Details), MaxDetailsLength)
}
return d, nil
}
// ABCIValidatorUpdate returns an abci.ValidatorUpdate from a staking validator type
// with the full validator power
// Implements Proof of Authority, which can be thought of as a binary staking
// model (either have consensus power, or not)
func (v Validator) ABCIValidatorUpdate() abci.ValidatorUpdate {
tmProtoPk, err := v.TmConsPublicKey()
if err != nil {
panic(err)
}
return abci.ValidatorUpdate{
PubKey: tmProtoPk,
Power: 1,
}
}
// ABCIValidatorUpdateZero returns an abci.ValidatorUpdate from a staking validator type
// with zero power used for validator updates.
func (v Validator) ABCIValidatorUpdateZero() abci.ValidatorUpdate {
tmProtoPk, err := v.TmConsPublicKey()
if err != nil {
panic(err)
}
return abci.ValidatorUpdate{
PubKey: tmProtoPk,
Power: 0,
}
}
// SetInitialCommission attempts to set a validator's initial commission. An
// error is returned if the commission is invalid.
func (v Validator) SetInitialCommission(commission Commission) (Validator, error) {
if err := commission.Validate(); err != nil {
return v, err
}
v.Commission = commission
return v, nil
}
// In some situations, the exchange rate becomes invalid, e.g. if
// Validator loses all tokens due to slashing. In this case,
// make all future delegations invalid.
func (v Validator) InvalidExRate() bool {
return v.Tokens.IsZero() && v.DelegatorShares.IsPositive()
}
// calculate the token worth of provided shares
func (v Validator) TokensFromShares(shares sdk.Dec) sdk.Dec {
if v.DelegatorShares.IsZero() {
return sdk.Dec(sdk.ZeroInt())
}
return (shares.MulInt(v.Tokens)).Quo(v.DelegatorShares)
}
// calculate the token worth of provided shares, truncated
func (v Validator) TokensFromSharesTruncated(shares sdk.Dec) sdk.Dec {
if (v.DelegatorShares.IsZero()) {
return sdk.Dec(sdk.ZeroInt())
}
return (shares.MulInt(v.Tokens)).QuoTruncate(v.DelegatorShares)
}
// TokensFromSharesRoundUp returns the token worth of provided shares, rounded
// up.
func (v Validator) TokensFromSharesRoundUp(shares sdk.Dec) sdk.Dec {
if (v.DelegatorShares.IsZero()) {
return sdk.Dec(sdk.ZeroInt())
}
return (shares.MulInt(v.Tokens)).QuoRoundUp(v.DelegatorShares)
}
// SharesFromTokens returns the shares of a delegation given a bond amount. It
// returns an error if the validator has no tokens.
func (v Validator) SharesFromTokens(amt sdk.Int) (sdk.Dec, error) {
if v.Tokens.IsZero() {
return sdk.ZeroDec(), ErrInsufficientShares
}
return v.GetDelegatorShares().MulInt(amt).QuoInt(v.GetTokens()), nil
}
// SharesFromTokensTruncated returns the truncated shares of a delegation given
// a bond amount. It returns an error if the validator has no tokens.
func (v Validator) SharesFromTokensTruncated(amt sdk.Int) (sdk.Dec, error) {
if v.Tokens.IsZero() {
return sdk.ZeroDec(), ErrInsufficientShares
}
return v.GetDelegatorShares().MulInt(amt).QuoTruncate(v.GetTokens().ToDec()), nil
}
// get the bonded tokens which the validator holds
func (v Validator) BondedTokens() sdk.Int {
if v.IsBonded() {
return v.Tokens
}
return sdk.ZeroInt()
}
// ConsensusPower gets the consensus-engine power. Aa reduction of 10^6 from
// validator tokens is applied
func (v Validator) ConsensusPower(r sdk.Int) int64 {
if v.IsBonded() {
return v.PotentialConsensusPower(r)
}
return 0
}
// PotentialConsensusPower returns the potential consensus-engine power.
func (v Validator) PotentialConsensusPower(r sdk.Int) int64 {
return sdk.TokensToConsensusPower(v.Tokens, r)
}
// UpdateStatus updates the location of the shares within a validator
// to reflect the new status
func (v Validator) UpdateStatus(newStatus BondStatus) Validator {
v.Status = newStatus
return v
}
// AddTokensFromDel adds tokens to a validator
func (v Validator) AddTokensFromDel(amount sdk.Int) (Validator, sdk.Dec) {
// calculate the shares to issue
var issuedShares sdk.Dec
if v.DelegatorShares.IsZero() {
// the first delegation to a validator sets the exchange rate to one
issuedShares = amount.ToDec()
} else {
shares, err := v.SharesFromTokens(amount)
if err != nil {
panic(err)
}
issuedShares = shares
}
v.Tokens = v.Tokens.Add(amount)
v.DelegatorShares = v.DelegatorShares.Add(issuedShares)
return v, issuedShares
}
// RemoveTokens removes tokens from a validator
func (v Validator) RemoveTokens(tokens sdk.Int) Validator {
if tokens.IsNegative() {
panic(fmt.Sprintf("should not happen: trying to remove negative tokens %v", tokens))
}
if v.Tokens.LT(tokens) {
panic(fmt.Sprintf("should not happen: only have %v tokens, trying to remove %v", v.Tokens, tokens))
}
v.Tokens = v.Tokens.Sub(tokens)
return v
}
// RemoveDelShares removes delegator shares from a validator.
// NOTE: because token fractions are left in the valiadator,
// the exchange rate of future shares of this validator can increase.
func (v Validator) RemoveDelShares(delShares sdk.Dec) (Validator, sdk.Int) {
remainingShares := v.DelegatorShares.Sub(delShares)
var issuedTokens sdk.Int
if remainingShares.IsZero() {
// last delegation share gets any trimmings
issuedTokens = v.Tokens
v.Tokens = sdk.ZeroInt()
} else {
// leave excess tokens in the validator
// however fully use all the delegator shares
issuedTokens = v.TokensFromShares(delShares).TruncateInt()
v.Tokens = v.Tokens.Sub(issuedTokens)
if v.Tokens.IsNegative() {
panic("attempting to remove more tokens than available in validator")
}
}
v.DelegatorShares = remainingShares
return v, issuedTokens
}
// MinEqual defines a more minimum set of equality conditions when comparing two
// validators.
func (v *Validator) MinEqual(other *Validator) bool {
return v.OperatorAddress == other.OperatorAddress &&
v.Status == other.Status &&
v.Tokens.Equal(other.Tokens) &&
v.DelegatorShares.Equal(other.DelegatorShares) &&
v.Description.Equal(other.Description) &&
v.Commission.Equal(other.Commission) &&
v.Jailed == other.Jailed &&
v.MinSelfDelegation.Equal(other.MinSelfDelegation) &&
v.ConsensusPubkey.Equal(other.ConsensusPubkey)
}
// Equal checks if the receiver equals the parameter
func (v *Validator) Equal(v2 *Validator) bool {
return v.MinEqual(v2) &&
v.UnbondingHeight == v2.UnbondingHeight &&
v.UnbondingTime.Equal(v2.UnbondingTime)
}
func (v Validator) IsJailed() bool { return v.Jailed }
func (v Validator) GetMoniker() string { return v.Description.Moniker }
func (v Validator) GetStatus() BondStatus { return v.Status }
func (v Validator) GetOperator() sdk.ValAddress {
if v.OperatorAddress == "" {
return nil
}
addr, err := sdk.ValAddressFromBech32(v.OperatorAddress)
if err != nil {
panic(err)
}
return addr
}
// ConsPubKey returns the validator PubKey as a cryptotypes.PubKey.
func (v Validator) ConsPubKey() (cryptotypes.PubKey, error) {
pk, ok := v.ConsensusPubkey.GetCachedValue().(cryptotypes.PubKey)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "expecting cryptotypes.PubKey, got %T", pk)
}
return pk, nil
}
// TmConsPublicKey casts Validator.ConsensusPubkey to tmprotocrypto.PubKey.
func (v Validator) TmConsPublicKey() (tmprotocrypto.PublicKey, error) {
pk, err := v.ConsPubKey()
if err != nil {
return tmprotocrypto.PublicKey{}, err
}
tmPk, err := cryptocodec.ToTmProtoPublicKey(pk)
if err != nil {
return tmprotocrypto.PublicKey{}, err
}
return tmPk, nil
}
// GetConsAddr extracts Consensus key address
func (v Validator) GetConsAddr() (sdk.ConsAddress, error) {
pk, ok := v.ConsensusPubkey.GetCachedValue().(cryptotypes.PubKey)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "expecting cryptotypes.PubKey, got %T", pk)
}
return sdk.ConsAddress(pk.Address()), nil
}
func (v Validator) GetTokens() sdk.Int { return v.Tokens }
func (v Validator) GetBondedTokens() sdk.Int { return v.BondedTokens() }
func (v Validator) GetConsensusPower(r sdk.Int) int64 {
return v.ConsensusPower(r)
}
func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate }
func (v Validator) GetMinSelfDelegation() sdk.Int { return v.MinSelfDelegation }
func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares }
// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
func (v Validator) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
var pk cryptotypes.PubKey
return unpacker.UnpackAny(v.ConsensusPubkey, &pk)
}