diff --git a/types/stake.go b/types/stake.go index 3c9d797b16..1d97fbbdbe 100644 --- a/types/stake.go +++ b/types/stake.go @@ -6,23 +6,23 @@ import ( ) // status of a validator -type ValidatorStatus byte +type BondStatus byte // nolint const ( - Bonded ValidatorStatus = 0x00 - Unbonding ValidatorStatus = 0x01 - Unbonded ValidatorStatus = 0x02 - Revoked ValidatorStatus = 0x03 + Bonded BondStatus = 0x00 + Unbonding BondStatus = 0x01 + Unbonded BondStatus = 0x02 + Revoked BondStatus = 0x03 ) // validator for a delegated proof of stake system type Validator interface { - GetStatus() ValidatorStatus // status of the validator - GetAddress() Address // owner address to receive/return validators coins - GetPubKey() crypto.PubKey // validation pubkey - GetPower() Rat // validation power - GetBondHeight() int64 // height in which the validator became active + GetStatus() BondStatus // status of the validator + GetAddress() Address // owner address to receive/return validators coins + GetPubKey() crypto.PubKey // validation pubkey + GetPower() Rat // validation power + GetBondHeight() int64 // height in which the validator became active } // validator which fulfills abci validator interface for use in Tendermint diff --git a/x/stake/handler.go b/x/stake/handler.go index cd52808801..b140696d0f 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -287,7 +287,8 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { // change the share types to unbonded if they were not already if validator.Status == sdk.Bonded { - p, validator = p.bondedToUnbondedPool(validator) + validator.Status = sdk.Unbonded + p, validator = p.UpdateSharesLocation(validator) } // lastly update the status diff --git a/x/stake/keeper.go b/x/stake/keeper.go index c72dd4b854..4992b4c71f 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -69,19 +69,23 @@ func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators Va func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { store := ctx.KVStore(k.storeKey) + pool := k.getPool(store) address := validator.Address + // update the main list ordered by address before exiting + defer func() { + bz := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(address), bz) + }() + // retreive the old validator record oldValidator, oldFound := k.GetValidator(ctx, address) - // marshal the validator record and add to the state - bz := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(address), bz) - powerIncreasing := false if oldFound { // if the voting power is the same no need to update any of the other indexes - if oldValidator.BondedShares.Equal(validator.BondedShares) { + if oldValidator.Status == sdk.Bonded && + oldValidator.BondedShares.Equal(validator.BondedShares) { return } else if oldValidator.BondedShares.LT(validator.BondedShares) { powerIncreasing = true @@ -116,7 +120,14 @@ func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { } // update the validator set for this validator - k.updateValidators(ctx, store, validator.Address) + valIsNowBonded := k.updateValidators(ctx, store, validator.Address) + + if oldValidator.Status != sdk.Bonded && valIsNowBonded { + validator.Status = sdk.Bonded + pool, validator = pool.UpdateSharesLocation(validator) + k.setPool(ctx, pool) + } + return } @@ -187,6 +198,8 @@ func (k Keeper) GetValidatorsBondedByPower(ctx sdk.Context) []Validator { return validators } +// XXX TODO build in consideration for revoked +// // Update the validator group and kick out any old validators. In addition this // function adds (or doesn't add) a validator which has updated its bonded // tokens to the validator group. -> this validator is specified through the @@ -198,7 +211,8 @@ func (k Keeper) GetValidatorsBondedByPower(ctx sdk.Context) []Validator { // ValidatorsBondedKey. This store is used to determine if a validator is a // validator without needing to iterate over the subspace as we do in // GetValidators. -func (k Keeper) updateValidators(ctx sdk.Context, store sdk.KVStore, updatedValidatorAddr sdk.Address) { +func (k Keeper) updateValidators(ctx sdk.Context, store sdk.KVStore, updatedValidatorAddr sdk.Address) (updatedIsBonded bool) { + updatedIsBonded = false // clear the current validators store, add to the ToKickOut temp store toKickOut := make(map[string][]byte) // map[key]value @@ -240,6 +254,7 @@ func (k Keeper) updateValidators(ctx sdk.Context, store sdk.KVStore, updatedVali if bytes.Equal(updatedValidatorAddr, validator.Address) { bz = k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc)) store.Set(GetTendermintUpdatesKey(updatedValidatorAddr), bz) + updatedIsBonded = true // the updatedValidatorAddr is for a bonded validator } iterator.Next() @@ -257,6 +272,7 @@ func (k Keeper) updateValidators(ctx sdk.Context, store sdk.KVStore, updatedVali bz := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc)) store.Set(GetTendermintUpdatesKey(addr), bz) } + return } //_________________________________________________________________________ @@ -392,11 +408,14 @@ func (k Keeper) setParams(ctx sdk.Context, params Params) { // load/save the pool func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) { + store := ctx.KVStore(k.storeKey) + return k.getPool(store) +} +func (k Keeper) getPool(store sdk.KVStore) (pool Pool) { // check if cached before anything if !k.pool.equal(Pool{}) { return k.pool } - store := ctx.KVStore(k.storeKey) b := store.Get(PoolKey) if b == nil { panic("Stored pool should not have been nil") diff --git a/x/stake/pool.go b/x/stake/pool.go index f22352c29b..e4871f1678 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -20,6 +20,14 @@ func (p Pool) bondedShareExRate() sdk.Rat { return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) } +// get the exchange rate of unbonding tokens held in validators per issued share +func (p Pool) unbondingShareExRate() sdk.Rat { + if p.UnbondingShares.IsZero() { + return sdk.OneRat() + } + return sdk.NewRat(p.UnbondingPool).Quo(p.UnbondingShares) +} + // get the exchange rate of unbonded tokens held in validators per issued share func (p Pool) unbondedShareExRate() sdk.Rat { if p.UnbondedShares.IsZero() { @@ -28,23 +36,38 @@ func (p Pool) unbondedShareExRate() sdk.Rat { return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares) } -// move a validators asset pool from bonded to unbonded pool -func (p Pool) bondedToUnbondedPool(validator Validator) (Pool, Validator) { +// XXX write test +// update the location of the shares within a validator if its bond status has changed +func (p Pool) UpdateSharesLocation(validator Validator) (Pool, Validator) { + var tokens int64 - // replace bonded shares with unbonded shares - p, tokens := p.removeSharesBonded(validator.BondedShares) - p, validator.BondedShares = p.addTokensUnbonded(tokens) - validator.Status = sdk.Unbonded - return p, validator -} + switch { + case !validator.BondedShares.IsZero(): + if validator.Status == sdk.Bonded { // return if nothing needs switching + return p, validator + } + p, tokens = p.removeSharesBonded(validator.BondedShares) + case !validator.UnbondingShares.IsZero(): + if validator.Status == sdk.Unbonding { + return p, validator + } + p, tokens = p.removeSharesUnbonding(validator.BondedShares) + case !validator.UnbondedShares.IsZero(): + if validator.Status == sdk.Unbonding { + return p, validator + } + p, tokens = p.removeSharesUnbonded(validator.BondedShares) + } -// move a validators asset pool from unbonded to bonded pool -func (p Pool) unbondedToBondedPool(validator Validator) (Pool, Validator) { + switch validator.Status { + case sdk.Bonded: + p, validator.BondedShares = p.addTokensBonded(tokens) + case sdk.Unbonding: + p, validator.UnbondingShares = p.addTokensUnbonding(tokens) + case sdk.Unbonded, sdk.Revoked: + p, validator.UnbondedShares = p.addTokensUnbonded(tokens) + } - // replace unbonded shares with bonded shares - p, tokens := p.removeSharesUnbonded(validator.BondedShares) - p, validator.BondedShares = p.addTokensBonded(tokens) - validator.Status = sdk.Bonded return p, validator } @@ -64,6 +87,20 @@ func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) return p, removedTokens } +func (p Pool) addTokensUnbonding(amount int64) (p2 Pool, issuedShares sdk.Rat) { + issuedShares = sdk.NewRat(amount).Quo(p.unbondingShareExRate()) // tokens * (shares/tokens) + p.UnbondingShares = p.UnbondingShares.Add(issuedShares) + p.UnbondingPool += amount + return p, issuedShares +} + +func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.unbondingShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondingShares = p.UnbondingShares.Sub(shares) + p.UnbondingPool -= removedTokens + return p, removedTokens +} + func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { issuedShares = sdk.NewRat(amount).Quo(p.unbondedShareExRate()) // tokens * (shares/tokens) p.UnbondedShares = p.UnbondedShares.Add(issuedShares) diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go index 417a455475..799ee69d24 100644 --- a/x/stake/pool_test.go +++ b/x/stake/pool_test.go @@ -258,7 +258,7 @@ func TestValidatorRemoveShares(t *testing.T) { // generate a random validator func randomValidator(r *rand.Rand) Validator { - var status sdk.ValidatorStatus + var status sdk.BondStatus if r.Float64() < float64(0.5) { status = sdk.Bonded } else { diff --git a/x/stake/types.go b/x/stake/types.go index ebae5aa362..fe4dbc5495 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -126,17 +126,21 @@ func initialPool() Pool { // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Validator struct { - Status sdk.ValidatorStatus `json:"status"` // Bonded status - Address sdk.Address `json:"address"` // Sender of BondTx - UnbondTx returns here - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of validator - BondedShares sdk.Rat `json:"bonded_shares"` // total shares of bonded global hold pool - UnbondingShares sdk.Rat `json:"unbonding_shares"` // total shares of unbonding global hold pool - UnbondedShares sdk.Rat `json:"unbonded_shares"` // total shares of unbonded global hold pool - DelegatorShares sdk.Rat `json:"liabilities"` // total shares issued to a validator's delegators + Status sdk.BondStatus `json:"status"` // bonded status + Address sdk.Address `json:"address"` // sender of BondTx - UnbondTx returns here + PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator - Description Description `json:"description"` // Description terms for the validator - BondHeight int64 `json:"validator_bond_height"` // Earliest height as a bonded validator - BondIntraTxCounter int16 `json:"validator_bond_counter"` // Block-local tx index of validator change + // note: There should only be one of the following 3 shares ever active in a delegator + // multiple terms are only added here for clarity. + BondedShares sdk.Rat `json:"bonded_shares"` // total shares of bonded global hold pool + UnbondingShares sdk.Rat `json:"unbonding_shares"` // total shares of unbonding global hold pool + UnbondedShares sdk.Rat `json:"unbonded_shares"` // total shares of unbonded global hold pool + + DelegatorShares sdk.Rat `json:"liabilities"` // total shares issued to a validator's delegators + + Description Description `json:"description"` // description terms for the validator + BondHeight int64 `json:"validator_bond_height"` // earliest height as a bonded validator + BondIntraTxCounter int16 `json:"validator_bond_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 @@ -246,7 +250,7 @@ func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator { var _ sdk.Validator = Validator{} // nolint - for sdk.Validator -func (v Validator) GetStatus() sdk.ValidatorStatus { return v.Status } +func (v Validator) GetStatus() sdk.BondStatus { return v.Status } func (v Validator) GetAddress() sdk.Address { return v.Address } func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } func (v Validator) GetPower() sdk.Rat { return v.BondedShares }