package types import ( "bytes" "fmt" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" ) // Validator defines the total amount of bond shares and their exchange rate to // coins. Accumulation of interest is modelled as an in increase in the // exchange rate, and slashing as a decrease. When coins are delegated to this // validator, the validator is credited with a Delegation whose number of // bond shares is based on the amount of coins delegated divided by the current // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Validator struct { Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? PoolShares PoolShares `json:"pool_shares"` // total shares for tokens held in the pool DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators Description Description `json:"description"` // description terms for the validator BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) // fee related PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools } // NewValidator - initialize a new validator func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Description) Validator { return Validator{ Owner: owner, PubKey: pubKey, Revoked: false, PoolShares: NewUnbondedShares(sdk.ZeroRat()), DelegatorShares: sdk.ZeroRat(), Description: description, BondHeight: int64(0), BondIntraTxCounter: int16(0), ProposerRewardPool: sdk.Coins{}, Commission: sdk.ZeroRat(), CommissionMax: sdk.ZeroRat(), CommissionChangeRate: sdk.ZeroRat(), CommissionChangeToday: sdk.ZeroRat(), PrevBondedShares: sdk.ZeroRat(), } } // only the vitals - does not check bond height of IntraTxCounter func (v Validator) Equal(c2 Validator) bool { return v.PubKey.Equals(c2.PubKey) && bytes.Equal(v.Owner, c2.Owner) && v.PoolShares.Equal(c2.PoolShares) && v.DelegatorShares.Equal(c2.DelegatorShares) && v.Description == c2.Description && //v.BondHeight == c2.BondHeight && //v.BondIntraTxCounter == c2.BondIntraTxCounter && // counter is always changing v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) && v.Commission.Equal(c2.Commission) && v.CommissionMax.Equal(c2.CommissionMax) && v.CommissionChangeRate.Equal(c2.CommissionChangeRate) && v.CommissionChangeToday.Equal(c2.CommissionChangeToday) && v.PrevBondedShares.Equal(c2.PrevBondedShares) } // Description - description fields for a validator type Description struct { Moniker string `json:"moniker"` // name Identity string `json:"identity"` // optional identity signature (ex. UPort or Keybase) Website string `json:"website"` // optional website link Details string `json:"details"` // optional details } func NewDescription(moniker, identity, website, details string) Description { return Description{ Moniker: moniker, Identity: identity, Website: website, Details: details, } } // update the description based on input func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) { if d.Moniker == "[do-not-modify]" { d2.Moniker = d.Moniker } if d.Identity == "[do-not-modify]" { d2.Identity = d.Identity } if d.Website == "[do-not-modify]" { d2.Website = d.Website } if d.Details == "[do-not-modify]" { d2.Details = d.Details } return Description{ Moniker: d2.Moniker, Identity: d2.Identity, Website: d2.Website, Details: d2.Details, }.EnsureLength() } // ensure the length of the description func (d Description) EnsureLength() (Description, sdk.Error) { if len(d.Moniker) > 70 { return d, ErrDescriptionLength(DefaultCodespace, "moniker", len(d.Moniker), 70) } if len(d.Identity) > 3000 { return d, ErrDescriptionLength(DefaultCodespace, "identity", len(d.Identity), 3000) } if len(d.Website) > 140 { return d, ErrDescriptionLength(DefaultCodespace, "website", len(d.Website), 140) } if len(d.Details) > 280 { return d, ErrDescriptionLength(DefaultCodespace, "details", len(d.Details), 280) } return d, nil } // abci validator from stake validator type func (v Validator) ABCIValidator() abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), Power: v.PoolShares.Bonded().RoundInt64(), } } // abci validator from stake validator type // with zero power used for validator updates func (v Validator) ABCIValidatorZero() abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), Power: 0, } } // abci validator from stake validator type func (v Validator) Status() sdk.BondStatus { return v.PoolShares.Status } // update the location of the shares within a validator if its bond status has changed func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) { var tokens int64 switch v.Status() { case sdk.Unbonded: if NewStatus == sdk.Unbonded { return v, pool } pool, tokens = pool.removeSharesUnbonded(v.PoolShares.Amount) case sdk.Unbonding: if NewStatus == sdk.Unbonding { return v, pool } pool, tokens = pool.removeSharesUnbonding(v.PoolShares.Amount) case sdk.Bonded: if NewStatus == sdk.Bonded { // return if nothing needs switching return v, pool } pool, tokens = pool.removeSharesBonded(v.PoolShares.Amount) } switch NewStatus { case sdk.Unbonded: pool, v.PoolShares = pool.addTokensUnbonded(tokens) case sdk.Unbonding: pool, v.PoolShares = pool.addTokensUnbonding(tokens) case sdk.Bonded: pool, v.PoolShares = pool.addTokensBonded(tokens) } return v, pool } // Remove pool shares // Returns corresponding tokens, which could be burned (e.g. when slashing // a validator) or redistributed elsewhere func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) { var tokens int64 switch v.Status() { case sdk.Unbonded: pool, tokens = pool.removeSharesUnbonded(poolShares) case sdk.Unbonding: pool, tokens = pool.removeSharesUnbonding(poolShares) case sdk.Bonded: pool, tokens = pool.removeSharesBonded(poolShares) } v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares) return v, pool, tokens } // TODO remove should only be tokens // get the power or potential power for a validator // if bonded, the power is the BondedShares // if not bonded, the power is the amount of bonded shares which the // the validator would have it was bonded func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) { return v.PoolShares.ToBonded(pool).Amount } //_________________________________________________________________________________________________________ // add tokens to a validator func (v Validator) AddTokensFromDel(pool Pool, amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { exRate := v.DelegatorShareExRate(pool) // bshr/delshr var poolShares PoolShares var equivalentBondedShares sdk.Rat switch v.Status() { case sdk.Unbonded: pool, poolShares = pool.addTokensUnbonded(amount) case sdk.Unbonding: pool, poolShares = pool.addTokensUnbonding(amount) case sdk.Bonded: pool, poolShares = pool.addTokensBonded(amount) } v.PoolShares.Amount = v.PoolShares.Amount.Add(poolShares.Amount) equivalentBondedShares = poolShares.ToBonded(pool).Amount issuedDelegatorShares = equivalentBondedShares.Quo(exRate) // bshr/(bshr/delshr) = delshr v.DelegatorShares = v.DelegatorShares.Add(issuedDelegatorShares) return v, pool, issuedDelegatorShares } // remove delegator shares from a validator // NOTE this function assumes the shares have already been updated for the validator status func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) { amount := v.DelegatorShareExRate(pool).Mul(delShares) eqBondedSharesToRemove := NewBondedShares(amount) v.DelegatorShares = v.DelegatorShares.Sub(delShares) switch v.Status() { case sdk.Unbonded: unbondedShares := eqBondedSharesToRemove.ToUnbonded(pool).Amount pool, createdCoins = pool.removeSharesUnbonded(unbondedShares) v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondedShares) case sdk.Unbonding: unbondingShares := eqBondedSharesToRemove.ToUnbonding(pool).Amount pool, createdCoins = pool.removeSharesUnbonding(unbondingShares) v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondingShares) case sdk.Bonded: pool, createdCoins = pool.removeSharesBonded(eqBondedSharesToRemove.Amount) v.PoolShares.Amount = v.PoolShares.Amount.Sub(eqBondedSharesToRemove.Amount) } return v, pool, createdCoins } // get the exchange rate of tokens over delegator shares // UNITS: eq-val-bonded-shares/delegator-shares func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat { if v.DelegatorShares.IsZero() { return sdk.OneRat() } eqBondedShares := v.PoolShares.ToBonded(pool).Amount return eqBondedShares.Quo(v.DelegatorShares) } //______________________________________________________________________ // ensure fulfills the sdk validator types var _ sdk.Validator = Validator{} // nolint - for sdk.Validator func (v Validator) GetRevoked() bool { return v.Revoked } func (v Validator) GetMoniker() string { return v.Description.Moniker } func (v Validator) GetStatus() sdk.BondStatus { return v.Status() } func (v Validator) GetOwner() sdk.Address { return v.Owner } func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() } func (v Validator) GetDelegatorShares() sdk.Rat { return v.DelegatorShares } func (v Validator) GetBondHeight() int64 { return v.BondHeight } //Human Friendly pretty printer func (v Validator) HumanReadableString() (string, error) { bechOwner, err := sdk.Bech32ifyAcc(v.Owner) if err != nil { return "", err } bechVal, err := sdk.Bech32ifyValPub(v.PubKey) if err != nil { return "", err } resp := "Validator \n" resp += fmt.Sprintf("Owner: %s\n", bechOwner) resp += fmt.Sprintf("Validator: %s\n", bechVal) resp += fmt.Sprintf("Shares: Status %s, Amount: %s\n", sdk.BondStatusToString(v.PoolShares.Status), v.PoolShares.Amount.FloatString()) resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.FloatString()) resp += fmt.Sprintf("Description: %s\n", v.Description) resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String()) resp += fmt.Sprintf("Commission: %s\n", v.Commission.String()) resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String()) resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String()) resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String()) resp += fmt.Sprintf("Previously Bonded Stares: %s\n", v.PrevBondedShares.String()) return resp, nil }