Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(sequencers): incorrect sorting mechanism allows manipulation of proposer selection #1292

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions x/sequencer/keeper/invariants.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ func SequencerPositiveBalancePostBondReduction(k Keeper) sdk.Invariant {
sequencers := k.GetAllSequencers(ctx)
for _, seq := range sequencers {
effectiveBond := seq.Tokens

// assert single denom for bond
if effectiveBond.Len() != 1 {
broken = true
msg += "sequencer has multiple denoms " + seq.Address + "\n"
}

if bondReductions := k.GetBondReductionsBySequencer(ctx, seq.Address); len(bondReductions) > 0 {
for _, bd := range bondReductions {
effectiveBond = effectiveBond.Sub(bd.DecreaseBondAmount)
Expand Down
32 changes: 13 additions & 19 deletions x/sequencer/keeper/msg_server_create_sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,22 @@ func (k msgServer) CreateSequencer(goCtx context.Context, msg *types.MsgCreateSe
}
}

bond := sdk.Coins{}
if minBond := k.GetParams(ctx).MinBond; !(minBond.IsNil() || minBond.IsZero()) {
if msg.Bond.Denom != minBond.Denom {
return nil, errorsmod.Wrapf(
types.ErrInvalidCoinDenom, "got %s, expected %s", msg.Bond.Denom, minBond.Denom,
)
}

if msg.Bond.Amount.LT(minBond.Amount) {
return nil, errorsmod.Wrapf(
types.ErrInsufficientBond, "got %s, expected %s", msg.Bond.Amount, minBond,
)
}
// validate bond requirement
minBond := k.GetParams(ctx).MinBond
if !msg.Bond.IsGTE(minBond) {
Copy link
Contributor

@omritoptix omritoptix Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we removed the check if the bond denom is the same as the minBond denom. in that case where do we enforce someone doesn't bond other token denom vs the bond denom defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's checked in IsGTE

// IsGTE returns true if they are the same type and the receiver is
// an equal or greater value

return nil, errorsmod.Wrapf(
types.ErrInsufficientBond, "got %s, expected %s", msg.Bond, minBond,
)
}

seqAcc := sdk.MustAccAddressFromBech32(msg.Creator)
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, seqAcc, types.ModuleName, sdk.NewCoins(msg.Bond))
if err != nil {
return nil, err
}
bond = sdk.NewCoins(msg.Bond)
// send bond to module account
seqAcc := sdk.MustAccAddressFromBech32(msg.Creator)
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, seqAcc, types.ModuleName, sdk.NewCoins(msg.Bond))
if err != nil {
return nil, err
}

bond := sdk.NewCoins(msg.Bond)
sequencer := types.Sequencer{
Address: msg.Creator,
DymintPubKey: msg.DymintPubKey,
Expand Down
96 changes: 53 additions & 43 deletions x/sequencer/keeper/msg_server_create_sequencer_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package keeper_test

import (
"errors"
"fmt"
"reflect"
"time"

errorsmod "cosmossdk.io/errors"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -25,75 +27,83 @@ const (
var bond = types.DefaultParams().MinBond

func (suite *SequencerTestSuite) TestMinBond() {
panicErr := errors.New("panic")

testCases := []struct {
name string
requiredBond sdk.Coin
bond sdk.Coin
expectedError error
}{
{
name: "No bond required",
requiredBond: sdk.Coin{},
bond: sdk.NewCoin("adym", sdk.NewInt(10000000)),
expectedError: nil,
},
{
name: "Valid bond",
requiredBond: bond,
bond: bond,
expectedError: nil,
},
{
name: "Bad denom",
requiredBond: bond,
bond: sdk.NewCoin("invalid", sdk.NewInt(100)),
expectedError: types.ErrInvalidCoinDenom,
},
{
name: "Insufficient bond",
requiredBond: bond,
bond: sdk.NewCoin(bond.Denom, bond.Amount.Quo(sdk.NewInt(2))),
expectedError: types.ErrInsufficientBond,
},
{
name: "wrong bond denom",
Copy link
Contributor

@omritoptix omritoptix Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also this test seems to test the wrong thing - i.e fail on invalid coin denom vs valid but not what we expected (i.e wrong denom).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the test a bit

requiredBond: bond,
bond: sdk.NewCoin("nonbonddenom", bond.Amount),
expectedError: panicErr,
},
}

for _, tc := range testCases {
seqParams := types.DefaultParams()
seqParams.MinBond = tc.requiredBond
suite.App.SequencerKeeper.SetParams(suite.Ctx, seqParams)
suite.Run(tc.name, func() {
seqParams := types.DefaultParams()
seqParams.MinBond = tc.requiredBond
suite.App.SequencerKeeper.SetParams(suite.Ctx, seqParams)

rollappId, pk := suite.CreateDefaultRollapp()
rollappId, pk := suite.CreateDefaultRollapp()

// fund account
addr := sdk.AccAddress(pk.Address())
pkAny, err := codectypes.NewAnyWithValue(pk)
suite.Require().Nil(err)
err = bankutil.FundAccount(suite.App.BankKeeper, suite.Ctx, addr, sdk.NewCoins(tc.bond))
suite.Require().Nil(err)
// fund account
addr := sdk.AccAddress(pk.Address())
pkAny, err := codectypes.NewAnyWithValue(pk)
suite.Require().Nil(err)
err = bankutil.FundAccount(suite.App.BankKeeper, suite.Ctx, addr, sdk.NewCoins(tc.bond))
suite.Require().Nil(err)

sequencerMsg1 := types.MsgCreateSequencer{
Creator: addr.String(),
DymintPubKey: pkAny,
Bond: bond,
RollappId: rollappId,
Metadata: types.SequencerMetadata{
Rpcs: []string{"https://rpc.wpd.evm.rollapp.noisnemyd.xyz:443"},
},
}
_, err = suite.msgServer.CreateSequencer(suite.Ctx, &sequencerMsg1)
if tc.expectedError != nil {
tc := tc
suite.Require().ErrorAs(err, &tc.expectedError, tc.name)
} else {
suite.Require().NoError(err)
sequencer, found := suite.App.SequencerKeeper.GetSequencer(suite.Ctx, addr.String())
suite.Require().True(found, tc.name)
if tc.requiredBond.IsNil() {
suite.Require().True(sequencer.Tokens.IsZero(), tc.name)
sequencerMsg1 := types.MsgCreateSequencer{
Creator: addr.String(),
DymintPubKey: pkAny,
Bond: tc.bond,
RollappId: rollappId,
Metadata: types.SequencerMetadata{
Rpcs: []string{"https://rpc.wpd.evm.rollapp.noisnemyd.xyz:443"},
},
}

// Use a defer and recover to catch potential panics
var createErr error
func() {
defer func() {
if r := recover(); r != nil {
createErr = errorsmod.Wrapf(panicErr, "panic: %v", r)
}
}()
_, createErr = suite.msgServer.CreateSequencer(suite.Ctx, &sequencerMsg1)
}()

if tc.expectedError != nil {
suite.Require().ErrorAs(createErr, &tc.expectedError, tc.name)
} else {
suite.Require().Equal(sdk.NewCoins(tc.requiredBond), sequencer.Tokens, tc.name)
suite.Require().NoError(createErr)
sequencer, found := suite.App.SequencerKeeper.GetSequencer(suite.Ctx, addr.String())
suite.Require().True(found, tc.name)
if tc.requiredBond.IsNil() {
suite.Require().True(sequencer.Tokens.IsZero(), tc.name)
} else {
suite.Require().Equal(sdk.NewCoins(tc.requiredBond), sequencer.Tokens, tc.name)
}
}
}
})
}
}

Expand Down
5 changes: 5 additions & 0 deletions x/sequencer/keeper/msg_server_increase_bond.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ func (k msgServer) IncreaseBond(goCtx context.Context, msg *types.MsgIncreaseBon
return nil, err
}

// validate the addition amt is of same denom of existing bond
if found, _ := sequencer.Tokens.Find(msg.AddAmount.Denom); !found {
return nil, types.ErrInvalidCoinDenom
}

// update the sequencers bond amount in state
sequencer.Tokens = sequencer.Tokens.Add(msg.AddAmount)
k.UpdateSequencer(ctx, &sequencer, sequencer.Status)
Expand Down
4 changes: 4 additions & 0 deletions x/sequencer/keeper/unbond.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"fmt"
"time"

errorsmod "cosmossdk.io/errors"
Expand Down Expand Up @@ -73,6 +74,9 @@ func (k Keeper) reduceSequencerBond(ctx sdk.Context, seq *types.Sequencer, amt s
if amt.IsZero() {
return nil
}
if !seq.Tokens.IsAllGTE(amt) {
return fmt.Errorf("sequencer does not have enough bond: got %s, reducing by %s", seq.Tokens.String(), amt.String())
}
if burn {
err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, amt)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion x/sequencer/types/msg_create_sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (msg *MsgCreateSequencer) ValidateBasic() error {
return errorsmod.Wrap(ErrInvalidMetadata, err.Error())
}

if !msg.Bond.IsValid() {
if !msg.Bond.IsValid() || msg.Bond.IsZero() {
return errorsmod.Wrapf(ErrInvalidCoins, "invalid bond amount: %s", msg.Bond.String())
}

Expand Down
2 changes: 1 addition & 1 deletion x/sequencer/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func validateMinBond(i interface{}) error {
}

if v.IsNil() || v.IsZero() {
return nil
return fmt.Errorf("min bond must be positive: %s", v)
}

if !v.IsValid() {
Expand Down
Loading