-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Co-authored-by: Julien Robert <julien@rbrt.fr>
- Loading branch information
1 parent
ac168db
commit 3335aa6
Showing
3 changed files
with
235 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
package keeper_test | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | ||
"gotest.tools/v3/assert" | ||
|
||
codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" | ||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
"github.com/cosmos/cosmos-sdk/runtime" | ||
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" | ||
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" | ||
"github.com/cosmos/cosmos-sdk/x/evidence/exported" | ||
"github.com/cosmos/cosmos-sdk/x/evidence/keeper" | ||
"github.com/cosmos/cosmos-sdk/x/evidence/testutil" | ||
"github.com/cosmos/cosmos-sdk/x/evidence/types" | ||
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" | ||
slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" | ||
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" | ||
"github.com/cosmos/cosmos-sdk/x/staking" | ||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" | ||
stakingtestutil "github.com/cosmos/cosmos-sdk/x/staking/testutil" | ||
) | ||
|
||
var ( | ||
pubkeys = []cryptotypes.PubKey{ | ||
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), | ||
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), | ||
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), | ||
} | ||
|
||
valAddresses = []sdk.ValAddress{ | ||
sdk.ValAddress(pubkeys[0].Address()), | ||
sdk.ValAddress(pubkeys[1].Address()), | ||
sdk.ValAddress(pubkeys[2].Address()), | ||
} | ||
|
||
// The default power validators are initialized to have within tests | ||
initAmt = sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) | ||
initCoins = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) | ||
) | ||
|
||
type fixture struct { | ||
ctx sdk.Context | ||
app *runtime.App | ||
|
||
evidenceKeeper keeper.Keeper | ||
bankKeeper bankkeeper.Keeper | ||
accountKeeper authkeeper.AccountKeeper | ||
slashingKeeper slashingkeeper.Keeper | ||
stakingKeeper *stakingkeeper.Keeper | ||
interfaceRegistry codectypes.InterfaceRegistry | ||
} | ||
|
||
func initFixture(t assert.TestingT) *fixture { | ||
f := &fixture{} | ||
var evidenceKeeper keeper.Keeper | ||
|
||
app, err := simtestutil.Setup(testutil.AppConfig, | ||
&evidenceKeeper, | ||
&f.interfaceRegistry, | ||
&f.accountKeeper, | ||
&f.bankKeeper, | ||
&f.slashingKeeper, | ||
&f.stakingKeeper, | ||
) | ||
assert.NilError(t, err) | ||
|
||
router := types.NewRouter() | ||
router = router.AddRoute(types.RouteEquivocation, testEquivocationHandler(evidenceKeeper)) | ||
evidenceKeeper.SetRouter(router) | ||
|
||
f.ctx = app.BaseApp.NewContext(false, tmproto.Header{Height: 1}) | ||
f.app = app | ||
f.evidenceKeeper = evidenceKeeper | ||
|
||
return f | ||
} | ||
|
||
func TestHandleDoubleSign(t *testing.T) { | ||
t.Parallel() | ||
f := initFixture(t) | ||
|
||
ctx := f.ctx.WithIsCheckTx(false).WithBlockHeight(1) | ||
populateValidators(t, f) | ||
|
||
power := int64(100) | ||
stakingParams := f.stakingKeeper.GetParams(ctx) | ||
operatorAddr, val := valAddresses[0], pubkeys[0] | ||
tstaking := stakingtestutil.NewHelper(t, ctx, f.stakingKeeper) | ||
|
||
selfDelegation := tstaking.CreateValidatorWithValPower(operatorAddr, val, power, true) | ||
|
||
// execute end-blocker and verify validator attributes | ||
staking.EndBlocker(ctx, f.stakingKeeper) | ||
assert.DeepEqual(t, | ||
f.bankKeeper.GetAllBalances(ctx, sdk.AccAddress(operatorAddr)).String(), | ||
sdk.NewCoins(sdk.NewCoin(stakingParams.BondDenom, initAmt.Sub(selfDelegation))).String(), | ||
) | ||
assert.DeepEqual(t, selfDelegation, f.stakingKeeper.Validator(ctx, operatorAddr).GetBondedTokens()) | ||
|
||
// handle a signature to set signing info | ||
f.slashingKeeper.HandleValidatorSignature(ctx, val.Address(), selfDelegation.Int64(), true) | ||
|
||
// double sign less than max age | ||
oldTokens := f.stakingKeeper.Validator(ctx, operatorAddr).GetTokens() | ||
evidence := &types.Equivocation{ | ||
Height: 0, | ||
Time: time.Unix(0, 0), | ||
Power: power, | ||
ConsensusAddress: sdk.ConsAddress(val.Address()).String(), | ||
} | ||
f.evidenceKeeper.HandleEquivocationEvidence(ctx, evidence) | ||
|
||
// should be jailed and tombstoned | ||
assert.Assert(t, f.stakingKeeper.Validator(ctx, operatorAddr).IsJailed()) | ||
assert.Assert(t, f.slashingKeeper.IsTombstoned(ctx, sdk.ConsAddress(val.Address()))) | ||
|
||
// tokens should be decreased | ||
newTokens := f.stakingKeeper.Validator(ctx, operatorAddr).GetTokens() | ||
assert.Assert(t, newTokens.LT(oldTokens)) | ||
|
||
// submit duplicate evidence | ||
f.evidenceKeeper.HandleEquivocationEvidence(ctx, evidence) | ||
|
||
// tokens should be the same (capped slash) | ||
assert.Assert(t, f.stakingKeeper.Validator(ctx, operatorAddr).GetTokens().Equal(newTokens)) | ||
|
||
// jump to past the unbonding period | ||
ctx = ctx.WithBlockTime(time.Unix(1, 0).Add(stakingParams.UnbondingTime)) | ||
|
||
// require we cannot unjail | ||
assert.Error(t, f.slashingKeeper.Unjail(ctx, operatorAddr), slashingtypes.ErrValidatorJailed.Error()) | ||
|
||
// require we be able to unbond now | ||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) | ||
del, _ := f.stakingKeeper.GetDelegation(ctx, sdk.AccAddress(operatorAddr), operatorAddr) | ||
validator, _ := f.stakingKeeper.GetValidator(ctx, operatorAddr) | ||
totalBond := validator.TokensFromShares(del.GetShares()).TruncateInt() | ||
tstaking.Ctx = ctx | ||
tstaking.Denom = stakingParams.BondDenom | ||
tstaking.Undelegate(sdk.AccAddress(operatorAddr), operatorAddr, totalBond, true) | ||
|
||
// query evidence from store | ||
evidences := f.evidenceKeeper.GetAllEvidence(ctx) | ||
assert.Assert(t, len(evidences) == 1) | ||
} | ||
|
||
func TestHandleDoubleSign_TooOld(t *testing.T) { | ||
t.Parallel() | ||
f := initFixture(t) | ||
|
||
ctx := f.ctx.WithIsCheckTx(false).WithBlockHeight(1).WithBlockTime(time.Now()) | ||
populateValidators(t, f) | ||
|
||
power := int64(100) | ||
stakingParams := f.stakingKeeper.GetParams(ctx) | ||
operatorAddr, val := valAddresses[0], pubkeys[0] | ||
tstaking := stakingtestutil.NewHelper(t, ctx, f.stakingKeeper) | ||
|
||
amt := tstaking.CreateValidatorWithValPower(operatorAddr, val, power, true) | ||
|
||
// execute end-blocker and verify validator attributes | ||
staking.EndBlocker(ctx, f.stakingKeeper) | ||
assert.DeepEqual(t, | ||
f.bankKeeper.GetAllBalances(ctx, sdk.AccAddress(operatorAddr)), | ||
sdk.NewCoins(sdk.NewCoin(stakingParams.BondDenom, initAmt.Sub(amt))), | ||
) | ||
assert.DeepEqual(t, amt, f.stakingKeeper.Validator(ctx, operatorAddr).GetBondedTokens()) | ||
|
||
evidence := &types.Equivocation{ | ||
Height: 0, | ||
Time: ctx.BlockTime(), | ||
Power: power, | ||
ConsensusAddress: sdk.ConsAddress(val.Address()).String(), | ||
} | ||
|
||
cp := f.app.BaseApp.GetConsensusParams(ctx) | ||
|
||
ctx = ctx.WithConsensusParams(cp) | ||
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(cp.Evidence.MaxAgeDuration + 1)) | ||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + cp.Evidence.MaxAgeNumBlocks + 1) | ||
f.evidenceKeeper.HandleEquivocationEvidence(ctx, evidence) | ||
|
||
assert.Assert(t, f.stakingKeeper.Validator(ctx, operatorAddr).IsJailed() == false) | ||
assert.Assert(t, f.slashingKeeper.IsTombstoned(ctx, sdk.ConsAddress(val.Address())) == false) | ||
} | ||
|
||
func populateValidators(t assert.TestingT, f *fixture) { | ||
// add accounts and set total supply | ||
totalSupplyAmt := initAmt.MulRaw(int64(len(valAddresses))) | ||
totalSupply := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, totalSupplyAmt)) | ||
assert.NilError(t, f.bankKeeper.MintCoins(f.ctx, minttypes.ModuleName, totalSupply)) | ||
|
||
for _, addr := range valAddresses { | ||
assert.NilError(t, f.bankKeeper.SendCoinsFromModuleToAccount(f.ctx, minttypes.ModuleName, (sdk.AccAddress)(addr), initCoins)) | ||
} | ||
} | ||
|
||
func newPubKey(pk string) (res cryptotypes.PubKey) { | ||
pkBytes, err := hex.DecodeString(pk) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
pubkey := &ed25519.PubKey{Key: pkBytes} | ||
|
||
return pubkey | ||
} | ||
|
||
func testEquivocationHandler(_ interface{}) types.Handler { | ||
return func(ctx sdk.Context, e exported.Evidence) error { | ||
if err := e.ValidateBasic(); err != nil { | ||
return err | ||
} | ||
|
||
ee, ok := e.(*types.Equivocation) | ||
if !ok { | ||
return fmt.Errorf("unexpected evidence type: %T", e) | ||
} | ||
if ee.Height%2 == 0 { | ||
return fmt.Errorf("unexpected even evidence height: %d", ee.Height) | ||
} | ||
|
||
return nil | ||
} | ||
} |
Oops, something went wrong.