Skip to content

Commit

Permalink
fix!: check v1 and v1beta1 MsgVote in VoteSpamDecorator
Browse files Browse the repository at this point in the history
#2912 introuced the VoteSpamDecorator.

The implementation was not complete and only v1beta1 gov types were handled.

The CLI supports v1 gov types so the validation was not sufficient.

This commit expands the antehandler validation to allow correct processing of both v1 and v1beta1 messages.
  • Loading branch information
MSalopek committed Feb 5, 2024
1 parent aae0c6c commit f22537f
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 32 deletions.
69 changes: 44 additions & 25 deletions ante/gov_vote_ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/authz"

govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"

stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

Expand Down Expand Up @@ -50,36 +53,52 @@ func (g GovVoteDecorator) AnteHandle(
// ValidateVoteMsgs checks if a voter has enough stake to vote
func (g GovVoteDecorator) ValidateVoteMsgs(ctx sdk.Context, msgs []sdk.Msg) error {
validMsg := func(m sdk.Msg) error {
if msg, ok := m.(*govv1beta1.MsgVote); ok {
accAddr, err := sdk.AccAddressFromBech32(msg.Voter)
var accAddr sdk.AccAddress
var err error

switch msg := m.(type) {
case *govv1beta1.MsgVote:
accAddr, err = sdk.AccAddressFromBech32(msg.Voter)
if err != nil {
return err

}
enoughStake := false
delegationCount := 0
stakedTokens := sdk.NewDec(0)
g.stakingKeeper.IterateDelegatorDelegations(ctx, accAddr, func(delegation stakingtypes.Delegation) bool {
validatorAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress)
if err != nil {
panic(err) // shouldn't happen
}
validator, found := g.stakingKeeper.GetValidator(ctx, validatorAddr)
if found {
shares := delegation.Shares
tokens := validator.TokensFromSharesTruncated(shares)
stakedTokens = stakedTokens.Add(tokens)
if stakedTokens.GTE(minStakedTokens) {
enoughStake = true
return true // break the iteration
}
case *govv1.MsgVote:
accAddr, err = sdk.AccAddressFromBech32(msg.Voter)
if err != nil {
return err

}
default:
// not a vote message - nothing to validate
return nil
}

enoughStake := false
delegationCount := 0
stakedTokens := sdk.NewDec(0)
g.stakingKeeper.IterateDelegatorDelegations(ctx, accAddr, func(delegation stakingtypes.Delegation) bool {
validatorAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress)
if err != nil {
panic(err) // shouldn't happen
}
validator, found := g.stakingKeeper.GetValidator(ctx, validatorAddr)
if found {
shares := delegation.Shares
tokens := validator.TokensFromSharesTruncated(shares)
stakedTokens = stakedTokens.Add(tokens)
if stakedTokens.GTE(minStakedTokens) {
enoughStake = true
return true // break the iteration
}
delegationCount++
// break the iteration if maxDelegationsChecked were already checked
return delegationCount >= maxDelegationsChecked
})
if !enoughStake {
return errorsmod.Wrapf(gaiaerrors.ErrInsufficientStake, "insufficient stake for voting - min required %v", minStakedTokens)
}
delegationCount++
// break the iteration if maxDelegationsChecked were already checked
return delegationCount >= maxDelegationsChecked
})

if !enoughStake {
return errorsmod.Wrapf(gaiaerrors.ErrInsufficientStake, "insufficient stake for voting - min required %v", minStakedTokens)
}

return nil
Expand Down
140 changes: 133 additions & 7 deletions ante/gov_vote_ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ import (

"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
sdk "github.com/cosmos/cosmos-sdk/types"
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/cosmos/gaia/v15/ante"
"github.com/cosmos/gaia/v15/app/helpers"
)

func TestVoteSpamDecorator(t *testing.T) {
// Test that the GovVoteDecorator rejects v1beta1 vote messages from accounts with less than 1 atom staked
// Submitting v1beta1.VoteMsg should not be possible through the CLI, but it's still possible to craft a transaction
func TestVoteSpamDecoratorGovV1Beta1(t *testing.T) {
gaiaApp := helpers.Setup(t)
ctx := gaiaApp.NewUncachedContext(true, tmproto.Header{})
decorator := ante.NewGovVoteDecorator(gaiaApp.AppCodec(), gaiaApp.StakingKeeper)
Expand Down Expand Up @@ -56,6 +59,12 @@ func TestVoteSpamDecorator(t *testing.T) {
validators []sdk.ValAddress
expectPass bool
}{
{
name: "delegate 0 atom",
bondAmt: sdk.ZeroInt(),
validators: []sdk.ValAddress{valAddr1},
expectPass: false,
},
{
name: "delegate 0.1 atom",
bondAmt: sdk.NewInt(100000),
Expand Down Expand Up @@ -97,12 +106,14 @@ func TestVoteSpamDecorator(t *testing.T) {
}

// Delegate tokens
amt := tc.bondAmt.Quo(sdk.NewInt(int64(len(tc.validators))))
for _, valAddr := range tc.validators {
val, found := stakingKeeper.GetValidator(ctx, valAddr)
require.True(t, found)
_, err := stakingKeeper.Delegate(ctx, delegator, amt, stakingtypes.Unbonded, val, true)
require.NoError(t, err)
if !tc.bondAmt.IsZero() {
amt := tc.bondAmt.Quo(sdk.NewInt(int64(len(tc.validators))))
for _, valAddr := range tc.validators {
val, found := stakingKeeper.GetValidator(ctx, valAddr)
require.True(t, found)
_, err := stakingKeeper.Delegate(ctx, delegator, amt, stakingtypes.Unbonded, val, true)
require.NoError(t, err)
}
}

// Create vote message
Expand All @@ -121,3 +132,118 @@ func TestVoteSpamDecorator(t *testing.T) {
}
}
}

// Test that the GovVoteDecorator rejects v1 vote messages from accounts with less than 1 atom staked
// Usually, only v1.VoteMsg can be submitted using the CLI.
func TestVoteSpamDecoratorGovV1(t *testing.T) {
gaiaApp := helpers.Setup(t)
ctx := gaiaApp.NewUncachedContext(true, tmproto.Header{})
decorator := ante.NewGovVoteDecorator(gaiaApp.AppCodec(), gaiaApp.StakingKeeper)
stakingKeeper := gaiaApp.StakingKeeper

// Get validator
valAddr1 := stakingKeeper.GetAllValidators(ctx)[0].GetOperator()

// Create one more validator
pk := ed25519.GenPrivKeyFromSecret([]byte{uint8(13)}).PubKey()
validator2, err := stakingtypes.NewValidator(
sdk.ValAddress(pk.Address()),
pk,
stakingtypes.Description{},
)
valAddr2 := validator2.GetOperator()
require.NoError(t, err)
// Make sure the validator is bonded so it's not removed on Undelegate
validator2.Status = stakingtypes.Bonded
stakingKeeper.SetValidator(ctx, validator2)
err = stakingKeeper.SetValidatorByConsAddr(ctx, validator2)
require.NoError(t, err)
stakingKeeper.SetNewValidatorByPowerIndex(ctx, validator2)
err = stakingKeeper.Hooks().AfterValidatorCreated(ctx, validator2.GetOperator())
require.NoError(t, err)

// Get delegator (this account was created during setup)
addr := gaiaApp.AccountKeeper.GetAccountAddressByID(ctx, 0)
delegator, err := sdk.AccAddressFromBech32(addr)
require.NoError(t, err)

tests := []struct {
name string
bondAmt math.Int
validators []sdk.ValAddress
expectPass bool
}{
{
name: "delegate 0 atom",
bondAmt: sdk.ZeroInt(),
validators: []sdk.ValAddress{valAddr1},
expectPass: false,
},
{
name: "delegate 0.1 atom",
bondAmt: sdk.NewInt(100000),
validators: []sdk.ValAddress{valAddr1},
expectPass: false,
},
{
name: "delegate 1 atom",
bondAmt: sdk.NewInt(1000000),
validators: []sdk.ValAddress{valAddr1},
expectPass: true,
},
{
name: "delegate 1 atom to two validators",
bondAmt: sdk.NewInt(1000000),
validators: []sdk.ValAddress{valAddr1, valAddr2},
expectPass: true,
},
{
name: "delegate 0.9 atom to two validators",
bondAmt: sdk.NewInt(900000),
validators: []sdk.ValAddress{valAddr1, valAddr2},
expectPass: false,
},
{
name: "delegate 10 atom",
bondAmt: sdk.NewInt(10000000),
validators: []sdk.ValAddress{valAddr1},
expectPass: true,
},
}

for _, tc := range tests {
// Unbond all tokens for this delegator
delegations := stakingKeeper.GetAllDelegatorDelegations(ctx, delegator)
for _, del := range delegations {
_, err := stakingKeeper.Undelegate(ctx, delegator, del.GetValidatorAddr(), del.GetShares())
require.NoError(t, err)
}

// Delegate tokens
if !tc.bondAmt.IsZero() {
amt := tc.bondAmt.Quo(sdk.NewInt(int64(len(tc.validators))))
for _, valAddr := range tc.validators {
val, found := stakingKeeper.GetValidator(ctx, valAddr)
require.True(t, found)
_, err := stakingKeeper.Delegate(ctx, delegator, amt, stakingtypes.Unbonded, val, true)
require.NoError(t, err)
}
}

// Create vote message
msg := govv1.NewMsgVote(
delegator,
0,
govv1.VoteOption_VOTE_OPTION_YES,
"new-v1-vote-message-test",
)

// Validate vote message
err := decorator.ValidateVoteMsgs(ctx, []sdk.Msg{msg})
if tc.expectPass {
require.NoError(t, err, "expected %v to pass", tc.name)
} else {
require.Error(t, err, "expected %v to fail", tc.name)
}
}
}

0 comments on commit f22537f

Please sign in to comment.