Skip to content

Commit

Permalink
epoching: more test infra and some fuzz tests on keeper functionaliti…
Browse files Browse the repository at this point in the history
…es (#60)
  • Loading branch information
SebastianElvis authored Jul 14, 2022
1 parent 3e12fc0 commit 5d15ff1
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 226 deletions.
18 changes: 9 additions & 9 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,15 @@ func NewBabylonApp(
stakingKeeper := stakingkeeper.NewKeeper(
appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName),
)

// NOTE: the epoching module has to be set before the chekpointing module, as the checkpointing module will have access to the epoching module
epochingKeeper := epochingkeeper.NewKeeper(
appCodec, keys[epochingtypes.StoreKey], keys[epochingtypes.StoreKey], app.GetSubspace(epochingtypes.ModuleName), &app.StakingKeeper,
)
// add msgServiceRouter so that the epoching module can forward unwrapped messages to the staking module
epochingKeeper.SetMsgServiceRouter(app.BaseApp.MsgServiceRouter())
app.EpochingKeeper = epochingKeeper

app.MintKeeper = mintkeeper.NewKeeper(
appCodec, keys[minttypes.StoreKey], app.GetSubspace(minttypes.ModuleName), &stakingKeeper,
app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName,
Expand Down Expand Up @@ -321,15 +330,6 @@ func NewBabylonApp(
),
)

// setup epoching keeper
// NOTE: the epoching module has to be set before the chekpointing module, as the checkpointing module will have access to the epoching module
epochingKeeper := epochingkeeper.NewKeeper(appCodec, keys[epochingtypes.StoreKey], keys[epochingtypes.StoreKey], app.GetSubspace(epochingtypes.ModuleName), &app.StakingKeeper)
// TODO: add modules that need to hook onto the epoching module here
epochingKeeper.SetHooks(epochingtypes.NewMultiEpochingHooks())
// add msgServiceRouter so that the epoching module can forward unwrapped messages to the staking module
epochingKeeper.SetMsgServiceRouter(app.BaseApp.MsgServiceRouter())
app.EpochingKeeper = epochingKeeper

btclightclientKeeper := *btclightclientkeeper.NewKeeper(appCodec, keys[btclightclienttypes.StoreKey], keys[btclightclienttypes.MemStoreKey], app.GetSubspace(btclightclienttypes.ModuleName))
btclightclientKeeper.SetHooks(btclightclienttypes.NewMultiBTCLightClientHooks())
app.BTCLightClientKeeper = btclightclientKeeper
Expand Down
23 changes: 15 additions & 8 deletions app/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func Setup(isCheckTx bool) *BabylonApp {
// that also act as delegators. For simplicity, each validator is bonded with a delegation
// of one consensus engine unit (10^6) in the default token of the babylon app from first genesis
// account. A Nop logger is set in BabylonApp.
func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *BabylonApp {
func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) (*BabylonApp, sdk.Context) {
app, genesisState := setup(true, 5)
// set genesis accounts
authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs)
Expand Down Expand Up @@ -120,22 +120,26 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs
}
validators = append(validators, validator)
delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec()))

}

// total bond amount = bond amount * number of validators
require.Equal(t, len(validators), len(delegations))
totalBondAmt := bondAmt.MulRaw(int64(len(validators)))

// set validators and delegations
stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations)
genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis)

totalSupply := sdk.NewCoins()
for _, b := range balances {
// add genesis acc tokens and delegated tokens to total supply
totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt))...)
totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(sdk.DefaultBondDenom, totalBondAmt))...)
}

// add bonded amount to bonded pool module account
balances = append(balances, banktypes.Balance{
Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)},
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, totalBondAmt)},
})

// update total supply
Expand All @@ -156,14 +160,17 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs

// commit genesis changes
app.Commit()
app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{
Height: app.LastBlockHeight() + 1,
height := app.LastBlockHeight() + 1
header := tmproto.Header{
Height: height,
AppHash: app.LastCommitID().Hash,
ValidatorsHash: valSet.Hash(),
NextValidatorsHash: valSet.Hash(),
}})
}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := app.BaseApp.NewContext(false, tmproto.Header{})

return app
return app, ctx
}

// SetupWithGenesisAccounts initializes a new BabylonApp with the provided genesis
Expand Down
53 changes: 53 additions & 0 deletions testutil/datagen/priv_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package datagen

import (
"github.com/tendermint/tendermint/crypto"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmtypes "github.com/tendermint/tendermint/types"

cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
)

// adapted from https://github.com/ClanNetwork/clan-network/blob/d00b1cc465/testutil/simapp/pv.go
// used for creating key pairs for genesis validators in tests

var _ tmtypes.PrivValidator = PV{}

// PV implements PrivValidator without any safety or persistence.
// Only use it for testing.
type PV struct {
PrivKey cryptotypes.PrivKey
}

func NewPV() PV {
return PV{ed25519.GenPrivKey()}
}

// GetPubKey implements PrivValidator interface
func (pv PV) GetPubKey() (crypto.PubKey, error) {
return cryptocodec.ToTmPubKeyInterface(pv.PrivKey.PubKey())
}

// SignVote implements PrivValidator interface
func (pv PV) SignVote(chainID string, vote *tmproto.Vote) error {
signBytes := tmtypes.VoteSignBytes(chainID, vote)
sig, err := pv.PrivKey.Sign(signBytes)
if err != nil {
return err
}
vote.Signature = sig
return nil
}

// SignProposal implements PrivValidator interface
func (pv PV) SignProposal(chainID string, proposal *tmproto.Proposal) error {
signBytes := tmtypes.ProposalSignBytes(chainID, proposal)
sig, err := pv.PrivKey.Sign(signBytes)
if err != nil {
return err
}
proposal.Signature = sig
return nil
}
5 changes: 3 additions & 2 deletions x/epoching/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import (
func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

// if this block is the first block of an epoch
// if this block is the first block of the next epoch
// note that we haven't incremented the epoch number yet
if k.GetEpoch(ctx).IsFirstBlock(ctx) {
epoch := k.GetEpoch(ctx)
if epoch.IsFirstBlockOfNextEpoch(ctx) {
// increase epoch number
IncEpoch := k.IncEpoch(ctx)
// init the slashed voting power of this new epoch
Expand Down
52 changes: 52 additions & 0 deletions x/epoching/keeper/epoch_msg_queue_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package keeper_test

import (
"math/rand"
"testing"

"github.com/babylonchain/babylon/x/epoching/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

func FuzzEpochMsgQueue(f *testing.F) {
f.Add(int64(11111))
f.Add(int64(22222))
f.Add(int64(55555))
f.Add(int64(12312))

f.Fuzz(func(t *testing.T, seed int64) {
rand.Seed(seed)

_, ctx, keeper, _, _ := setupTestKeeper()
// ensure that the epoch msg queue is correct at the genesis
require.Empty(t, keeper.GetEpochMsgs(ctx))
require.Equal(t, uint64(0), keeper.GetQueueLength(ctx))

// Enqueue a random number of msgs
numQueuedMsgs := rand.Uint64() % 100
for i := uint64(0); i < numQueuedMsgs; i++ {
msg := types.QueuedMessage{
TxId: sdk.Uint64ToBigEndian(i),
MsgId: sdk.Uint64ToBigEndian(i),
}
keeper.EnqueueMsg(ctx, msg)
}

// ensure that each msg in the queue is correct
epochMsgs := keeper.GetEpochMsgs(ctx)
for i, msg := range epochMsgs {
require.Equal(t, sdk.Uint64ToBigEndian(uint64(i)), msg.TxId)
require.Equal(t, sdk.Uint64ToBigEndian(uint64(i)), msg.MsgId)
require.Nil(t, msg.Msg)
}

// after clearing the msg queue, ensure that the epoch msg queue is empty
keeper.ClearEpochMsgs(ctx)
require.Empty(t, keeper.GetEpochMsgs(ctx))
require.Equal(t, uint64(0), keeper.GetQueueLength(ctx))
})
}

// TODO (stateful tests): fuzz HandleQueueMsg. initialise some validators, let them submit some msgs and trigger HandleQueueMsg
// require mocking valid QueueMsgs
4 changes: 4 additions & 0 deletions x/epoching/keeper/epoch_slashed_val_set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package keeper_test

// TODO (stateful tests): slash some random validators and check if the resulting (slashed) validator sets are consistent or not
// require mocking slashing
43 changes: 43 additions & 0 deletions x/epoching/keeper/epoch_val_set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package keeper_test

import (
"math/rand"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

func FuzzEpochValSet(f *testing.F) {
f.Add(int64(11111))
f.Add(int64(22222))
f.Add(int64(55555))
f.Add(int64(12312))

f.Fuzz(func(t *testing.T, seed int64) {
rand.Seed(seed)

app, ctx, keeper, _, _, valSet := setupTestKeeperWithValSet(t)
getValSet := keeper.GetValidatorSet(ctx, 0)
require.Equal(t, len(valSet.Validators), len(getValSet))
for i := range getValSet {
require.Equal(t, sdk.ValAddress(valSet.Validators[i].Address), getValSet[i].Addr)
}

// generate a random number of new blocks
numIncBlocks := rand.Uint64()%1000 + 1
for i := uint64(0); i < numIncBlocks; i++ {
ctx = genAndApplyEmptyBlock(app, ctx)
}

// check whether the validator set remains the same or not
getValSet2 := keeper.GetValidatorSet(ctx, keeper.GetEpoch(ctx).EpochNumber)
require.Equal(t, len(valSet.Validators), len(getValSet2))
for i := range getValSet2 {
require.Equal(t, sdk.ValAddress(valSet.Validators[i].Address), getValSet[i].Addr)
}
})
}

// TODO (stateful tests): create some random validators and check if the resulting validator set is consistent or not
// require mocking Msg(Wrapped)CreateValidator
47 changes: 47 additions & 0 deletions x/epoching/keeper/epochs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package keeper_test

import (
"math/rand"
"testing"

"github.com/babylonchain/babylon/x/epoching/types"
"github.com/stretchr/testify/require"
)

func FuzzEpochs(f *testing.F) {
f.Add(int64(11111))
f.Add(int64(22222))
f.Add(int64(55555))
f.Add(int64(12312))

f.Fuzz(func(t *testing.T, seed int64) {
rand.Seed(seed)

app, ctx, keeper, _, _ := setupTestKeeper()
// ensure that the epoch info is correct at the genesis
epoch := keeper.GetEpoch(ctx)
require.Equal(t, epoch.EpochNumber, uint64(0))
require.Equal(t, epoch.FirstBlockHeight, uint64(0))

// set a random epoch interval
epochInterval := rand.Uint64()%100 + 1
keeper.SetParams(ctx, types.Params{
EpochInterval: epochInterval,
})
// increment a random number of new blocks
numIncBlocks := rand.Uint64()%1000 + 1
for i := uint64(0); i < numIncBlocks; i++ {
ctx = genAndApplyEmptyBlock(app, ctx)
}

// ensure that the epoch info is still correct
expectedEpochNumber := numIncBlocks / epochInterval
if numIncBlocks%epochInterval > 0 {
expectedEpochNumber += 1
}
actualNewEpoch := keeper.GetEpoch(ctx)
require.Equal(t, expectedEpochNumber, actualNewEpoch.EpochNumber)
require.Equal(t, epochInterval, actualNewEpoch.CurrentEpochInterval)
require.Equal(t, (expectedEpochNumber-1)*epochInterval+1, actualNewEpoch.FirstBlockHeight)
})
}
Loading

0 comments on commit 5d15ff1

Please sign in to comment.