diff --git a/cmd/kvd/add_account.go b/cmd/kvd/add_account.go index 2982a309..95ae9b0e 100644 --- a/cmd/kvd/add_account.go +++ b/cmd/kvd/add_account.go @@ -94,9 +94,12 @@ func AddGenesisAccountCmd( baseAccount := auth.NewBaseAccount(addr, coins.Sort(), nil, 0, 0) if !vestingAmt.IsZero() { - baseVestingAccount := vesting.NewBaseVestingAccount( + baseVestingAccount, err := vesting.NewBaseVestingAccount( baseAccount, vestingAmt.Sort(), vestingEnd, ) + if err != nil { + return fmt.Errorf("Failed to create base vesting account: %w", err) + } switch { case vestingPeriodsFile != "": diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index b959d4ef..43a4f0b8 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -24,24 +24,31 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) validatorVestingKeys := k.GetAllAccountKeys(ctx) for _, key := range validatorVestingKeys { acc := k.GetAccountFromAuthKeeper(ctx, key) - vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress) - if !found || !vote.SignedLastBlock { - // if the validator was not found or explicitly didn't sign, increment the missing sign count - k.UpdateMissingSignCount(ctx, acc.GetAddress(), true) - } else { - k.UpdateMissingSignCount(ctx, acc.GetAddress(), false) - } - - // check if a period ended in the last block - endTimes := k.GetPeriodEndTimes(ctx, key) - - for i, t := range endTimes { - if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t { - k.UpdateVestedCoinsProgress(ctx, key, i) + if k.AccountIsVesting(ctx, acc.GetAddress()) { + vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress) + if !found || !vote.SignedLastBlock { + if ctx.BlockHeight() <= 1 { + // don't count missed blocks on block 1 since there is no vote history + k.UpdateMissingSignCount(ctx, acc.GetAddress(), false) + } else { + // if the validator was not found or explicitly didn't sign, increment the missing sign count + k.UpdateMissingSignCount(ctx, acc.GetAddress(), true) + } + } else { + k.UpdateMissingSignCount(ctx, acc.GetAddress(), false) } + + // check if a period ended in the last block + endTimes := k.GetPeriodEndTimes(ctx, key) + + for i, t := range endTimes { + if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t { + k.UpdateVestedCoinsProgress(ctx, key, i) + } + } + // handle any new/remaining debt on the account + k.HandleVestingDebt(ctx, key, currentBlockTime) } - // handle any new/remaining debt on the account - k.HandleVestingDebt(ctx, key, currentBlockTime) } k.SetPreviousBlockTime(ctx, currentBlockTime) } diff --git a/x/validator-vesting/abci_test.go b/x/validator-vesting/abci_test.go index f63f4c13..b2e65667 100644 --- a/x/validator-vesting/abci_test.go +++ b/x/validator-vesting/abci_test.go @@ -15,6 +15,81 @@ import ( "github.com/kava-labs/kava/x/validator-vesting/internal/types" ) +func TestBeginBlockerZeroHeight(t *testing.T) { + ctx, ak, _, stakingKeeper, _, vvk := keeper.CreateTestInput(t, false, 1000) + now := tmtime.Now() + vva := keeper.ValidatorVestingDelegatorTestAccount(now) + ak.SetAccount(ctx, vva) + delTokens := sdk.TokensFromConsensusPower(30) + vvk.SetValidatorVestingAccountKey(ctx, vva.Address) + + keeper.CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5}) + + val1, found := stakingKeeper.GetValidator(ctx, keeper.ValOpAddr1) + require.True(t, found) + _, err := stakingKeeper.Delegate(ctx, vva.Address, delTokens, sdk.Unbonded, val1, true) + require.NoError(t, err) + + _ = staking.EndBlocker(ctx, stakingKeeper) + + // require that there exists one delegation + var delegations int + stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) { + delegations++ + return false + }) + + require.Equal(t, 1, delegations) + + val := abci.Validator{ + Address: val1.ConsPubKey.Address(), + Power: val1.ConsensusPower(), + } + + vva.ValidatorAddress = val1.ConsAddress() + ak.SetAccount(ctx, vva) + + height := int64(1) + blockTime := now + addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) } + + header := abci.Header{Height: height, Time: addHour(blockTime)} + ctx = ctx.WithBlockHeader(header) + + // mark the validator as absent + req := abci.RequestBeginBlock{ + Header: header, + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: abci.Validator{}, + SignedLastBlock: false, + }}, + }, + } + + BeginBlocker(ctx, req, vvk) + + vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) + // require missed block counter doesn't increment because there's no voting history + require.Equal(t, types.CurrentPeriodProgress{0, 1}, vva.CurrentPeriodProgress) + + // mark the validator as having missed + req = abci.RequestBeginBlock{ + Header: header, + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: val, + SignedLastBlock: false, + }}, + }, + } + + BeginBlocker(ctx, req, vvk) + + vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) + require.Equal(t, types.CurrentPeriodProgress{0, 2}, vva.CurrentPeriodProgress) +} + func TestBeginBlockerSignedBlock(t *testing.T) { ctx, ak, _, stakingKeeper, _, vvk := keeper.CreateTestInput(t, false, 1000) now := tmtime.Now() @@ -51,13 +126,13 @@ func TestBeginBlockerSignedBlock(t *testing.T) { vva.ValidatorAddress = val1.ConsAddress() ak.SetAccount(ctx, vva) - height := int64(0) + height := int64(1) blockTime := now addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) } header := abci.Header{Height: height, Time: addHour(blockTime)} - + ctx = ctx.WithBlockHeader(header) // mark the validator as having signed req := abci.RequestBeginBlock{ Header: header, @@ -76,7 +151,26 @@ func TestBeginBlockerSignedBlock(t *testing.T) { require.Equal(t, types.CurrentPeriodProgress{0, 1}, vva.CurrentPeriodProgress) header = abci.Header{Height: height, Time: addHour(blockTime)} + // mark the validator as having signed + ctx = ctx.WithBlockHeader(header) + req = abci.RequestBeginBlock{ + Header: header, + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: val, + SignedLastBlock: true, + }}, + }, + } + BeginBlocker(ctx, req, vvk) + height++ + blockTime = addHour(blockTime) + vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) + require.Equal(t, types.CurrentPeriodProgress{0, 2}, vva.CurrentPeriodProgress) + + header = abci.Header{Height: height, Time: addHour(blockTime)} + ctx = ctx.WithBlockHeader(header) // mark the validator as having missed req = abci.RequestBeginBlock{ Header: header, @@ -92,8 +186,10 @@ func TestBeginBlockerSignedBlock(t *testing.T) { height++ blockTime = addHour(blockTime) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) - require.Equal(t, types.CurrentPeriodProgress{1, 2}, vva.CurrentPeriodProgress) + require.Equal(t, types.CurrentPeriodProgress{1, 3}, vva.CurrentPeriodProgress) + header = abci.Header{Height: height, Time: addHour(blockTime)} + ctx = ctx.WithBlockHeader(header) // mark the validator as being absent req = abci.RequestBeginBlock{ Header: header, @@ -106,14 +202,12 @@ func TestBeginBlockerSignedBlock(t *testing.T) { } BeginBlocker(ctx, req, vvk) - height++ - blockTime = addHour(blockTime) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) - require.Equal(t, types.CurrentPeriodProgress{2, 3}, vva.CurrentPeriodProgress) + require.Equal(t, types.CurrentPeriodProgress{2, 4}, vva.CurrentPeriodProgress) } func TestBeginBlockerSuccessfulPeriod(t *testing.T) { - height := int64(0) + height := int64(1) now := tmtime.Now() blockTime := now numBlocks := int64(14) @@ -156,7 +250,7 @@ func TestBeginBlockerSuccessfulPeriod(t *testing.T) { BeginBlocker(ctx, req, vvk) blockTime = addHour(blockTime) - if height == 11 { + if height == 12 { vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) // require that missing sign count is set back to zero after the period increments. require.Equal(t, types.CurrentPeriodProgress{0, 0}, vva.CurrentPeriodProgress) @@ -170,10 +264,10 @@ func TestBeginBlockerSuccessfulPeriod(t *testing.T) { } func TestBeginBlockerUnsuccessfulPeriod(t *testing.T) { - height := int64(0) + height := int64(1) now := tmtime.Now() blockTime := now - numBlocks := int64(12) + numBlocks := int64(13) addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) } ctx, ak, _, stakingKeeper, supplyKeeper, vvk := keeper.CreateTestInput(t, false, 1000) diff --git a/x/validator-vesting/alias.go b/x/validator-vesting/alias.go index 9f9914e9..dfd168bb 100644 --- a/x/validator-vesting/alias.go +++ b/x/validator-vesting/alias.go @@ -1,7 +1,10 @@ +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/internal/types/ +// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/internal/keeper/ package validatorvesting -// nolint -// DONTCOVER import ( "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" "github.com/kava-labs/kava/x/validator-vesting/internal/types" @@ -13,19 +16,49 @@ const ( ) var ( - NewValidatorVestingAccountRaw = types.NewValidatorVestingAccountRaw - NewValidatorVestingAccount = types.NewValidatorVestingAccount - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState - RegisterCodec = types.RegisterCodec - ValidatorVestingAccountPrefix = types.ValidatorVestingAccountPrefix + // functions aliases + RegisterCodec = types.RegisterCodec + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + ValidateGenesis = types.ValidateGenesis + ValidatorVestingAccountKey = types.ValidatorVestingAccountKey + CreateTestAddrs = types.CreateTestAddrs + TestAddr = types.TestAddr + CreateTestPubKeys = types.CreateTestPubKeys + NewPubKey = types.NewPubKey + NewValidatorVestingAccountRaw = types.NewValidatorVestingAccountRaw + NewValidatorVestingAccount = types.NewValidatorVestingAccount + NewKeeper = keeper.NewKeeper + MakeTestCodec = keeper.MakeTestCodec + CreateTestInput = keeper.CreateTestInput + ValidatorVestingTestAccount = keeper.ValidatorVestingTestAccount + ValidatorVestingTestAccounts = keeper.ValidatorVestingTestAccounts + ValidatorVestingDelegatorTestAccount = keeper.ValidatorVestingDelegatorTestAccount + CreateValidators = keeper.CreateValidators + + // variable aliases + ModuleCdc = types.ModuleCdc BlocktimeKey = types.BlocktimeKey - ValidatorVestingAccountKey = types.ValidatorVestingAccountKey - NewKeeper = keeper.NewKeeper + ValidatorVestingAccountPrefix = types.ValidatorVestingAccountPrefix + ValOpPk1 = keeper.ValOpPk1 + ValOpPk2 = keeper.ValOpPk2 + ValOpPk3 = keeper.ValOpPk3 + ValOpAddr1 = keeper.ValOpAddr1 + ValOpAddr2 = keeper.ValOpAddr2 + ValOpAddr3 = keeper.ValOpAddr3 + ValConsPk11 = keeper.ValConsPk11 + ValConsPk12 = keeper.ValConsPk12 + ValConsPk13 = keeper.ValConsPk13 + ValConsAddr1 = keeper.ValConsAddr1 + ValConsAddr2 = keeper.ValConsAddr2 + ValConsAddr3 = keeper.ValConsAddr3 + TestAddrs = keeper.TestAddrs ) type ( GenesisState = types.GenesisState - Keeper = keeper.Keeper + VestingProgress = types.VestingProgress + CurrentPeriodProgress = types.CurrentPeriodProgress ValidatorVestingAccount = types.ValidatorVestingAccount + Keeper = keeper.Keeper ) diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go index bce5603b..bb0ed89e 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -135,12 +135,14 @@ func (k Keeper) SetVestingProgress(ctx sdk.Context, addr sdk.AccAddress, period k.ak.SetAccount(ctx, vv) } +// AddDebt adds the input amount to DebtAfterFailedVesting field func (k Keeper) AddDebt(ctx sdk.Context, addr sdk.AccAddress, amount sdk.Coins) { vv := k.GetAccountFromAuthKeeper(ctx, addr) vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(amount) k.ak.SetAccount(ctx, vv) } +// ResetCurrentPeriodProgress resets CurrentPeriodProgress to zero values func (k Keeper) ResetCurrentPeriodProgress(ctx sdk.Context, addr sdk.AccAddress) { vv := k.GetAccountFromAuthKeeper(ctx, addr) vv.CurrentPeriodProgress = types.CurrentPeriodProgress{TotalBlocks: 0, MissedBlocks: 0} @@ -202,3 +204,17 @@ func (k Keeper) GetPeriodEndTimes(ctx sdk.Context, addr sdk.AccAddress) []int64 } return endTimes } + +// AccountIsVesting returns true if all vesting periods is complete and there is no debt +func (k Keeper) AccountIsVesting(ctx sdk.Context, addr sdk.AccAddress) bool { + vv := k.GetAccountFromAuthKeeper(ctx, addr) + if !vv.DebtAfterFailedVesting.IsZero() { + return false + } + for _, p := range vv.VestingPeriodProgress { + if !p.PeriodComplete { + return false + } + } + return true +} diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/internal/keeper/keeper_test.go index 19d11010..f304ddbd 100644 --- a/x/validator-vesting/internal/keeper/keeper_test.go +++ b/x/validator-vesting/internal/keeper/keeper_test.go @@ -102,6 +102,25 @@ func TestGetEndTImes(t *testing.T) { require.Equal(t, expectedEndTimes, endTimes) } +func TestAccountIsVesting(t *testing.T) { + ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000) + + now := tmtime.Now() + + vva := ValidatorVestingDelegatorTestAccount(now) + ak.SetAccount(ctx, vva) + keeper.SetValidatorVestingAccountKey(ctx, vva.Address) + + require.Equal(t, false, keeper.AccountIsVesting(ctx, vva.Address)) + + for i := range vva.VestingPeriodProgress { + vva.VestingPeriodProgress[i] = types.VestingProgress{true, true} + ak.SetAccount(ctx, vva) + } + require.Equal(t, true, keeper.AccountIsVesting(ctx, vva.Address)) + +} + func TestSetMissingSignCount(t *testing.T) { ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000) @@ -142,7 +161,7 @@ func TestUpdateVestedCoinsProgress(t *testing.T) { vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0) vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) - // require that debt is zero + // require that debt is zerox require.Equal(t, sdk.Coins(nil), vva.DebtAfterFailedVesting) // require that the first vesting progress variable is successful require.Equal(t, []types.VestingProgress{types.VestingProgress{true, true}, types.VestingProgress{false, false}, types.VestingProgress{false, false}}, vva.VestingPeriodProgress) diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go index 27d7bde1..19a414fb 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account.go +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -32,7 +32,7 @@ type VestingProgress struct { // CurrentPeriodProgress tracks the progress of the current vesting period type CurrentPeriodProgress struct { MissedBlocks int64 `json:"missed_blocks" yaml:"missed_blocks"` - TotalBlocks int64 `json:"total_blocks" yaml:"total_blocks` + TotalBlocks int64 `json:"total_blocks" yaml:"total_blocks"` } // GetSignedPercentage returns the percentage of blocks signed for the current vesting period @@ -63,7 +63,7 @@ type ValidatorVestingAccount struct { ValidatorAddress sdk.ConsAddress `json:"validator_address" yaml:"validator_address"` ReturnAddress sdk.AccAddress `json:"return_address" yaml:"return_address"` SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"` - CurrentPeriodProgress CurrentPeriodProgress `json:"missing_sign_count" yaml:"missing_sign_count"` + CurrentPeriodProgress CurrentPeriodProgress `json:"current_period_progress" yaml:"current_period_progress"` VestingPeriodProgress []VestingProgress `json:"vesting_period_progress" yaml:"vesting_period_progress"` DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` } diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/internal/types/validator_vesting_account_test.go index 2d5a1f51..4c19c68e 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account_test.go +++ b/x/validator-vesting/internal/types/validator_vesting_account_test.go @@ -35,7 +35,7 @@ func TestNewAccount(t *testing.T) { origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := auth.NewBaseAccountWithAddress(testAddr) bacc.SetCoins(origCoins) - bva := vesting.NewBaseVestingAccount(&bacc, origCoins, endTime) + bva, _ := vesting.NewBaseVestingAccount(&bacc, origCoins, endTime) require.NotPanics(t, func() { NewValidatorVestingAccountRaw(bva, now.Unix(), periods, testConsAddr, nil, 90) }) vva := NewValidatorVestingAccountRaw(bva, now.Unix(), periods, testConsAddr, nil, 90) vva.PubKey = testPk diff --git a/x/validator-vesting/module.go b/x/validator-vesting/module.go index 19c8f220..075811e9 100644 --- a/x/validator-vesting/module.go +++ b/x/validator-vesting/module.go @@ -3,7 +3,6 @@ package validatorvesting import ( "encoding/json" "math/rand" - "os" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -40,7 +39,6 @@ func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { // DefaultGenesis returns default genesis state as raw bytes for the validator-vesting // module. func (AppModuleBasic) DefaultGenesis() json.RawMessage { - types.ModuleCdc.PrintTypes(os.Stdout) return types.ModuleCdc.MustMarshalJSON(types.DefaultGenesisState()) } diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go index e8bbeab3..cccdc358 100644 --- a/x/validator-vesting/simulation/genesis.go +++ b/x/validator-vesting/simulation/genesis.go @@ -35,7 +35,7 @@ func RandomizedGenState(simState *module.SimulationState) { duration := va.GetEndTime() - va.GetStartTime() vestingPeriods := getRandomVestingPeriods(duration, simState.Rand, va.GetCoins()) vestingCoins := getVestingCoins(vestingPeriods) - bva := vestingtypes.NewBaseVestingAccount(&bacc, vestingCoins, va.GetEndTime()) + bva, _ := vestingtypes.NewBaseVestingAccount(&bacc, vestingCoins, va.GetEndTime()) var gacc authexported.GenesisAccount if simState.Rand.Intn(100) < 50 { // convert to periodic vesting account 50%