diff --git a/Gopkg.lock b/Gopkg.lock index 5160d752e904..62f1f7e5b2f2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -239,12 +239,12 @@ version = "v1.0.0" [[projects]] - digest = "1:645110e089152bd0f4a011a2648fbb0e4df5977be73ca605781157ac297f50c4" + digest = "1:e32dfc6abff6a3633ef4d9a1022fd707c8ef26f1e1e8f855dc58dc415ce7c8f3" name = "github.com/mitchellh/mapstructure" packages = ["."] pruneopts = "UT" - revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8" - version = "v1.0.0" + revision = "fe40af7a9c397fa3ddba203c38a5042c5d0475ad" + version = "v1.1.1" [[projects]] digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" @@ -536,7 +536,7 @@ "salsa20/salsa", ] pruneopts = "UT" - revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" + revision = "e3636079e1a4c1f337f212cc5cd2aca108f6c900" [[projects]] digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" @@ -556,14 +556,14 @@ [[projects]] branch = "master" - digest = "1:68023dc297a659d5eb2dafd62eda811456b338c5b3ec3c27da79e8a47d3f456a" + digest = "1:8bc8ecef1d63576cfab4d08b44a1f255dd67e5b019b7a44837d62380f266a91c" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "2f1df4e56cdeb503a08d8577e6f1a7eb12efab82" + revision = "e4b3c5e9061176387e7cea65e4dc5853801f3fb7" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -590,11 +590,11 @@ [[projects]] branch = "master" - digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a" + digest = "1:1e6b0176e8c5dd8ff551af65c76f8b73a99bcf4d812cedff1b91711b7df4804c" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "UT" - revision = "0e822944c569bf5c9afd034adaa56208bd2906ac" + revision = "c7e5094acea1ca1b899e2259d80a6b0f882f81f8" [[projects]] digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" diff --git a/PENDING.md b/PENDING.md index 05c677265b95..18da22592b62 100644 --- a/PENDING.md +++ b/PENDING.md @@ -64,6 +64,7 @@ BREAKING CHANGES * [x/auth] \#2377 auth.StdSignMsg -> txbuilder.StdSignMsg * [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index * [x/staking] \#2236 more distribution hooks for distribution + * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock * Tendermint diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index 28fe0cb5990e..26b32cc93a72 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -45,6 +45,7 @@ func TestTallyNoOneVotes(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5}) + stake.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -69,6 +70,7 @@ func TestTallyOnlyValidatorsAllYes(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5}) + stake.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -98,6 +100,7 @@ func TestTallyOnlyValidators51No(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6}) + stake.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -126,6 +129,7 @@ func TestTallyOnlyValidators51Yes(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) + stake.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -157,6 +161,7 @@ func TestTallyOnlyValidatorsVetoed(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) + stake.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -188,6 +193,7 @@ func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) + stake.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -219,6 +225,7 @@ func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) + stake.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -250,6 +257,7 @@ func TestTallyOnlyValidatorsNonVoter(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) + stake.EndBlocker(ctx, sk) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() @@ -279,6 +287,7 @@ func TestTallyDelgatorOverride(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) + stake.EndBlocker(ctx, sk) delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 30)) stakeHandler(ctx, delegator1Msg) @@ -315,6 +324,7 @@ func TestTallyDelgatorInherit(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) + stake.EndBlocker(ctx, sk) delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 30)) stakeHandler(ctx, delegator1Msg) @@ -349,6 +359,7 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6, 7}) + stake.EndBlocker(ctx, sk) delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 10)) stakeHandler(ctx, delegator1Msg) @@ -402,6 +413,8 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { delegator1Msg2 := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewInt64Coin("steak", 10)) stakeHandler(ctx, delegator1Msg2) + stake.EndBlocker(ctx, sk) + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() proposal.SetStatus(StatusVotingPeriod) @@ -432,6 +445,7 @@ func TestTallyJailedValidator(t *testing.T) { } createValidators(t, stakeHandler, ctx, valAddrs, []int64{25, 6, 7}) + stake.EndBlocker(ctx, sk) delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewInt64Coin("steak", 10)) stakeHandler(ctx, delegator1Msg) @@ -443,6 +457,8 @@ func TestTallyJailedValidator(t *testing.T) { require.True(t, found) sk.Jail(ctx, sdk.ConsAddress(val2.ConsPubKey.Address())) + stake.EndBlocker(ctx, sk) + proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() proposal.SetStatus(StatusVotingPeriod) diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index ee93cfea393a..51705cbef098 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -36,12 +36,8 @@ func initChain(r *rand.Rand, accounts []Account, setups []RandSetup, app *baseap res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, accounts)}) validators = make(map[string]mockValidator) for _, validator := range res.Validators { - pubkey, err := tmtypes.PB2TM.PubKey(validator.PubKey) - if err != nil { - panic(err) - } - address := pubkey.Address() - validators[string(address)] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)} + str := fmt.Sprintf("%v", validator.PubKey) + validators[str] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)} } for i := 0; i < len(setups); i++ { @@ -400,22 +396,23 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValidator, updates []abci.ValidatorUpdate, event func(string)) map[string]mockValidator { for _, update := range updates { + str := fmt.Sprintf("%v", update.PubKey) switch { case update.Power == 0: - if _, ok := current[string(update.PubKey.Data)]; !ok { + if _, ok := current[str]; !ok { tb.Fatalf("tried to delete a nonexistent validator") } event("endblock/validatorupdates/kicked") - delete(current, string(update.PubKey.Data)) + delete(current, str) default: // Does validator already exist? - if mVal, ok := current[string(update.PubKey.Data)]; ok { + if mVal, ok := current[str]; ok { mVal.val = update event("endblock/validatorupdates/updated") } else { // Set this new validator - current[string(update.PubKey.Data)] = mockValidator{update, GetMemberOfInitialState(r, initialLivenessWeightings)} + current[str] = mockValidator{update, GetMemberOfInitialState(r, initialLivenessWeightings)} event("endblock/validatorupdates/added") } } diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 9cdcd05334f0..7aebf0d0bf11 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -45,6 +45,9 @@ func TestJailedValidatorDelegations(t *testing.T) { got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) + // end block + stake.EndBlocker(ctx, stakeKeeper) + // set dummy signing info newInfo := ValidatorSigningInfo{ StartHeight: int64(0), diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 6845f35ce2c2..e32e7af5b314 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -85,13 +85,16 @@ func TestSlashingPeriodCap(t *testing.T) { // double sign less than max age keeper.handleDoubleSign(ctx, valConsPubKey.Address(), 0, time.Unix(0, 0), amtInt) - // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) + // end block + stake.EndBlocker(ctx, sk) // update block height ctx = ctx.WithBlockHeight(int64(1)) // unjail to measure power sk.Unjail(ctx, valConsAddr) + // end block + stake.EndBlocker(ctx, sk) // power should be reduced expectedPower := sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) @@ -100,10 +103,14 @@ func TestSlashingPeriodCap(t *testing.T) { keeper.handleDoubleSign(ctx, valConsPubKey.Address(), 0, time.Unix(0, 0), amtInt) // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) + // end block + stake.EndBlocker(ctx, sk) // update block height ctx = ctx.WithBlockHeight(int64(2)) // unjail to measure power sk.Unjail(ctx, valConsAddr) + // end block + stake.EndBlocker(ctx, sk) // power should be equal, no more should have been slashed expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) @@ -114,6 +121,8 @@ func TestSlashingPeriodCap(t *testing.T) { require.True(t, sk.Validator(ctx, addr).GetJailed()) // unjail to measure power sk.Unjail(ctx, valConsAddr) + // end block + stake.EndBlocker(ctx, sk) // power should be reduced expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(18).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) @@ -180,6 +189,9 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, int64(0), info.StartHeight) require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter) + // end block + stake.EndBlocker(ctx, sk) + // validator should have been jailed validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) @@ -193,6 +205,9 @@ func TestHandleAbsentValidator(t *testing.T) { got = slh(ctx, NewMsgUnjail(addr)) require.True(t, got.IsOK()) + // end block + stake.EndBlocker(ctx, sk) + // validator should be rebonded now validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) @@ -222,12 +237,19 @@ func TestHandleAbsentValidator(t *testing.T) { keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) } + // end block + stake.EndBlocker(ctx, sk) + // validator should be jailed again after 500 unsigned blocks nextHeight = height + keeper.MinSignedPerWindow(ctx) + 1 for ; height <= nextHeight; height++ { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) } + + // end block + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) } @@ -296,6 +318,9 @@ func TestHandleAlreadyJailed(t *testing.T) { keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) } + // end block + stake.EndBlocker(ctx, sk) + // validator should have been jailed and slashed validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index 5f0ccf8ed54e..a98d97c54ce7 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -77,6 +77,9 @@ func TestBeginBlocker(t *testing.T) { BeginBlocker(ctx, req, keeper) } + // end block + stake.EndBlocker(ctx, sk) + // validator should be jailed validator, found := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)) require.True(t, found) diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 740eba415fed..e1f33d94cdff 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -17,7 +17,7 @@ import ( // Returns final validator set after applying all declaration and delegations func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res []abci.ValidatorUpdate, err error) { keeper.SetPool(ctx, data.Pool) - keeper.SetNewParams(ctx, data.Params) + keeper.SetParams(ctx, data.Params) keeper.InitIntraTxCounter(ctx) for i, validator := range data.Validators { @@ -31,26 +31,16 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ return res, errors.Errorf("genesis validator cannot have zero delegator shares, validator: %v", validator) } - // Manually set indexes for the first time + // Manually set indices for the first time keeper.SetValidatorByConsAddr(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) - - if validator.Status == sdk.Bonded { - keeper.SetValidatorBondedIndex(ctx, validator) - } } for _, bond := range data.Bonds { keeper.SetDelegation(ctx, bond) } - keeper.UpdateBondedValidatorsFull(ctx) - - vals := keeper.GetValidatorsBonded(ctx) - res = make([]abci.ValidatorUpdate, len(vals)) - for i, val := range vals { - res[i] = val.ABCIValidatorUpdate() - } + res = keeper.ApplyAndReturnValidatorSetUpdates(ctx) return } diff --git a/x/stake/handler.go b/x/stake/handler.go index 65915a65e591..f3a007fd51d9 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -52,7 +52,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid k.SetIntraTxCounter(ctx, 0) // calculate validator set changes - ValidatorUpdates = k.GetTendermintUpdates(ctx) + ValidatorUpdates = k.ApplyAndReturnValidatorSetUpdates(ctx) return } @@ -82,7 +82,6 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k msg.Commission.Rate, msg.Commission.MaxChangeRate, msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, ) - validator, err := validator.SetInitialCommission(commission) if err != nil { return err.Result() @@ -90,6 +89,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k k.SetValidator(ctx, validator) k.SetValidatorByConsAddr(ctx, validator) + k.SetNewValidatorByPowerIndex(ctx, validator) // move coins from the msg.Address account to a (self-delegation) delegator account // the validator account and global shares are updated within here @@ -130,13 +130,13 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe validator.Description = description if msg.CommissionRate != nil { - if err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate); err != nil { + commission, err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate) + if err != nil { return err.Result() } + validator.Commission = commission } - // We don't need to run through all the power update logic within k.UpdateValidator - // We just need to override the entry in state, since only the description has changed. k.SetValidator(ctx, validator) tags := sdk.NewTags( diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 5780bf5431bb..0e4a2bf65481 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -63,6 +63,10 @@ func TestValidatorByPowerIndex(t *testing.T) { got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + // must end-block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + // verify the self-delegation exists bond, found := keeper.GetDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) require.True(t, found) @@ -83,14 +87,20 @@ func TestValidatorByPowerIndex(t *testing.T) { got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + // must end-block + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + // slash and jail the first validator consAddr0 := sdk.ConsAddress(keep.PKs[0].Address()) keeper.Slash(ctx, consAddr0, 0, initBond, sdk.NewDecWithPrec(5, 1)) keeper.Jail(ctx, consAddr0) + keeper.ApplyAndReturnValidatorSetUpdates(ctx) validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding require.Equal(t, int64(500000), validator.Tokens.RoundInt64()) // ensure tokens slashed + keeper.Unjail(ctx, consAddr0) // the old power record should have been deleted as the power changed require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) @@ -121,6 +131,8 @@ func TestValidatorByPowerIndex(t *testing.T) { got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + EndBlocker(ctx, keeper) + // verify that by power key nolonger exists _, found = keeper.GetValidator(ctx, validatorAddr) require.False(t, found) @@ -136,8 +148,10 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { msgCreateValidator1 := newTestMsgCreateValidator(addr1, pk1, 10) got := handleMsgCreateValidator(ctx, msgCreateValidator1, keeper) require.True(t, got.IsOK(), "%v", got) - validator, found := keeper.GetValidator(ctx, addr1) + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + validator, found := keeper.GetValidator(ctx, addr1) require.True(t, found) assert.Equal(t, sdk.Bonded, validator.Status) assert.Equal(t, addr1, validator.OperatorAddr) @@ -160,6 +174,11 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { msgCreateValidator4 := newTestMsgCreateValidator(addr2, pk2, 10) got = handleMsgCreateValidator(ctx, msgCreateValidator4, keeper) require.True(t, got.IsOK(), "%v", got) + + // must end-block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + validator, found = keeper.GetValidator(ctx, addr2) require.True(t, found) @@ -180,6 +199,11 @@ func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) { msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, 10) got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) require.True(t, got.IsOK(), "%v", got) + + // must end-block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) @@ -211,6 +235,10 @@ func TestLegacyValidatorDelegations(t *testing.T) { got := handleMsgCreateValidator(ctx, msgCreateVal, keeper) require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) + // must end-block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + // verify the validator exists and has the correct attributes validator, found := keeper.GetValidator(ctx, valAddr) require.True(t, found) @@ -302,6 +330,9 @@ func TestIncrementsMsgDelegate(t *testing.T) { got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Bonded, validator.Status) @@ -384,6 +415,9 @@ func TestIncrementsMsgUnbond(t *testing.T) { amt2 := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(denom) require.Equal(t, amt1.Sub(sdk.NewInt(initBond)).Int64(), amt2.Int64(), "expected coins to be subtracted") + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, initBond*2, validator.DelegatorShares.RoundInt64()) @@ -533,11 +567,8 @@ func TestMultipleMsgDelegate(t *testing.T) { // unbond them all for i, delegatorAddr := range delegatorAddrs { msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) - msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) - require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded _, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) @@ -564,11 +595,8 @@ func TestJailValidator(t *testing.T) { // unbond the validators bond portion msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) - msgCompleteUnbondingValidator := NewMsgCompleteUnbonding(sdk.AccAddress(validatorAddr), validatorAddr) got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) - require.True(t, got.IsOK(), "expected no error") - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingValidator, keeper) - require.True(t, got.IsOK(), "expected no error") + require.True(t, got.IsOK(), "expected no error: %v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) @@ -750,16 +778,22 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 1, len(keeper.GetValidatorsBonded(ctx))) msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) // unbond the valdator-2 @@ -767,6 +801,9 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + // because there are extra validators waiting to get in, the queued // validator (aka. validator-1) should make it into the bonded group, thus // the total number of validators should stay the same @@ -777,142 +814,6 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { require.Equal(t, sdk.Bonded, val1.Status, "%v", val1) } -func TestJoiningAsCliffValidator(t *testing.T) { - ctx, _, keeper := keep.CreateTestInput(t, false, 1000) - validatorAddr1, validatorAddr2 := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) - - // make sure that the cliff validator is nil to begin with - cliffVal := keeper.GetCliffValidator(ctx) - require.Equal(t, []byte(nil), cliffVal) - - // set the unbonding time - params := keeper.GetParams(ctx) - params.UnbondingTime = 0 - params.MaxValidators = 2 - keeper.SetParams(ctx, params) - - // add the first validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) - got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - - // cliff validator should still be nil - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, []byte(nil), cliffVal) - - // Add the second validator - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) - got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - - // now that we've reached maximum validators, the val-2 should be added to the cliff (top) - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, validatorAddr2.Bytes(), cliffVal) -} - -func TestJoiningToCreateFirstCliffValidator(t *testing.T) { - ctx, _, keeper := keep.CreateTestInput(t, false, 1000) - validatorAddr1, validatorAddr2 := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) - - // make sure that the cliff validator is nil to begin with - cliffVal := keeper.GetCliffValidator(ctx) - require.Equal(t, []byte(nil), cliffVal) - - // set the unbonding time - params := keeper.GetParams(ctx) - params.UnbondingTime = 0 - params.MaxValidators = 2 - keeper.SetParams(ctx, params) - - // add the first validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) - got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - - // cliff validator should still be nil - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, []byte(nil), cliffVal) - - // Add the second validator - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 60) - got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - - // now that we've reached maximum validators, validator-1 should be added to the cliff (top) - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, validatorAddr1.Bytes(), cliffVal) -} - -func TestCliffValidator(t *testing.T) { - ctx, _, keeper := keep.CreateTestInput(t, false, 1000) - validatorAddr1 := sdk.ValAddress(keep.Addrs[0]) - validatorAddr2 := sdk.ValAddress(keep.Addrs[1]) - validatorAddr3 := sdk.ValAddress(keep.Addrs[2]) - - // make sure that the cliff validator is nil to begin with - cliffVal := keeper.GetCliffValidator(ctx) - require.Equal(t, []byte(nil), cliffVal) - - // set the unbonding time - params := keeper.GetParams(ctx) - params.UnbondingTime = 0 - params.MaxValidators = 2 - keeper.SetParams(ctx, params) - - // add the first validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) - got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - - // cliff validator should still be nil - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, []byte(nil), cliffVal) - - // Add the second validator - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) - got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - - // now that we've reached maximum validators, validator-2 should be added to the cliff (top) - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, validatorAddr2.Bytes(), cliffVal) - - // add the third validator, which should not make it to being bonded, - // so the cliff validator should not change because nobody has been kicked out - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) - got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, validatorAddr2.Bytes(), cliffVal) - - // unbond valdator-2 - msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr2), validatorAddr2, sdk.NewDec(30)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") - - vals := keeper.GetValidatorsBonded(ctx) - require.Equal(t, 2, len(vals)) - - // now the validator set should be updated, - // where val-3 enters the validator set on the cliff - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, validatorAddr3.Bytes(), cliffVal) - - // unbond valdator-1 - msgBeginUnbonding = NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr1), validatorAddr1, sdk.NewDec(50)) - got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") - - // get bonded validators - should just be one - vals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(vals)) - - // cliff now should be empty - cliffVal = keeper.GetCliffValidator(ctx) - require.Equal(t, []byte(nil), cliffVal) -} - func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) valA, valB, del := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]), keep.Addrs[2] @@ -931,6 +832,10 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgDelegate") + // apply Tendermint updates + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + // a block passes ctx = ctx.WithBlockHeight(1) @@ -949,6 +854,10 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { require.True(t, found) require.Equal(t, sdk.NewDec(6), delegation.Shares) + // must apply validator updates + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + // slash the validator by half keeper.Slash(ctx, consAddr0, 0, 20, sdk.NewDecWithPrec(5, 1)) @@ -991,6 +900,9 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { require.True(t, found) require.Equal(t, sdk.NewDec(3), delegation.Shares) + // end blocker + EndBlocker(ctx, keeper) + // validator power should have been reduced to zero // ergo validator should have been removed from the store _, found = keeper.GetValidator(ctx, valA) diff --git a/x/stake/keeper/_store.md b/x/stake/keeper/_store.md index 5c070b9e0930..d569389417c3 100644 --- a/x/stake/keeper/_store.md +++ b/x/stake/keeper/_store.md @@ -41,9 +41,3 @@ prefixed areas of the staking store which are accessed in `x/stake/keeper.go`. The transient store persists between transations but not between blocks -## Tendermint Updates - - Prefix Key Space: TendermintUpdatesTKey - - Key/Sort: Validator Operator Address - - Value: Tendermint ABCI Validator - - Contains: Validators are queued to affect the consensus validation set in Tendermint - - Used For: Informing Tendermint of the validator set updates diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index cc46646a730e..d7e9c57952d6 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -265,18 +265,13 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co } } - pool := k.GetPool(ctx) - validator, pool, newShares = validator.AddTokensFromDel(pool, bondAmt.Amount) - delegation.Shares = delegation.Shares.Add(newShares) + validator, newShares = k.AddValidatorTokensAndShares(ctx, validator, bondAmt.Amount) - // Update delegation height + // Update delegation + delegation.Shares = delegation.Shares.Add(newShares) delegation.Height = ctx.BlockHeight() - - k.SetPool(ctx, pool) k.SetDelegation(ctx, delegation) - k.UpdateValidator(ctx, validator) - - return + return newShares, nil } // unbond the the delegation return @@ -313,8 +308,9 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA // if the delegation is the operator of the validator then // trigger a jail validator - if bytes.Equal(delegation.DelegatorAddr, validator.OperatorAddr) && validator.Jailed == false { - validator.Jailed = true + if bytes.Equal(delegation.DelegatorAddr, validator.OperatorAddr) && !validator.Jailed { + k.jailValidator(ctx, validator) + validator = k.mustGetValidator(ctx, validator.OperatorAddr) } k.RemoveDelegation(ctx, delegation) @@ -325,14 +321,10 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA } // remove the coins from the validator - pool := k.GetPool(ctx) - validator, pool, amount = validator.RemoveDelShares(pool, shares) - - k.SetPool(ctx, pool) + validator, amount = k.RemoveValidatorTokensAndShares(ctx, validator, shares) - // update then remove validator if necessary - validator = k.UpdateValidator(ctx, validator) - if validator.DelegatorShares.IsZero() { + if validator.DelegatorShares.IsZero() && validator.Status != sdk.Bonded { + // if bonded, we must remove in EndBlocker instead k.RemoveValidator(ctx, validator.OperatorAddr) } @@ -368,7 +360,7 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, valSrcAddr sd } } -// complete unbonding an unbonding record +// begin unbonding an unbonding record func (k Keeper) BeginUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) sdk.Error { diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index ed65f1f404b3..863c08a9674c 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -25,9 +25,9 @@ func TestDelegation(t *testing.T) { } keeper.SetPool(ctx, pool) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - validators[2] = keeper.UpdateValidator(ctx, validators[2]) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) // first add a validators[0] to delegate too @@ -184,7 +184,7 @@ func TestUnbondDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) require.Equal(t, int64(10), pool.BondedTokens.RoundInt64()) @@ -226,7 +226,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) selfDelegation := types.Delegation{ DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), @@ -236,10 +236,11 @@ func TestUndelegateSelfDelegation(t *testing.T) { keeper.SetDelegation(ctx, selfDelegation) // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -252,6 +253,10 @@ func TestUndelegateSelfDelegation(t *testing.T) { err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.Equal(t, int64(10), validator.Tokens.RoundInt64()) @@ -269,7 +274,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) selfDelegation := types.Delegation{ DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), @@ -279,10 +284,11 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { keeper.SetDelegation(ctx, selfDelegation) // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -303,6 +309,10 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.Equal(t, blockHeight, validator.UnbondingHeight) @@ -340,7 +350,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -351,10 +361,11 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { keeper.SetDelegation(ctx, selfDelegation) // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -374,6 +385,10 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.Equal(t, blockHeight, validator.UnbondingHeight) @@ -506,7 +521,7 @@ func TestRedelegateSelfDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -520,14 +535,16 @@ func TestRedelegateSelfDelegation(t *testing.T) { validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) + pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(10)) keeper.SetPool(ctx, pool) - validator2 = keeper.UpdateValidator(ctx, validator2) + validator2 = testingUpdateValidator(keeper, ctx, validator2) + require.Equal(t, sdk.Bonded, validator2.Status) // create a second delegation to this validator validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -539,6 +556,10 @@ func TestRedelegateSelfDelegation(t *testing.T) { err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[1], sdk.NewDec(10)) require.NoError(t, err) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.Equal(t, int64(10), validator.Tokens.RoundInt64()) @@ -552,11 +573,12 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { //create a validator with a self-delegation validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator.BondIntraTxCounter = 1 validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -567,10 +589,11 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { keeper.SetDelegation(ctx, selfDelegation) // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -581,10 +604,11 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { // create a second validator validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + validator2.BondIntraTxCounter = 2 validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator2 = keeper.UpdateValidator(ctx, validator2) + validator2 = testingUpdateValidator(keeper, ctx, validator2) header := ctx.BlockHeader() blockHeight := int64(10) @@ -597,6 +621,10 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.Equal(t, blockHeight, validator.UnbondingHeight) @@ -634,7 +662,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -645,10 +673,12 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { keeper.SetDelegation(ctx, selfDelegation) // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + validator.BondIntraTxCounter = 1 require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -659,10 +689,12 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { // create a second validator validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + validator2.BondIntraTxCounter = 2 validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator2 = keeper.UpdateValidator(ctx, validator2) + validator2 = testingUpdateValidator(keeper, ctx, validator2) + require.Equal(t, sdk.Bonded, validator2.Status) header := ctx.BlockHeader() blockHeight := int64(10) @@ -675,6 +707,10 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.Equal(t, blockHeight, validator.UnbondingHeight) diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 82170a4aedb1..007c2a5de76f 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -64,25 +64,9 @@ func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { return } -// Need a distinct function because setParams depends on an existing previous -// record of params to exist (to check if maxValidators has changed) - and we -// panic on retrieval if it doesn't exist - hence if we use setParams for the very -// first params set it will panic. -func (k Keeper) SetNewParams(ctx sdk.Context, params types.Params) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - // set the params func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { store := ctx.KVStore(k.storeKey) - exParams := k.GetParams(ctx) - - // if max validator count changes, must recalculate validator set - if exParams.MaxValidators != params.MaxValidators { - k.UpdateBondedValidatorsFull(ctx) - } b := k.cdc.MustMarshalBinary(params) store.Set(ParamKey, b) } diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 91e6a69708ca..02d54e2b71ae 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -18,18 +18,13 @@ var ( ValidatorsByConsAddrKey = []byte{0x03} // prefix for each key to a validator index, by pubkey ValidatorsBondedIndexKey = []byte{0x04} // prefix for each key to a validator index, for bonded validators ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power - ValidatorCliffIndexKey = []byte{0x06} // key for the validator index of the cliff validator - ValidatorPowerCliffKey = []byte{0x07} // key for the power of the validator on the cliff - IntraTxCounterKey = []byte{0x08} // key for intra-block tx index - DelegationKey = []byte{0x09} // key for a delegation - UnbondingDelegationKey = []byte{0x0A} // key for an unbonding-delegation - UnbondingDelegationByValIndexKey = []byte{0x0B} // prefix for each key for an unbonding-delegation, by validator operator - RedelegationKey = []byte{0x0C} // key for a redelegation - RedelegationByValSrcIndexKey = []byte{0x0D} // prefix for each key for an redelegation, by source validator operator - RedelegationByValDstIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by destination validator operator - - // Keys for store prefixes (transient) - TendermintUpdatesTKey = []byte{0x00} // prefix for each key to a validator which is being updated + IntraTxCounterKey = []byte{0x06} // key for intra-block tx index + DelegationKey = []byte{0x07} // key for a delegation + UnbondingDelegationKey = []byte{0x08} // key for an unbonding-delegation + UnbondingDelegationByValIndexKey = []byte{0x09} // prefix for each key for an unbonding-delegation, by validator operator + RedelegationKey = []byte{0x0A} // key for a redelegation + RedelegationByValSrcIndexKey = []byte{0x0B} // prefix for each key for an redelegation, by source validator operator + RedelegationByValDstIndexKey = []byte{0x0C} // prefix for each key for an redelegation, by destination validator operator ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -46,12 +41,6 @@ func GetValidatorByConsAddrKey(addr sdk.ConsAddress) []byte { return append(ValidatorsByConsAddrKey, addr.Bytes()...) } -// gets the key for the current validator group -// VALUE: none (key rearrangement with GetValKeyFromValBondedIndexKey) -func GetValidatorsBondedIndexKey(operatorAddr sdk.ValAddress) []byte { - return append(ValidatorsBondedIndexKey, operatorAddr.Bytes()...) -} - // Get the validator operator address from ValBondedIndexKey func GetAddressFromValBondedIndexKey(IndexKey []byte) []byte { return IndexKey[1:] // remove prefix bytes @@ -66,6 +55,11 @@ func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) [] return getValidatorPowerRank(validator, pool) } +// get the bonded validator index key for an operator address +func GetBondedValidatorIndexKey(operator sdk.ValAddress) []byte { + return append(ValidatorsBondedIndexKey, operator...) +} + // get the power ranking of a validator // NOTE the larger values are of higher value // nolint: unparam @@ -74,34 +68,19 @@ func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { potentialPower := validator.Tokens powerBytes := []byte(potentialPower.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) - jailedBytes := make([]byte, 1) - if validator.Jailed { - jailedBytes[0] = byte(0x00) - } else { - jailedBytes[0] = byte(0x01) - } - // heightBytes and counterBytes represent strings like powerBytes does heightBytes := make([]byte, binary.MaxVarintLen64) binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) counterBytes := make([]byte, 2) binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) - return append(append(append(append( + return append(append(append( ValidatorsByPowerIndexKey, - jailedBytes...), powerBytes...), heightBytes...), counterBytes...) } -// get the key for the accumulated update validators -// VALUE: abci.Validator -// note records using these keys should never persist between blocks -func GetTendermintUpdatesTKey(operatorAddr sdk.ValAddress) []byte { - return append(TendermintUpdatesTKey, operatorAddr.Bytes()...) -} - //______________________________________________________________________________ // gets the key for delegator bond with validator diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index 7bfe4eb25428..dc57dc5dd47b 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -95,20 +95,18 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh } } - // Cannot decrease balance below zero + // cannot decrease balance below zero tokensToBurn := sdk.MinDec(remainingSlashAmount, validator.Tokens) - // burn validator's tokens + // burn validator's tokens and update the validator + validator = k.RemoveValidatorTokens(ctx, validator, tokensToBurn) pool := k.GetPool(ctx) - validator, pool = validator.RemoveTokens(pool, tokensToBurn) pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) k.SetPool(ctx, pool) - // update the validator, possibly kicking it out - validator = k.UpdateValidator(ctx, validator) - // remove validator if it has no more tokens - if validator.Tokens.IsZero() { + if validator.Tokens.IsZero() && validator.Status != sdk.Bonded { + // if bonded, we must remove in ApplyAndReturnValidatorSetUpdates instead k.RemoveValidator(ctx, validator.OperatorAddr) } @@ -123,7 +121,8 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh // jail a validator func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { - k.setJailed(ctx, consAddr, true) + validator := k.mustGetValidatorByConsAddr(ctx, consAddr) + k.jailValidator(ctx, validator) logger := ctx.Logger().With("module", "x/stake") logger.Info(fmt.Sprintf("validator %s jailed", consAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 @@ -132,24 +131,14 @@ func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { // unjail a validator func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) { - k.setJailed(ctx, consAddr, false) + validator := k.mustGetValidatorByConsAddr(ctx, consAddr) + k.unjailValidator(ctx, validator) logger := ctx.Logger().With("module", "x/stake") logger.Info(fmt.Sprintf("validator %s unjailed", consAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return } -// set the jailed flag on a validator -func (k Keeper) setJailed(ctx sdk.Context, consAddr sdk.ConsAddress, isJailed bool) { - validator, found := k.GetValidatorByConsAddr(ctx, consAddr) - if !found { - panic(fmt.Errorf("validator with consensus-Address %s not found, cannot set jailed to %v", consAddr, isJailed)) - } - validator.Jailed = isJailed - k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it - return -} - // slash an unbonding delegation and update the pool // return the amount that would have been slashed assuming // the unbonding delegation had enough stake to slash diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go index 0a6cccac2abf..7959679c6fbf 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/stake/keeper/slash_test.go @@ -26,8 +26,10 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { for i := 0; i < numVals; i++ { validator := types.NewValidator(addrVals[i], PKs[i], types.Description{}) validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(amt)) + validator.BondIntraTxCounter = int16(i) + pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(amt)) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) keeper.SetValidatorByConsAddr(ctx, validator) } pool = keeper.GetPool(ctx) @@ -161,6 +163,10 @@ func TestSlashRedelegation(t *testing.T) { rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + // initialbalance unchanged require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), rd.InitialBalance) @@ -201,6 +207,11 @@ func TestSlashValidatorAtCurrentHeight(t *testing.T) { require.True(t, found) newPool := keeper.GetPool(ctx) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) + + validator = keeper.mustGetValidator(ctx, validator.OperatorAddr) // power decreased require.Equal(t, sdk.NewDec(5), validator.GetPower()) // pool bonded shares decreased @@ -232,6 +243,10 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { require.True(t, found) keeper.Slash(ctx, consAddr, 10, 10, fraction) + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + // read updating unbonding delegation ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) @@ -301,6 +316,8 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { newPool = keeper.GetPool(ctx) // just 1 bonded token burned again since that's all the validator now has require.Equal(t, int64(10), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) // read updated validator // power decreased by 1 again, validator is out of stake // ergo validator should have been removed from the store @@ -402,6 +419,8 @@ func TestSlashWithRedelegation(t *testing.T) { newPool = keeper.GetPool(ctx) // four more bonded tokens burned require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) // read updated validator // validator decreased to zero power, should have been removed from the store _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index 03d4642b671b..8f5b2fa3967f 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -111,7 +111,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context ck := bank.NewBaseKeeper(accountMapper) keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) - keeper.SetNewParams(ctx, types.DefaultParams()) + keeper.SetParams(ctx, types.DefaultParams()) keeper.InitIntraTxCounter(ctx) // fill all the addresses with some coins, set the loose pool tokens simultaneously @@ -202,5 +202,22 @@ func createTestPubKeys(numPubKeys int) []crypto.PubKey { // does a certain by-power index record exist func ValidatorByPowerIndexExists(ctx sdk.Context, keeper Keeper, power []byte) bool { store := ctx.KVStore(keeper.storeKey) - return store.Get(power) != nil + return store.Has(power) +} + +func testingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Validator) types.Validator { + pool := keeper.GetPool(ctx) + keeper.SetValidator(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, pool) + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator, found := keeper.GetValidator(ctx, validator.OperatorAddr) + if !found { + panic("validator expected but not found") + } + return validator +} + +func validatorByPowerIndexExists(k Keeper, ctx sdk.Context, power []byte) bool { + store := ctx.KVStore(k.storeKey) + return store.Has(power) } diff --git a/x/stake/keeper/val_state_change.go b/x/stake/keeper/val_state_change.go new file mode 100644 index 000000000000..f9cf6e463fb6 --- /dev/null +++ b/x/stake/keeper/val_state_change.go @@ -0,0 +1,268 @@ +package keeper + +import ( + "bytes" + "fmt" + "sort" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// Apply and return accumulated updates to the bonded validator set +// +// CONTRACT: Only validators with non-zero power or zero-power that were bonded +// at the previous block height or were removed from the validator set entirely +// are returned to Tendermint. +func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []abci.ValidatorUpdate) { + + store := ctx.KVStore(k.storeKey) + maxValidators := k.GetParams(ctx).MaxValidators + + // retrieve last validator set + last := k.retrieveLastValidatorSet(ctx) + + // iterate over validators, highest power to lowest + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) + count := 0 + for ; iterator.Valid() && count < int(maxValidators); iterator.Next() { + + // fetch the validator + operator := sdk.ValAddress(iterator.Value()) + validator := k.mustGetValidator(ctx, operator) + + if validator.Jailed { + panic("should never retrieve a jailed validator from the power store") + } + + // if we get to a zero-power validator (which we don't bond), + // there are no more possible bonded validators + // note: we must check the ABCI power, since we round before sending to Tendermint + if validator.Tokens.RoundInt64() == int64(0) { + break + } + + // apply the appropriate state change if necessary + switch validator.Status { + case sdk.Unbonded: + validator = k.unbondedToBonded(ctx, validator) + case sdk.Unbonding: + validator = k.unbondingToBonded(ctx, validator) + case sdk.Bonded: + // no state change + default: + panic("unexpected validator status") + } + + // fetch the old power bytes + var operatorBytes [sdk.AddrLen]byte + copy(operatorBytes[:], operator[:]) + oldPowerBytes, found := last[operatorBytes] + + // calculate the new power bytes + newPowerBytes := validator.ABCIValidatorPowerBytes(k.cdc) + + // update the validator set if power has changed + if !found || !bytes.Equal(oldPowerBytes, newPowerBytes) { + updates = append(updates, validator.ABCIValidatorUpdate()) + } + + // validator still in the validator set, so delete from the copy + delete(last, operatorBytes) + + // set the bonded validator index + store.Set(GetBondedValidatorIndexKey(operator), newPowerBytes) + + // keep count + count++ + + } + + // sort the no-longer-bonded validators + noLongerBonded := k.sortNoLongerBonded(last) + + // iterate through the sorted no-longer-bonded validators + for _, operator := range noLongerBonded { + + // fetch the validator + validator := k.mustGetValidator(ctx, sdk.ValAddress(operator)) + + // bonded to unbonding + k.bondedToUnbonding(ctx, validator) + + // remove validator if it has no more tokens + if validator.Tokens.IsZero() { + k.RemoveValidator(ctx, validator.OperatorAddr) + } + + // delete from the bonded validator index + store.Delete(GetBondedValidatorIndexKey(operator)) + + // update the validator set + updates = append(updates, validator.ABCIValidatorUpdateZero()) + + } + + return updates +} + +// Validator state transitions + +func (k Keeper) bondedToUnbonding(ctx sdk.Context, validator types.Validator) types.Validator { + if validator.Status != sdk.Bonded { + panic(fmt.Sprintf("bad state transition bondedToUnbonding, validator: %v\n", validator)) + } + return k.beginUnbondingValidator(ctx, validator) +} + +func (k Keeper) unbondingToBonded(ctx sdk.Context, validator types.Validator) types.Validator { + if validator.Status != sdk.Unbonding { + panic(fmt.Sprintf("bad state transition unbondingToBonded, validator: %v\n", validator)) + } + return k.bondValidator(ctx, validator) +} + +func (k Keeper) unbondedToBonded(ctx sdk.Context, validator types.Validator) types.Validator { + if validator.Status != sdk.Unbonded { + panic(fmt.Sprintf("bad state transition unbondedToBonded, validator: %v\n", validator)) + } + return k.bondValidator(ctx, validator) +} + +func (k Keeper) unbondingToUnbonded(ctx sdk.Context, validator types.Validator) types.Validator { + if validator.Status != sdk.Unbonded { + panic(fmt.Sprintf("bad state transition unbondingToBonded, validator: %v\n", validator)) + } + return k.completeUnbondingValidator(ctx, validator) +} + +// send a validator to jail +func (k Keeper) jailValidator(ctx sdk.Context, validator types.Validator) { + if validator.Jailed { + panic(fmt.Sprintf("cannot jail already jailed validator, validator: %v\n", validator)) + } + + pool := k.GetPool(ctx) + validator.Jailed = true + k.SetValidator(ctx, validator) + k.DeleteValidatorByPowerIndex(ctx, validator, pool) +} + +// remove a validator from jail +func (k Keeper) unjailValidator(ctx sdk.Context, validator types.Validator) { + if !validator.Jailed { + panic(fmt.Sprintf("cannot unjail already unjailed validator, validator: %v\n", validator)) + } + + pool := k.GetPool(ctx) + validator.Jailed = false + k.SetValidator(ctx, validator) + k.SetValidatorByPowerIndex(ctx, validator, pool) +} + +// perform all the store operations for when a validator status becomes bonded +func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types.Validator { + + pool := k.GetPool(ctx) + + k.DeleteValidatorByPowerIndex(ctx, validator, pool) + + validator.BondHeight = ctx.BlockHeight() + + // set the status + validator, pool = validator.UpdateStatus(pool, sdk.Bonded) + k.SetPool(ctx, pool) + + // save the now bonded validator record to the three referenced stores + k.SetValidator(ctx, validator) + + k.SetValidatorByPowerIndex(ctx, validator, pool) + + // call the bond hook if present + if k.hooks != nil { + k.hooks.OnValidatorBonded(ctx, validator.ConsAddress()) + } + + return validator +} + +// perform all the store operations for when a validator status begins unbonding +func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validator) types.Validator { + + pool := k.GetPool(ctx) + params := k.GetParams(ctx) + + k.DeleteValidatorByPowerIndex(ctx, validator, pool) + + // sanity check + if validator.Status != sdk.Bonded { + panic(fmt.Sprintf("should not already be unbonded or unbonding, validator: %v\n", validator)) + } + + // set the status + validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) + k.SetPool(ctx, pool) + + validator.UnbondingMinTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) + validator.UnbondingHeight = ctx.BlockHeader().Height + + // save the now unbonded validator record + k.SetValidator(ctx, validator) + + k.SetValidatorByPowerIndex(ctx, validator, pool) + + // call the unbond hook if present + if k.hooks != nil { + k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) + } + + return validator +} + +// perform all the store operations for when a validator status becomes unbonded +func (k Keeper) completeUnbondingValidator(ctx sdk.Context, validator types.Validator) types.Validator { + pool := k.GetPool(ctx) + validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + k.SetPool(ctx, pool) + k.SetValidator(ctx, validator) + return validator +} + +// map of operator addresses to serialized power +type validatorsByAddr map[[sdk.AddrLen]byte][]byte + +// retrieve the last validator set +func (k Keeper) retrieveLastValidatorSet(ctx sdk.Context) validatorsByAddr { + last := make(validatorsByAddr) + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + for ; iterator.Valid(); iterator.Next() { + var operator [sdk.AddrLen]byte + copy(operator[:], iterator.Key()[1:]) + powerBytes := iterator.Value() + last[operator] = make([]byte, len(powerBytes)) + copy(last[operator][:], powerBytes[:]) + } + return last +} + +// given a map of remaining validators to previous bonded power +// returns the list of validators to be unbonded, sorted by operator address +func (k Keeper) sortNoLongerBonded(last validatorsByAddr) [][]byte { + // sort the map keys for determinism + noLongerBonded := make([][]byte, len(last)) + index := 0 + for operatorBytes := range last { + operator := make([]byte, sdk.AddrLen) + copy(operator[:], operatorBytes[:]) + noLongerBonded[index] = operator + index++ + } + // sorted by address - order doesn't matter + sort.SliceStable(noLongerBonded, func(i, j int) bool { + return bytes.Compare(noLongerBonded[i], noLongerBonded[j]) == -1 + }) + return noLongerBonded +} diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 668a2af23289..b405312a1b2d 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -1,13 +1,9 @@ package keeper import ( - "bytes" "container/list" "fmt" - abci "github.com/tendermint/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -58,6 +54,14 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty return validator, true } +func (k Keeper) mustGetValidator(ctx sdk.Context, addr sdk.ValAddress) types.Validator { + validator, found := k.GetValidator(ctx, addr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %X\n", addr)) + } + return validator +} + // get a single validator by consensus address func (k Keeper) GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator types.Validator, found bool) { store := ctx.KVStore(k.storeKey) @@ -68,6 +72,16 @@ func (k Keeper) GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress return k.GetValidator(ctx, opAddr) } +func (k Keeper) mustGetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) types.Validator { + validator, found := k.GetValidatorByConsAddr(ctx, consAddr) + if !found { + panic(fmt.Errorf("validator with consensus-Address %s not found", consAddr)) + } + return validator +} + +//___________________________________________________________________________ + // set the main record holding validator details func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) @@ -84,23 +98,110 @@ func (k Keeper) SetValidatorByConsAddr(ctx sdk.Context, validator types.Validato // validator index func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Validator, pool types.Pool) { + // jailed validators are not kept in the power index + if validator.Jailed { + return + } store := ctx.KVStore(k.storeKey) store.Set(GetValidatorsByPowerIndexKey(validator, pool), validator.OperatorAddr) } // validator index -func (k Keeper) SetValidatorBondedIndex(ctx sdk.Context, validator types.Validator) { +func (k Keeper) DeleteValidatorByPowerIndex(ctx sdk.Context, validator types.Validator, pool types.Pool) { store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorsBondedIndexKey(validator.OperatorAddr), []byte{}) + store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) } -// used in testing -func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool { +// validator index +func (k Keeper) SetNewValidatorByPowerIndex(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) - return store.Get(power) != nil + pool := k.GetPool(ctx) + store.Set(GetValidatorsByPowerIndexKey(validator, pool), validator.OperatorAddr) +} + +//___________________________________________________________________________ + +// Update the tokens of an existing validator, update the validators power index key +func (k Keeper) AddValidatorTokensAndShares(ctx sdk.Context, validator types.Validator, + tokensToAdd sdk.Int) (valOut types.Validator, addedShares sdk.Dec) { + + pool := k.GetPool(ctx) + k.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, addedShares = validator.AddTokensFromDel(pool, tokensToAdd) + // increment the intra-tx counter + // in case of a conflict, the validator which least recently changed power takes precedence + counter := k.GetIntraTxCounter(ctx) + validator.BondIntraTxCounter = counter + k.SetIntraTxCounter(ctx, counter+1) + k.SetValidator(ctx, validator) + k.SetPool(ctx, pool) + k.SetValidatorByPowerIndex(ctx, validator, pool) + return validator, addedShares +} + +// Update the tokens of an existing validator, update the validators power index key +func (k Keeper) RemoveValidatorTokensAndShares(ctx sdk.Context, validator types.Validator, + sharesToRemove sdk.Dec) (valOut types.Validator, removedTokens sdk.Dec) { + + pool := k.GetPool(ctx) + k.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, removedTokens = validator.RemoveDelShares(pool, sharesToRemove) + k.SetValidator(ctx, validator) + k.SetPool(ctx, pool) + k.SetValidatorByPowerIndex(ctx, validator, pool) + return validator, removedTokens +} + +// Update the tokens of an existing validator, update the validators power index key +func (k Keeper) RemoveValidatorTokens(ctx sdk.Context, validator types.Validator, tokensToRemove sdk.Dec) types.Validator { + pool := k.GetPool(ctx) + k.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool = validator.RemoveTokens(pool, tokensToRemove) + k.SetValidator(ctx, validator) + k.SetPool(ctx, pool) + k.SetValidatorByPowerIndex(ctx, validator, pool) + return validator +} + +// UpdateValidatorCommission attempts to update a validator's commission rate. +// An error is returned if the new commission rate is invalid. +func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, validator types.Validator, newRate sdk.Dec) (types.Commission, sdk.Error) { + commission := validator.Commission + blockTime := ctx.BlockHeader().Time + + if err := commission.ValidateNewRate(newRate, blockTime); err != nil { + return commission, err + } + + commission.Rate = newRate + commission.UpdateTime = blockTime + + return commission, nil +} + +// remove the validator record and associated indexes +// except for the bonded validator index which is only handled in ApplyAndReturnTendermintUpdates +func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { + + // first retrieve the old validator record + validator, found := k.GetValidator(ctx, address) + if !found { + return + } + + // delete the old validator record + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + store.Delete(GetValidatorKey(address)) + store.Delete(GetValidatorByConsAddrKey(sdk.ConsAddress(validator.ConsPubKey.Address()))) + store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) + } -// Get the set of all validators with no limits, used during genesis dump +//___________________________________________________________________________ +// get groups of validators + +// get the set of all validators with no limits, used during genesis dump func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) @@ -132,8 +233,6 @@ func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve uint16) (validators [ return validators[:i] // trim if the array length < maxRetrieve } -//___________________________________________________________________________ - // get the group of the bonded validators func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validator) { store := ctx.KVStore(k.storeKey) @@ -153,8 +252,7 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") } address := GetAddressFromValBondedIndexKey(iterator.Key()) - validator, found := k.GetValidator(ctx, address) - ensureValidatorFound(found, address) + validator := k.mustGetValidator(ctx, address) validators[i] = validator i++ @@ -163,9 +261,7 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat } // get the group of bonded validators sorted by power-rank -// -// TODO: Rename to GetBondedValidatorsByPower or GetValidatorsByPower(ctx, status) -func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { +func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { store := ctx.KVStore(k.storeKey) maxValidators := k.GetParams(ctx).MaxValidators validators := make([]types.Validator, maxValidators) @@ -176,8 +272,7 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { i := 0 for ; iterator.Valid() && i < int(maxValidators); iterator.Next() { address := iterator.Value() - validator, found := k.GetValidator(ctx, address) - ensureValidatorFound(found, address) + validator := k.mustGetValidator(ctx, address) if validator.Status == sdk.Bonded { validators[i] = validator @@ -186,554 +281,3 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { } return validators[:i] // trim } - -//_________________________________________________________________________ -// Accumulated updates to the active/bonded validator set for tendermint - -// get the most recently updated validators -// -// CONTRACT: Only validators with non-zero power or zero-power that were bonded -// at the previous block height or were removed from the validator set entirely -// are returned to Tendermint. -func (k Keeper) GetTendermintUpdates(ctx sdk.Context) (updates []abci.ValidatorUpdate) { - tstore := ctx.TransientStore(k.storeTKey) - - iterator := sdk.KVStorePrefixIterator(tstore, TendermintUpdatesTKey) - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var abciVal abci.ValidatorUpdate - - abciValBytes := iterator.Value() - k.cdc.MustUnmarshalBinary(abciValBytes, &abciVal) - - pub, err := tmtypes.PB2TM.PubKey(abciVal.GetPubKey()) - if err != nil { - panic(err) - } - val, found := k.GetValidator(ctx, sdk.ValAddress(pub.Address())) - if found { - // The validator is new or already exists in the store and must adhere to - // Tendermint invariants. - prevBonded := val.BondHeight < ctx.BlockHeight() && val.BondHeight > val.UnbondingHeight - zeroPower := val.GetPower().Equal(sdk.ZeroDec()) - - if !zeroPower || zeroPower && prevBonded { - updates = append(updates, abciVal) - } - } else { - // Add the ABCI validator in such a case where the validator was removed - // from the store as it must have existed before. - updates = append(updates, abciVal) - } - } - return -} - -//___________________________________________________________________________ - -// Perform all the necessary steps for when a validator changes its power. This -// function updates all validator stores as well as tendermint update store. -// It may kick out validators if a new validator is entering the bonded validator -// group. -// -// TODO: Remove above nolint, function needs to be simplified! -func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator { - tstore := ctx.TransientStore(k.storeTKey) - pool := k.GetPool(ctx) - oldValidator, oldFound := k.GetValidator(ctx, validator.OperatorAddr) - - validator = k.updateForJailing(ctx, oldFound, oldValidator, validator) - powerIncreasing := k.getPowerIncreasing(ctx, oldFound, oldValidator, validator) - validator.BondHeight, validator.BondIntraTxCounter = k.bondIncrement(ctx, oldFound, oldValidator) - valPower := k.updateValidatorPower(ctx, oldFound, oldValidator, validator, pool) - cliffPower := k.GetCliffValidatorPower(ctx) - cliffValExists := (cliffPower != nil) - var valPowerLTcliffPower bool - if cliffValExists { - valPowerLTcliffPower = (bytes.Compare(valPower, cliffPower) == -1) - } - - switch { - - // if the validator is already bonded and the power is increasing, we need - // perform the following: - // a) update Tendermint - // b) check if the cliff validator needs to be updated - case powerIncreasing && !validator.Jailed && - (oldFound && oldValidator.Status == sdk.Bonded): - - bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdate()) - tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bz) - - if cliffValExists { - cliffAddr := sdk.ValAddress(k.GetCliffValidator(ctx)) - if bytes.Equal(cliffAddr, validator.OperatorAddr) { - k.updateCliffValidator(ctx, validator) - } - } - - // if is a new validator and the new power is less than the cliff validator - case cliffValExists && !oldFound && valPowerLTcliffPower: - // skip to completion - - // if was unbonded and the new power is less than the cliff validator - case cliffValExists && - (oldFound && oldValidator.Status == sdk.Unbonded) && - valPowerLTcliffPower: //(valPower < cliffPower - // skip to completion - - default: - // default case - validator was either: - // a) not-bonded and now has power-rank greater than cliff validator - // b) bonded and now has decreased in power - - // update the validator set for this validator - updatedVal, updated := k.UpdateBondedValidators(ctx, validator) - if updated { - // the validator has changed bonding status - validator = updatedVal - break - } - - // if decreased in power but still bonded, update Tendermint validator - if oldFound && oldValidator.BondedTokens().GT(validator.BondedTokens()) { - bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdate()) - tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bz) - } - } - - k.SetValidator(ctx, validator) - return validator -} - -// updateCliffValidator determines if the current cliff validator needs to be -// updated or swapped. If the provided affected validator is the current cliff -// validator before it's power was increased, either the cliff power key will -// be updated or if it's power is greater than the next bonded validator by -// power, it'll be swapped. -func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validator) { - var newCliffVal types.Validator - - store := ctx.KVStore(k.storeKey) - pool := k.GetPool(ctx) - cliffAddr := sdk.ValAddress(k.GetCliffValidator(ctx)) - - oldCliffVal, found := k.GetValidator(ctx, cliffAddr) - if !found { - panic(fmt.Sprintf("cliff validator record not found for address: %X\n", cliffAddr)) - } - - // Create a validator iterator ranging from smallest to largest by power - // starting the current cliff validator's power. - start := GetValidatorsByPowerIndexKey(oldCliffVal, pool) - end := sdk.PrefixEndBytes(ValidatorsByPowerIndexKey) - iterator := store.Iterator(start, end) - - if iterator.Valid() { - ownerAddr := iterator.Value() - currVal, found := k.GetValidator(ctx, ownerAddr) - ensureValidatorFound(found, ownerAddr) - - if currVal.Status != sdk.Bonded || currVal.Jailed { - panic(fmt.Sprintf("unexpected jailed or unbonded validator for address: %X\n", ownerAddr)) - } - - newCliffVal = currVal - iterator.Close() - } else { - panic("failed to create valid validator power iterator") - } - - affectedValRank := GetValidatorsByPowerIndexKey(affectedVal, pool) - newCliffValRank := GetValidatorsByPowerIndexKey(newCliffVal, pool) - - if bytes.Equal(affectedVal.OperatorAddr, newCliffVal.OperatorAddr) { - // The affected validator remains the cliff validator, however, since - // the store does not contain the new power, update the new power rank. - store.Set(ValidatorPowerCliffKey, affectedValRank) - } else if bytes.Compare(affectedValRank, newCliffValRank) > 0 { - // The affected validator no longer remains the cliff validator as it's - // power is greater than the new cliff validator. - k.setCliffValidator(ctx, newCliffVal, pool) - } else { - panic("invariant broken: the cliff validator should change or it should remain the same") - } -} - -func (k Keeper) updateForJailing(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator { - if newValidator.Jailed && oldFound && oldValidator.Status == sdk.Bonded { - newValidator = k.beginUnbondingValidator(ctx, newValidator) - - // need to also clear the cliff validator spot because the jail has - // opened up a new spot which will be filled when - // updateValidatorsBonded is called - k.clearCliffValidator(ctx) - } - return newValidator -} - -// nolint: unparam -func (k Keeper) getPowerIncreasing(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) bool { - if oldFound && oldValidator.BondedTokens().LT(newValidator.BondedTokens()) { - return true - } - return false -} - -// get the bond height and incremented intra-tx counter -// nolint: unparam -func (k Keeper) bondIncrement( - ctx sdk.Context, found bool, oldValidator types.Validator) (height int64, intraTxCounter int16) { - - // if already a validator, copy the old block height and counter - if found && oldValidator.Status == sdk.Bonded { - height = oldValidator.BondHeight - intraTxCounter = oldValidator.BondIntraTxCounter - return - } - - height = ctx.BlockHeight() - counter := k.GetIntraTxCounter(ctx) - intraTxCounter = counter - - k.SetIntraTxCounter(ctx, counter+1) - return -} - -func (k Keeper) updateValidatorPower(ctx sdk.Context, oldFound bool, oldValidator, - newValidator types.Validator, pool types.Pool) (valPower []byte) { - store := ctx.KVStore(k.storeKey) - - // update the list ordered by voting power - if oldFound { - store.Delete(GetValidatorsByPowerIndexKey(oldValidator, pool)) - } - valPower = GetValidatorsByPowerIndexKey(newValidator, pool) - store.Set(valPower, newValidator.OperatorAddr) - - return valPower -} - -// Update the bonded validator group based on a change to the validator -// affectedValidator. This function potentially adds the affectedValidator to -// the bonded validator group which kicks out the cliff validator. Under this -// situation this function returns the updated affectedValidator. -// -// The correct bonded subset of validators is retrieved by iterating through an -// index of the validators sorted by power, stored using the -// ValidatorsByPowerIndexKey. Simultaneously the current validator records are -// updated in store with the ValidatorsBondedIndexKey. This store is used to -// determine if a validator is a validator without needing to iterate over all -// validators. -func (k Keeper) UpdateBondedValidators( - ctx sdk.Context, affectedValidator types.Validator) ( - updatedVal types.Validator, updated bool) { - - store := ctx.KVStore(k.storeKey) - - oldCliffValidatorAddr := k.GetCliffValidator(ctx) - maxValidators := k.GetParams(ctx).MaxValidators - bondedValidatorsCount := 0 - var validator, validatorToBond types.Validator - newValidatorBonded := false - - // create a validator iterator ranging from largest to smallest by power - iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) - for ; iterator.Valid() && bondedValidatorsCount < int(maxValidators); iterator.Next() { - - // either retrieve the original validator from the store, or under the - // situation that this is the "affected validator" just use the - // validator provided because it has not yet been updated in the store - ownerAddr := iterator.Value() - if bytes.Equal(ownerAddr, affectedValidator.OperatorAddr) { - validator = affectedValidator - } else { - var found bool - validator, found = k.GetValidator(ctx, ownerAddr) - ensureValidatorFound(found, ownerAddr) - } - - // if we've reached jailed validators no further bonded validators exist - if validator.Jailed { - if validator.Status == sdk.Bonded { - panic(fmt.Sprintf("jailed validator cannot be bonded, address: %X\n", ownerAddr)) - } - - break - } - - // increment the total number of bonded validators and potentially mark - // the validator to bond - if validator.Status != sdk.Bonded { - validatorToBond = validator - if newValidatorBonded { - panic("already decided to bond a validator, can't bond another!") - } - newValidatorBonded = true - } - - bondedValidatorsCount++ - } - - iterator.Close() - - if newValidatorBonded && bytes.Equal(oldCliffValidatorAddr, validator.OperatorAddr) { - panic("cliff validator has not been changed, yet we bonded a new validator") - } - - // clear or set the cliff validator - if bondedValidatorsCount == int(maxValidators) { - k.setCliffValidator(ctx, validator, k.GetPool(ctx)) - } else if len(oldCliffValidatorAddr) > 0 { - k.clearCliffValidator(ctx) - } - - // swap the cliff validator for a new validator if the affected validator - // was bonded - if newValidatorBonded { - if oldCliffValidatorAddr != nil { - oldCliffVal, found := k.GetValidator(ctx, oldCliffValidatorAddr) - ensureValidatorFound(found, oldCliffValidatorAddr) - - if bytes.Equal(validatorToBond.OperatorAddr, affectedValidator.OperatorAddr) { - - // begin unbonding the old cliff validator iff the affected - // validator was newly bonded and has greater power - k.beginUnbondingValidator(ctx, oldCliffVal) - } else { - // otherwise begin unbonding the affected validator, which must - // have been kicked out - affectedValidator = k.beginUnbondingValidator(ctx, affectedValidator) - } - } - - validator = k.bondValidator(ctx, validatorToBond) - if bytes.Equal(validator.OperatorAddr, affectedValidator.OperatorAddr) { - return validator, true - } - - return affectedValidator, true - } - - return types.Validator{}, false -} - -// full update of the bonded validator set, many can be added/kicked -func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - - // clear the current validators store, add to the ToKickOut temp store - toKickOut := make(map[string]byte) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) - for ; iterator.Valid(); iterator.Next() { - ownerAddr := GetAddressFromValBondedIndexKey(iterator.Key()) - toKickOut[string(ownerAddr)] = 0 - } - - iterator.Close() - - var validator types.Validator - - oldCliffValidatorAddr := k.GetCliffValidator(ctx) - maxValidators := k.GetParams(ctx).MaxValidators - bondedValidatorsCount := 0 - - iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) - for ; iterator.Valid() && bondedValidatorsCount < int(maxValidators); iterator.Next() { - var found bool - - ownerAddr := iterator.Value() - validator, found = k.GetValidator(ctx, ownerAddr) - ensureValidatorFound(found, ownerAddr) - - _, found = toKickOut[string(ownerAddr)] - if found { - delete(toKickOut, string(ownerAddr)) - } else { - // If the validator wasn't in the toKickOut group it means it wasn't - // previously a validator, therefor update the validator to enter - // the validator group. - validator = k.bondValidator(ctx, validator) - } - - if validator.Jailed { - // we should no longer consider jailed validators as they are ranked - // lower than any non-jailed/bonded validators - if validator.Status == sdk.Bonded { - panic(fmt.Sprintf("jailed validator cannot be bonded for address: %s\n", ownerAddr)) - } - break - } - - bondedValidatorsCount++ - } - - iterator.Close() - - // clear or set the cliff validator - if bondedValidatorsCount == int(maxValidators) { - k.setCliffValidator(ctx, validator, k.GetPool(ctx)) - } else if len(oldCliffValidatorAddr) > 0 { - k.clearCliffValidator(ctx) - } - - kickOutValidators(k, ctx, toKickOut) - return -} - -func kickOutValidators(k Keeper, ctx sdk.Context, toKickOut map[string]byte) { - for key := range toKickOut { - ownerAddr := []byte(key) - validator, found := k.GetValidator(ctx, ownerAddr) - ensureValidatorFound(found, ownerAddr) - k.beginUnbondingValidator(ctx, validator) - } -} - -// perform all the store operations for when a validator status becomes unbonded -func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validator) types.Validator { - - store := ctx.KVStore(k.storeKey) - pool := k.GetPool(ctx) - params := k.GetParams(ctx) - - // sanity check - if validator.Status == sdk.Unbonded || - validator.Status == sdk.Unbonding { - panic(fmt.Sprintf("should not already be unbonded or unbonding, validator: %v\n", validator)) - } - - // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) - k.SetPool(ctx, pool) - - validator.UnbondingMinTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) - validator.UnbondingHeight = ctx.BlockHeader().Height - - // save the now unbonded validator record - k.SetValidator(ctx, validator) - - // add to accumulated changes for tendermint - bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdateZero()) - tstore := ctx.TransientStore(k.storeTKey) - tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) - - // also remove from the Bonded types.Validators Store - store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) - - k.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) - return validator -} - -// perform all the store operations for when a validator status becomes bonded -func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types.Validator { - - store := ctx.KVStore(k.storeKey) - pool := k.GetPool(ctx) - - // sanity check - if validator.Status == sdk.Bonded { - panic(fmt.Sprintf("should not already be bonded, validator: %v\n", validator)) - } - - validator.BondHeight = ctx.BlockHeight() - - // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Bonded) - k.SetPool(ctx, pool) - - // save the now bonded validator record to the three referenced stores - k.SetValidator(ctx, validator) - store.Set(GetValidatorsBondedIndexKey(validator.OperatorAddr), []byte{}) - - // add to accumulated changes for tendermint - bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdate()) - tstore := ctx.TransientStore(k.storeTKey) - tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) - - k.OnValidatorBonded(ctx, validator.ConsAddress()) - return validator -} - -// remove the validator record and associated indexes -func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { - - k.OnValidatorRemoved(ctx, address) - - // first retrieve the old validator record - validator, found := k.GetValidator(ctx, address) - if !found { - return - } - - // delete the old validator record - store := ctx.KVStore(k.storeKey) - pool := k.GetPool(ctx) - store.Delete(GetValidatorKey(address)) - store.Delete(GetValidatorByConsAddrKey(sdk.ConsAddress(validator.ConsPubKey.Address()))) - store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) - - // delete from the current and power weighted validator groups if the validator - // is bonded - and add validator with zero power to the validator updates - if store.Get(GetValidatorsBondedIndexKey(validator.OperatorAddr)) == nil { - return - } - store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) - - bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorUpdateZero()) - tstore := ctx.TransientStore(k.storeTKey) - tstore.Set(GetTendermintUpdatesTKey(address), bz) -} - -// UpdateValidatorCommission attempts to update a validator's commission rate. -// An error is returned if the new commission rate is invalid. -func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, validator types.Validator, newRate sdk.Dec) sdk.Error { - commission := validator.Commission - blockTime := ctx.BlockHeader().Time - - if err := commission.ValidateNewRate(newRate, blockTime); err != nil { - return err - } - - validator.Commission.Rate = newRate - validator.Commission.UpdateTime = blockTime - - k.SetValidator(ctx, validator) - k.OnValidatorCommissionChange(ctx, validator.OperatorAddr) - return nil -} - -//__________________________________________________________________________ - -// get the current validator on the cliff -func (k Keeper) GetCliffValidator(ctx sdk.Context) []byte { - store := ctx.KVStore(k.storeKey) - return store.Get(ValidatorCliffIndexKey) -} - -// get the current power of the validator on the cliff -func (k Keeper) GetCliffValidatorPower(ctx sdk.Context) []byte { - store := ctx.KVStore(k.storeKey) - return store.Get(ValidatorPowerCliffKey) -} - -// set the current validator and power of the validator on the cliff -func (k Keeper) setCliffValidator(ctx sdk.Context, validator types.Validator, pool types.Pool) { - store := ctx.KVStore(k.storeKey) - bz := GetValidatorsByPowerIndexKey(validator, pool) - store.Set(ValidatorPowerCliffKey, bz) - store.Set(ValidatorCliffIndexKey, validator.OperatorAddr) -} - -// clear the current validator and power of the validator on the cliff -func (k Keeper) clearCliffValidator(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - store.Delete(ValidatorPowerCliffKey) - store.Delete(ValidatorCliffIndexKey) -} - -func ensureValidatorFound(found bool, ownerAddr []byte) { - if !found { - panic(fmt.Sprintf("validator record not found for address: %X\n", ownerAddr)) - } -} diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 7533fed6e928..9b4dc3c55ef1 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -13,18 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -// for testing, remove all validator update entries after applied to Tendermint -func clearTendermintUpdates(ctx sdk.Context, k Keeper) { - store := ctx.TransientStore(k.storeTKey) - - // delete subspace - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesTKey) - for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) - } - iterator.Close() -} - //_______________________________________________________ func TestSetValidator(t *testing.T) { @@ -41,11 +29,17 @@ func TestSetValidator(t *testing.T) { assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) keeper.SetPool(ctx, pool) - keeper.UpdateValidator(ctx, validator) + keeper.SetValidator(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, pool) - // after the save the validator should be bonded + // ensure update + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) validator, found := keeper.GetValidator(ctx, valAddr) require.True(t, found) + require.Equal(t, 1, len(updates)) + require.Equal(t, validator.ABCIValidatorUpdate(), updates[0]) + + // after the save the validator should be bonded require.Equal(t, sdk.Bonded, validator.Status) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares)) @@ -59,7 +53,7 @@ func TestSetValidator(t *testing.T) { require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validator, resVals[0])) - resVals = keeper.GetValidatorsByPower(ctx) + resVals = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, 1, len(resVals)) require.True(ValEq(t, validator, resVals[0])) @@ -71,10 +65,6 @@ func TestSetValidator(t *testing.T) { require.Equal(t, 1, len(resVals)) require.True(ValEq(t, validator, resVals[0])) - updates := keeper.GetTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validator.ABCIValidatorUpdate(), updates[0]) - allVals := keeper.GetAllValidators(ctx) require.Equal(t, 1, len(allVals)) } @@ -94,27 +84,28 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) { require.Equal(t, sdk.Unbonded, validator.Status) require.Equal(t, int64(100), validator.Tokens.RoundInt64()) keeper.SetPool(ctx, pool) - keeper.UpdateValidator(ctx, validator) + testingUpdateValidator(keeper, ctx, validator) validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) pool = keeper.GetPool(ctx) power := GetValidatorsByPowerIndexKey(validator, pool) - require.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + require.True(t, validatorByPowerIndexExists(keeper, ctx, power)) // burn half the delegator shares + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewDec(2))) require.Equal(t, int64(50), burned.RoundInt64()) - keeper.SetPool(ctx, pool) // update the pool - keeper.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out - require.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + keeper.SetPool(ctx, pool) // update the pool + testingUpdateValidator(keeper, ctx, validator) // update the validator, possibly kicking it out + require.False(t, validatorByPowerIndexExists(keeper, ctx, power)) pool = keeper.GetPool(ctx) validator, found = keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) power = GetValidatorsByPowerIndexKey(validator, pool) - require.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + require.True(t, validatorByPowerIndexExists(keeper, ctx, power)) } func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { @@ -144,7 +135,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { val, pool, _ = val.AddTokensFromDel(pool, sdk.NewInt(int64((i+1)*10))) keeper.SetPool(ctx, pool) - val = keeper.UpdateValidator(ctx, val) + val = testingUpdateValidator(keeper, ctx, val) validators[i] = val } @@ -152,17 +143,10 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { // remove enough tokens to kick out the validator below the current cliff // validator and next in line cliff validator + keeper.DeleteValidatorByPowerIndex(ctx, nextCliffVal, pool) nextCliffVal, pool, _ = nextCliffVal.RemoveDelShares(pool, sdk.NewDec(21)) keeper.SetPool(ctx, pool) - nextCliffVal = keeper.UpdateValidator(ctx, nextCliffVal) - - // require the cliff validator has changed - cliffVal := validators[numVals-maxVals-1] - require.Equal(t, cliffVal.OperatorAddr, sdk.ValAddress(keeper.GetCliffValidator(ctx))) - - // require the cliff validator power has changed - cliffPower := keeper.GetCliffValidatorPower(ctx) - require.Equal(t, GetValidatorsByPowerIndexKey(cliffVal, pool), cliffPower) + nextCliffVal = testingUpdateValidator(keeper, ctx, nextCliffVal) expectedValStatus := map[int]sdk.BondStatus{ 9: sdk.Bonded, 8: sdk.Bonded, 7: sdk.Bonded, 5: sdk.Bonded, 4: sdk.Bonded, @@ -182,75 +166,6 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { } } -func TestCliffValidatorChange(t *testing.T) { - numVals := 10 - maxVals := 5 - - // create context, keeper, and pool for tests - ctx, _, keeper := CreateTestInput(t, false, 0) - pool := keeper.GetPool(ctx) - - // create keeper parameters - params := keeper.GetParams(ctx) - params.MaxValidators = uint16(maxVals) - keeper.SetParams(ctx, params) - - // create a random pool - pool.LooseTokens = sdk.NewDec(10000) - pool.BondedTokens = sdk.NewDec(1234) - keeper.SetPool(ctx, pool) - - validators := make([]types.Validator, numVals) - for i := 0; i < len(validators); i++ { - moniker := fmt.Sprintf("val#%d", int64(i)) - val := types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{Moniker: moniker}) - val.BondHeight = int64(i) - val.BondIntraTxCounter = int16(i) - val, pool, _ = val.AddTokensFromDel(pool, sdk.NewInt(int64((i+1)*10))) - - keeper.SetPool(ctx, pool) - val = keeper.UpdateValidator(ctx, val) - validators[i] = val - } - - // add a large amount of tokens to current cliff validator - currCliffVal := validators[numVals-maxVals] - currCliffVal, pool, _ = currCliffVal.AddTokensFromDel(pool, sdk.NewInt(200)) - keeper.SetPool(ctx, pool) - currCliffVal = keeper.UpdateValidator(ctx, currCliffVal) - - // assert new cliff validator to be set to the second lowest bonded validator by power - newCliffVal := validators[numVals-maxVals+1] - require.Equal(t, newCliffVal.OperatorAddr, sdk.ValAddress(keeper.GetCliffValidator(ctx))) - - // assert cliff validator power should have been updated - cliffPower := keeper.GetCliffValidatorPower(ctx) - require.Equal(t, GetValidatorsByPowerIndexKey(newCliffVal, pool), cliffPower) - - // add small amount of tokens to new current cliff validator - newCliffVal, pool, _ = newCliffVal.AddTokensFromDel(pool, sdk.NewInt(1)) - keeper.SetPool(ctx, pool) - newCliffVal = keeper.UpdateValidator(ctx, newCliffVal) - - // assert cliff validator has not change but increased in power - cliffPower = keeper.GetCliffValidatorPower(ctx) - require.Equal(t, newCliffVal.OperatorAddr, sdk.ValAddress(keeper.GetCliffValidator(ctx))) - require.Equal(t, GetValidatorsByPowerIndexKey(newCliffVal, pool), cliffPower) - - // add enough power to cliff validator to be equal in rank to next validator - newCliffVal, pool, _ = newCliffVal.AddTokensFromDel(pool, sdk.NewInt(9)) - keeper.SetPool(ctx, pool) - newCliffVal = keeper.UpdateValidator(ctx, newCliffVal) - - // assert new cliff validator due to power rank construction - newCliffVal = validators[numVals-maxVals+2] - require.Equal(t, newCliffVal.OperatorAddr, sdk.ValAddress(keeper.GetCliffValidator(ctx))) - - // assert cliff validator power should have been updated - cliffPower = keeper.GetCliffValidatorPower(ctx) - require.Equal(t, GetValidatorsByPowerIndexKey(newCliffVal, pool), cliffPower) -} - func TestSlashToZeroPowerRemoved(t *testing.T) { // initialize setup ctx, _, keeper := CreateTestInput(t, false, 100) @@ -263,12 +178,14 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { require.Equal(t, int64(100), validator.Tokens.RoundInt64()) keeper.SetPool(ctx, pool) keeper.SetValidatorByConsAddr(ctx, validator) - validator = keeper.UpdateValidator(ctx, validator) + validator = testingUpdateValidator(keeper, ctx, validator) require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) // slash the validator by 100% consAddr0 := sdk.ConsAddress(PKs[0].Address()) keeper.Slash(ctx, consAddr0, 0, 100, sdk.OneDec()) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) // validator should have been deleted _, found := keeper.GetValidator(ctx, addrVals[0]) require.False(t, found) @@ -306,7 +223,7 @@ func TestValidatorBasics(t *testing.T) { assert.True(sdk.DecEq(t, sdk.ZeroDec(), pool.BondedTokens)) // set and retrieve a record - validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) keeper.SetValidatorByConsAddr(ctx, validators[0]) resVal, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) @@ -333,7 +250,7 @@ func TestValidatorBasics(t *testing.T) { validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.NewDec(10) validators[0].DelegatorShares = sdk.NewDec(10) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) resVal, found = keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) assert.True(ValEq(t, validators[0], resVal)) @@ -343,8 +260,8 @@ func TestValidatorBasics(t *testing.T) { assert.True(ValEq(t, validators[0], resVals[0])) // add other validators - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - validators[2] = keeper.UpdateValidator(ctx, validators[2]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) resVal, found = keeper.GetValidator(ctx, addrVals[1]) require.True(t, found) assert.True(ValEq(t, validators[1], resVal)) @@ -364,7 +281,7 @@ func TestValidatorBasics(t *testing.T) { require.False(t, found) } -// test how the validators are sorted, tests GetValidatorsByPower +// test how the validators are sorted, tests GetBondedValidatorsByPower func GetValidatorSortingUnmixed(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) @@ -377,11 +294,11 @@ func GetValidatorSortingUnmixed(t *testing.T) { validators[i].Status = sdk.Bonded validators[i].Tokens = sdk.NewDec(amt) validators[i].DelegatorShares = sdk.NewDec(amt) - keeper.UpdateValidator(ctx, validators[i]) + testingUpdateValidator(keeper, ctx, validators[i]) } // first make sure everything made it in to the gotValidator group - resValidators := keeper.GetValidatorsByPower(ctx) + resValidators := keeper.GetBondedValidatorsByPower(ctx) assert.Equal(t, n, len(resValidators)) assert.Equal(t, sdk.NewDec(400), resValidators[0].BondedTokens(), "%v", resValidators) assert.Equal(t, sdk.NewDec(200), resValidators[1].BondedTokens(), "%v", resValidators) @@ -396,15 +313,15 @@ func GetValidatorSortingUnmixed(t *testing.T) { // test a basic increase in voting power validators[3].Tokens = sdk.NewDec(500) - keeper.UpdateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) + testingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) // test a decrease in voting power validators[3].Tokens = sdk.NewDec(300) - keeper.UpdateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) + testingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) assert.True(ValEq(t, validators[4], resValidators[1])) @@ -412,8 +329,8 @@ func GetValidatorSortingUnmixed(t *testing.T) { // test equal voting power, different age validators[3].Tokens = sdk.NewDec(200) ctx = ctx.WithBlockHeight(10) - keeper.UpdateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) + testingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) assert.True(ValEq(t, validators[4], resValidators[1])) @@ -422,8 +339,8 @@ func GetValidatorSortingUnmixed(t *testing.T) { // no change in voting power - no change in sort ctx = ctx.WithBlockHeight(20) - keeper.UpdateValidator(ctx, validators[4]) - resValidators = keeper.GetValidatorsByPower(ctx) + testingUpdateValidator(keeper, ctx, validators[4]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) assert.True(ValEq(t, validators[4], resValidators[1])) @@ -431,12 +348,12 @@ func GetValidatorSortingUnmixed(t *testing.T) { // change in voting power of both validators, both still in v-set, no age change validators[3].Tokens = sdk.NewDec(300) validators[4].Tokens = sdk.NewDec(300) - keeper.UpdateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) + testingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) ctx = ctx.WithBlockHeight(30) - keeper.UpdateValidator(ctx, validators[4]) - resValidators = keeper.GetValidatorsByPower(ctx) + testingUpdateValidator(keeper, ctx, validators[4]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n, "%v", resValidators) assert.True(ValEq(t, validators[3], resValidators[0])) assert.True(ValEq(t, validators[4], resValidators[1])) @@ -473,7 +390,7 @@ func GetValidatorSortingMixed(t *testing.T) { validators[4].Tokens = sdk.NewDec(amts[4]) for i := range amts { - keeper.UpdateValidator(ctx, validators[i]) + testingUpdateValidator(keeper, ctx, validators[i]) } val0, found := keeper.GetValidator(ctx, sdk.ValAddress(Addrs[0])) require.True(t, found) @@ -492,7 +409,7 @@ func GetValidatorSortingMixed(t *testing.T) { require.Equal(t, sdk.Bonded, val4.Status) // first make sure everything made it in to the gotValidator group - resValidators := keeper.GetValidatorsByPower(ctx) + resValidators := keeper.GetBondedValidatorsByPower(ctx) assert.Equal(t, n, len(resValidators)) assert.Equal(t, sdk.NewDec(400), resValidators[0].BondedTokens(), "%v", resValidators) assert.Equal(t, sdk.NewDec(200), resValidators[1].BondedTokens(), "%v", resValidators) @@ -525,24 +442,26 @@ func TestGetValidatorsEdgeCases(t *testing.T) { moniker := fmt.Sprintf("val#%d", int64(i)) validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{Moniker: moniker}) validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) - validators[i] = keeper.UpdateValidator(ctx, validators[i]) + validators[i] = testingUpdateValidator(keeper, ctx, validators[i]) } for i := range amts { validators[i], found = keeper.GetValidator(ctx, validators[i].OperatorAddr) require.True(t, found) } - resValidators := keeper.GetValidatorsByPower(ctx) + resValidators := keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[2], resValidators[0])) assert.True(ValEq(t, validators[3], resValidators[1])) pool := keeper.GetPool(ctx) + keeper.DeleteValidatorByPowerIndex(ctx, validators[0], pool) validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(500)) keeper.SetPool(ctx, pool) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - resValidators = keeper.GetValidatorsByPower(ctx) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[2], resValidators[1])) @@ -556,28 +475,31 @@ func TestGetValidatorsEdgeCases(t *testing.T) { validators[3], found = keeper.GetValidator(ctx, validators[3].OperatorAddr) require.True(t, found) + keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewInt(1)) keeper.SetPool(ctx, pool) - validators[3] = keeper.UpdateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) + validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[3], resValidators[1])) // validator 3 kicked out temporarily + keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].RemoveDelShares(pool, sdk.NewDec(201)) keeper.SetPool(ctx, pool) - validators[3] = keeper.UpdateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) + validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[2], resValidators[1])) // validator 4 does not get spot back + keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewInt(200)) keeper.SetPool(ctx, pool) - validators[3] = keeper.UpdateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) + validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[2], resValidators[1])) @@ -600,34 +522,39 @@ func TestValidatorBondHeight(t *testing.T) { validators[0] = types.NewValidator(sdk.ValAddress(Addrs[0]), PKs[0], types.Description{}) validators[1] = types.NewValidator(sdk.ValAddress(Addrs[1]), PKs[1], types.Description{}) validators[2] = types.NewValidator(sdk.ValAddress(Addrs[2]), PKs[2], types.Description{}) + validators[0].BondIntraTxCounter = 0 + validators[1].BondIntraTxCounter = 1 + validators[2].BondIntraTxCounter = 2 validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(200)) validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewInt(100)) validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(100)) keeper.SetPool(ctx, pool) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) //////////////////////////////////////// // If two validators both increase to the same voting power in the same block, // the one with the first transaction should become bonded - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - validators[2] = keeper.UpdateValidator(ctx, validators[2]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) pool = keeper.GetPool(ctx) - resValidators := keeper.GetValidatorsByPower(ctx) + resValidators := keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, uint16(len(resValidators)), params.MaxValidators) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[1], resValidators[1])) + keeper.DeleteValidatorByPowerIndex(ctx, validators[1], pool) + keeper.DeleteValidatorByPowerIndex(ctx, validators[2], pool) validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewInt(50)) validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(50)) keeper.SetPool(ctx, pool) - validators[2] = keeper.UpdateValidator(ctx, validators[2]) - resValidators = keeper.GetValidatorsByPower(ctx) + validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, params.MaxValidators, uint16(len(resValidators))) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[2], resValidators[1])) } @@ -646,20 +573,21 @@ func TestFullValidatorSetPowerChange(t *testing.T) { pool := keeper.GetPool(ctx) validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) - keeper.UpdateValidator(ctx, validators[i]) + testingUpdateValidator(keeper, ctx, validators[i]) } for i := range amts { var found bool validators[i], found = keeper.GetValidator(ctx, validators[i].OperatorAddr) require.True(t, found) } - assert.Equal(t, sdk.Unbonding, validators[0].Status) + assert.Equal(t, sdk.Unbonded, validators[0].Status) assert.Equal(t, sdk.Unbonding, validators[1].Status) assert.Equal(t, sdk.Bonded, validators[2].Status) assert.Equal(t, sdk.Bonded, validators[3].Status) assert.Equal(t, sdk.Unbonded, validators[4].Status) - resValidators := keeper.GetValidatorsByPower(ctx) + resValidators := keeper.GetBondedValidatorsByPower(ctx) assert.Equal(t, max, len(resValidators)) assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs assert.True(ValEq(t, validators[3], resValidators[1])) @@ -668,14 +596,14 @@ func TestFullValidatorSetPowerChange(t *testing.T) { pool := keeper.GetPool(ctx) validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(600)) keeper.SetPool(ctx, pool) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - resValidators = keeper.GetValidatorsByPower(ctx) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) assert.Equal(t, max, len(resValidators)) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[2], resValidators[1])) } -func TestGetTendermintUpdatesAllNone(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesAllNone(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -693,17 +621,22 @@ func TestGetTendermintUpdatesAllNone(t *testing.T) { // test from nothing to something // tendermintUpdate set: {} -> {c1, c3} - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + pool := keeper.GetPool(ctx) + keeper.SetValidator(ctx, validators[0]) + keeper.SetValidatorByPowerIndex(ctx, validators[0], pool) + keeper.SetValidator(ctx, validators[1]) + keeper.SetValidatorByPowerIndex(ctx, validators[1], pool) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) assert.Equal(t, 2, len(updates)) - assert.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) - assert.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + assert.Equal(t, validators[0].ABCIValidatorUpdate(), updates[1]) + assert.Equal(t, validators[1].ABCIValidatorUpdate(), updates[0]) } -func TestGetTendermintUpdatesIdentical(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesIdentical(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -714,19 +647,18 @@ func TestGetTendermintUpdatesIdentical(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test identical, // tendermintUpdate set: {} -> {} - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) } -func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesSingleValueChange(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -737,24 +669,23 @@ func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test single value change // tendermintUpdate set: {} -> {c1'} validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.NewDec(600) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) } -func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesMultipleValueChange(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20} @@ -765,10 +696,9 @@ func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test multiple value change // tendermintUpdate set: {c1, c3} -> {c1', c3'} @@ -776,16 +706,16 @@ func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(190)) validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewInt(80)) keeper.SetPool(ctx, pool) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(updates)) - require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) - require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) + require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[1]) + require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[0]) } -func TestGetTendermintUpdatesInserted(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesInserted(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{10, 20, 5, 15, 25} @@ -796,36 +726,42 @@ func TestGetTendermintUpdatesInserted(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test validtor added at the beginning // tendermintUpdate set: {} -> {c0} - validators[2] = keeper.UpdateValidator(ctx, validators[2]) - updates := keeper.GetTendermintUpdates(ctx) + pool := keeper.GetPool(ctx) + keeper.SetValidator(ctx, validators[2]) + keeper.SetValidatorByPowerIndex(ctx, validators[2], pool) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) require.Equal(t, 1, len(updates)) require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) // test validtor added at the beginning // tendermintUpdate set: {} -> {c0} - clearTendermintUpdates(ctx, keeper) - validators[3] = keeper.UpdateValidator(ctx, validators[3]) - updates = keeper.GetTendermintUpdates(ctx) + pool = keeper.GetPool(ctx) + keeper.SetValidator(ctx, validators[3]) + keeper.SetValidatorByPowerIndex(ctx, validators[3], pool) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators[3], _ = keeper.GetValidator(ctx, validators[3].OperatorAddr) require.Equal(t, 1, len(updates)) require.Equal(t, validators[3].ABCIValidatorUpdate(), updates[0]) // test validtor added at the end // tendermintUpdate set: {} -> {c0} - clearTendermintUpdates(ctx, keeper) - validators[4] = keeper.UpdateValidator(ctx, validators[4]) - updates = keeper.GetTendermintUpdates(ctx) + pool = keeper.GetPool(ctx) + keeper.SetValidator(ctx, validators[4]) + keeper.SetValidatorByPowerIndex(ctx, validators[4], pool) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators[4], _ = keeper.GetValidator(ctx, validators[4].OperatorAddr) require.Equal(t, 1, len(updates)) require.Equal(t, validators[4].ABCIValidatorUpdate(), updates[0]) } -func TestGetTendermintUpdatesWithCliffValidator(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesWithCliffValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) params := types.DefaultParams() params.MaxValidators = 2 @@ -839,34 +775,33 @@ func TestGetTendermintUpdatesWithCliffValidator(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test validator added at the end but not inserted in the valset // tendermintUpdate set: {} -> {} - keeper.UpdateValidator(ctx, validators[2]) - updates := keeper.GetTendermintUpdates(ctx) + testingUpdateValidator(keeper, ctx, validators[2]) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 0, len(updates)) // test validator change its power and become a gotValidator (pushing out an existing) // tendermintUpdate set: {} -> {c0, c4} - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) pool := keeper.GetPool(ctx) validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(10)) keeper.SetPool(ctx, pool) - validators[2] = keeper.UpdateValidator(ctx, validators[2]) - - updates = keeper.GetTendermintUpdates(ctx) + keeper.SetValidator(ctx, validators[2]) + keeper.SetValidatorByPowerIndex(ctx, validators[2], pool) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) require.Equal(t, 2, len(updates), "%v", updates) - require.Equal(t, validators[0].ABCIValidatorUpdateZero(), updates[0]) - require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[1]) + require.Equal(t, validators[0].ABCIValidatorUpdateZero(), updates[1]) + require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) } -func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) amts := []int64{100, 100} @@ -875,12 +810,12 @@ func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { pool := keeper.GetPool(ctx) validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) + validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) } - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // check initial power require.Equal(t, sdk.NewDec(100).RoundInt64(), validators[0].GetPower().RoundInt64()) @@ -892,21 +827,21 @@ func TestGetTendermintUpdatesPowerDecrease(t *testing.T) { validators[0], pool, _ = validators[0].RemoveDelShares(pool, sdk.NewDec(20)) validators[1], pool, _ = validators[1].RemoveDelShares(pool, sdk.NewDec(30)) keeper.SetPool(ctx, pool) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) // power has changed require.Equal(t, sdk.NewDec(80).RoundInt64(), validators[0].GetPower().RoundInt64()) require.Equal(t, sdk.NewDec(70).RoundInt64(), validators[1].GetPower().RoundInt64()) // Tendermint updates should reflect power change - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(updates)) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) } -func TestGetTendermintUpdatesNewValidator(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesNewValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) params := keeper.GetParams(ctx) params.MaxValidators = uint16(3) @@ -923,28 +858,33 @@ func TestGetTendermintUpdatesNewValidator(t *testing.T) { valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{}) + validators[i].BondIntraTxCounter = int16(i) validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) - validators[i] = keeper.UpdateValidator(ctx, validators[i]) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) } // verify initial Tendermint updates are correct - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, len(validators), len(updates)) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // update initial validator set for i, amt := range amts { pool := keeper.GetPool(ctx) + keeper.DeleteValidatorByPowerIndex(ctx, validators[i], pool) validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) - validators[i] = keeper.UpdateValidator(ctx, validators[i]) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) } // add a new validator that goes from zero power, to non-zero power, back to @@ -958,10 +898,11 @@ func TestGetTendermintUpdatesNewValidator(t *testing.T) { validator, pool, _ = validator.AddTokensFromDel(pool, amt) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) + keeper.SetValidator(ctx, validator) validator, pool, _ = validator.RemoveDelShares(pool, sdk.NewDecFromInt(amt)) - validator = keeper.UpdateValidator(ctx, validator) + keeper.SetValidator(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, pool) // add a new validator that increases in power valPubKey = PKs[len(validators)+2] @@ -969,19 +910,22 @@ func TestGetTendermintUpdatesNewValidator(t *testing.T) { validator = types.NewValidator(valAddr, valPubKey, types.Description{}) validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(500)) - + keeper.SetValidator(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, pool) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) // verify initial Tendermint updates are correct - updates = keeper.GetTendermintUpdates(ctx) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator, _ = keeper.GetValidator(ctx, validator.OperatorAddr) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) require.Equal(t, len(validators)+1, len(updates)) require.Equal(t, validator.ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[1]) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[2]) } -func TestGetTendermintUpdatesBondTransition(t *testing.T) { +func TestApplyAndReturnValidatorSetUpdatesBondTransition(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) params := keeper.GetParams(ctx) params.MaxValidators = uint16(2) @@ -1000,60 +944,67 @@ func TestGetTendermintUpdatesBondTransition(t *testing.T) { validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{Moniker: moniker}) validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) - + validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) - validators[i] = keeper.UpdateValidator(ctx, validators[i]) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) } // verify initial Tendermint updates are correct - updates := keeper.GetTendermintUpdates(ctx) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(updates)) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // delegate to validator with lowest power but not enough to bond ctx = ctx.WithBlockHeight(1) pool := keeper.GetPool(ctx) - validator, found := keeper.GetValidator(ctx, validators[0].OperatorAddr) + var found bool + validators[0], found = keeper.GetValidator(ctx, validators[0].OperatorAddr) require.True(t, found) - validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(1)) - + keeper.DeleteValidatorByPowerIndex(ctx, validators[0], pool) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(1)) keeper.SetPool(ctx, pool) - validators[0] = keeper.UpdateValidator(ctx, validator) + keeper.SetValidator(ctx, validators[0]) + keeper.SetValidatorByPowerIndex(ctx, validators[0], pool) // verify initial Tendermint updates are correct - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // create a series of events that will bond and unbond the validator with // lowest power in a single block context (height) ctx = ctx.WithBlockHeight(2) pool = keeper.GetPool(ctx) - validator, found = keeper.GetValidator(ctx, validators[1].OperatorAddr) + validators[1], found = keeper.GetValidator(ctx, validators[1].OperatorAddr) require.True(t, found) - validator, pool, _ = validator.RemoveDelShares(pool, validator.DelegatorShares) - + keeper.DeleteValidatorByPowerIndex(ctx, validators[0], pool) + validators[0], pool, _ = validators[0].RemoveDelShares(pool, validators[0].DelegatorShares) keeper.SetPool(ctx, pool) - validator = keeper.UpdateValidator(ctx, validator) - - validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(250)) + keeper.SetValidator(ctx, validators[0]) + keeper.SetValidatorByPowerIndex(ctx, validators[0], pool) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 0, len(updates)) + keeper.DeleteValidatorByPowerIndex(ctx, validators[1], pool) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewInt(250)) keeper.SetPool(ctx, pool) - validators[1] = keeper.UpdateValidator(ctx, validator) + keeper.SetValidator(ctx, validators[1]) + keeper.SetValidatorByPowerIndex(ctx, validators[1], pool) // verify initial Tendermint updates are correct - updates = keeper.GetTendermintUpdates(ctx) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 1, len(updates)) require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[0]) - clearTendermintUpdates(ctx, keeper) - require.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) } func TestUpdateValidatorCommission(t *testing.T) { @@ -1072,6 +1023,9 @@ func TestUpdateValidatorCommission(t *testing.T) { val1, _ = val1.SetInitialCommission(commission1) val2, _ = val2.SetInitialCommission(commission2) + keeper.SetValidator(ctx, val1) + keeper.SetValidator(ctx, val2) + testCases := []struct { validator types.Validator newRate sdk.Dec @@ -1085,11 +1039,13 @@ func TestUpdateValidatorCommission(t *testing.T) { } for i, tc := range testCases { - err := keeper.UpdateValidatorCommission(ctx, tc.validator, tc.newRate) + commission, err := keeper.UpdateValidatorCommission(ctx, tc.validator, tc.newRate) if tc.expectedErr { require.Error(t, err, "expected error for test case #%d with rate: %s", i, tc.newRate) } else { + tc.validator.Commission = commission + keeper.SetValidator(ctx, tc.validator) val, found := keeper.GetValidator(ctx, tc.validator.OperatorAddr) require.True(t, found, diff --git a/x/stake/querier/queryable_test.go b/x/stake/querier/queryable_test.go index ee52773c6e4c..d950f90bf91c 100644 --- a/x/stake/querier/queryable_test.go +++ b/x/stake/querier/queryable_test.go @@ -71,8 +71,8 @@ func TestQueryValidators(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) } keeper.SetPool(ctx, pool) - validators[0] = keeper.UpdateValidator(ctx, validators[0]) - validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.SetValidator(ctx, validators[0]) + keeper.SetValidator(ctx, validators[1]) // Query Validators queriedValidators := keeper.GetValidators(ctx, params.MaxValidators) @@ -114,9 +114,14 @@ func TestQueryDelegation(t *testing.T) { // Create Validators and Delegation val1 := types.NewValidator(addrVal1, pk1, types.Description{}) keeper.SetValidator(ctx, val1) + pool := keeper.GetPool(ctx) + keeper.SetValidatorByPowerIndex(ctx, val1, pool) keeper.Delegate(ctx, addrAcc2, sdk.NewCoin("steak", sdk.NewInt(20)), val1, true) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + // Query Delegator bonded validators queryParams := newTestDelegatorQuery(addrAcc2) bz, errRes := cdc.MarshalJSON(queryParams) diff --git a/x/stake/stake.go b/x/stake/stake.go index 7e60b3113a83..a9a3ca3cd544 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -37,9 +37,7 @@ var ( GetValidatorKey = keeper.GetValidatorKey GetValidatorByConsAddrKey = keeper.GetValidatorByConsAddrKey - GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey - GetTendermintUpdatesTKey = keeper.GetTendermintUpdatesTKey GetDelegationKey = keeper.GetDelegationKey GetDelegationsKey = keeper.GetDelegationsKey ParamKey = keeper.ParamKey @@ -48,9 +46,6 @@ var ( ValidatorsByConsAddrKey = keeper.ValidatorsByConsAddrKey ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey - ValidatorCliffIndexKey = keeper.ValidatorCliffIndexKey - ValidatorPowerCliffKey = keeper.ValidatorPowerCliffKey - TendermintUpdatesTKey = keeper.TendermintUpdatesTKey DelegationKey = keeper.DelegationKey IntraTxCounterKey = keeper.IntraTxCounterKey GetUBDKey = keeper.GetUBDKey diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index 57794691f5ef..dbd4e2a54000 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -314,6 +314,12 @@ func (v Validator) ABCIValidatorUpdate() abci.ValidatorUpdate { } } +// ABCIValidatorPowerBytes +func (v Validator) ABCIValidatorPowerBytes(cdc *codec.Codec) []byte { + power := v.BondedTokens().RoundInt64() + return cdc.MustMarshalBinary(power) +} + // ABCIValidatorUpdateZero returns an abci.ValidatorUpdate from a staked validator type // with zero power used for validator updates. func (v Validator) ABCIValidatorUpdateZero() abci.ValidatorUpdate { @@ -429,6 +435,7 @@ func (v Validator) BondedTokens() sdk.Dec { return sdk.ZeroDec() } +// TODO remove this once the validator queue logic is implemented // Returns if the validator should be considered unbonded func (v Validator) IsUnbonded(ctx sdk.Context) bool { switch v.Status {