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

epoching: API for querying the validator set of a given epoch #236

Merged
merged 3 commits into from
Dec 7, 2022
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 proto/babylon/epoching/v1/epoching.proto
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,10 @@ message DelegationLifecycle {
string del_addr = 1;
repeated DelegationStateUpdate del_life = 2;
}

message Validator {
// addr is the validator's address (in sdk.ValAddress)
bytes addr = 1;
// power is the validator's voting power
int64 power = 2;
}
16 changes: 16 additions & 0 deletions proto/babylon/epoching/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ service Query {
rpc DelegationLifecycle(QueryDelegationLifecycleRequest) returns (QueryDelegationLifecycleResponse) {
option (google.api.http).get = "/babylon/epoching/v1/delegation_lifecycle/{del_addr}";
}

// EpochValSet queries the validator set of a given epoch
rpc EpochValSet(QueryEpochValSetRequest) returns (QueryEpochValSetResponse) {
option (google.api.http).get = "/babylon/epoching/v1/epochs/{epoch_num=*}/validator_set";
}
}

// QueryParamsRequest is the request type for the Query/Params RPC method.
Expand Down Expand Up @@ -127,3 +132,14 @@ message QueryDelegationLifecycleRequest {
message QueryDelegationLifecycleResponse {
DelegationLifecycle del_life = 1;
}

message QueryEpochValSetRequest {
uint64 epoch_num = 1;
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

message QueryEpochValSetResponse {
repeated babylon.epoching.v1.Validator validators = 1;
int64 total_voting_power = 2;
cosmos.base.query.v1beta1.PageResponse pagination = 3;
}
9 changes: 5 additions & 4 deletions x/checkpointing/keeper/grpc_query_bls_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package keeper_test

import (
"math/rand"
"testing"

"github.com/babylonchain/babylon/app"
"github.com/babylonchain/babylon/crypto/bls12381"
"github.com/babylonchain/babylon/testutil/datagen"
Expand All @@ -11,8 +14,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/stretchr/testify/require"
"math/rand"
"testing"
)

// FuzzQueryBLSKeySet does the following checks
Expand Down Expand Up @@ -53,7 +54,7 @@ func FuzzQueryBLSKeySet(f *testing.F) {
require.NoError(t, err)
require.Len(t, res.ValidatorWithBlsKeys, 1)
require.Equal(t, res.ValidatorWithBlsKeys[0].BlsPubKey, genesisBLSPubkey.Bytes())
require.Equal(t, res.ValidatorWithBlsKeys[0].ValidatorAddress, genesisVal.Addr.String())
require.Equal(t, res.ValidatorWithBlsKeys[0].ValidatorAddress, genesisVal.GetValAddressStr())

// add n new validators via MsgWrappedCreateValidator
n := rand.Intn(3) + 1
Expand Down Expand Up @@ -88,7 +89,7 @@ func FuzzQueryBLSKeySet(f *testing.F) {
expectedValSet := ek.GetValidatorSet(ctx, 2)
require.Len(t, expectedValSet, n+1)
for i, expectedVal := range expectedValSet {
require.Equal(t, expectedVal.Addr.String(), resp.ValidatorWithBlsKeys[i].ValidatorAddress)
require.Equal(t, expectedVal.GetValAddressStr(), resp.ValidatorWithBlsKeys[i].ValidatorAddress)
}

// 3.1 query BLS public keys when there are n+1 validators with limit pagination
Expand Down
2 changes: 1 addition & 1 deletion x/checkpointing/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ func (k Keeper) GetBLSPubKeySet(ctx sdk.Context, epochNumber uint64) ([]*types.V
return nil, err
}
valWithblsKeys[i] = &types.ValidatorWithBlsKey{
ValidatorAddress: val.Addr.String(),
ValidatorAddress: val.GetValAddressStr(),
BlsPubKey: pubkey,
}
}
Expand Down
7 changes: 4 additions & 3 deletions x/checkpointing/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package keeper_test

import (
"math/rand"
"testing"

"github.com/babylonchain/babylon/app"
appparams "github.com/babylonchain/babylon/app/params"
"github.com/babylonchain/babylon/crypto/bls12381"
Expand All @@ -14,8 +17,6 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
"math/rand"
"testing"
)

// FuzzWrappedCreateValidator tests adding new validators via
Expand Down Expand Up @@ -76,7 +77,7 @@ func FuzzWrappedCreateValidator(f *testing.F) {
for _, msg := range wcvMsgs {
found := false
for _, val := range valSet {
if msg.MsgCreateValidator.ValidatorAddress == val.Addr.String() {
if msg.MsgCreateValidator.ValidatorAddress == val.GetValAddressStr() {
found = true
}
}
Expand Down
2 changes: 1 addition & 1 deletion x/epoching/keeper/epoch_slashed_val_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func FuzzSlashedValSet(f *testing.F) {
sortVals(excpectedSlashedVals)
actualSlashedVals = types.NewSortedValidatorSet(actualSlashedVals)
for i := range actualSlashedVals {
require.Equal(t, excpectedSlashedVals[i], actualSlashedVals[i].Addr)
require.Equal(t, excpectedSlashedVals[i], actualSlashedVals[i].GetValAddress())
}

// go to the 1st block and thus epoch 1
Expand Down
4 changes: 2 additions & 2 deletions x/epoching/keeper/epoch_val_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func FuzzEpochValSet(f *testing.F) {
for i := range getValSet {
consAddr, err := valSet[i].GetConsAddr()
require.NoError(t, err)
require.Equal(t, sdk.ValAddress(consAddr), getValSet[i].Addr)
require.Equal(t, sdk.ValAddress(consAddr), getValSet[i].GetValAddress())
}

// generate a random number of new blocks
Expand All @@ -41,7 +41,7 @@ func FuzzEpochValSet(f *testing.F) {
for i := range getValSet2 {
consAddr, err := valSet[i].GetConsAddr()
require.NoError(t, err)
require.Equal(t, sdk.ValAddress(consAddr), getValSet[i].Addr)
require.Equal(t, sdk.ValAddress(consAddr), getValSet[i].GetValAddress())
}
})
}
Expand Down
38 changes: 38 additions & 0 deletions x/epoching/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,41 @@ func (k Keeper) DelegationLifecycle(c context.Context, req *types.QueryDelegatio
DelLife: lc,
}, nil
}

func (k Keeper) EpochValSet(c context.Context, req *types.QueryEpochValSetRequest) (*types.QueryEpochValSetResponse, error) {
ctx := sdk.UnwrapSDKContext(c)

epoch := k.GetEpoch(ctx)
if epoch.EpochNumber < req.EpochNum {
return nil, types.ErrUnknownEpochNumber
}

totalVotingPower := k.GetTotalVotingPower(ctx, epoch.EpochNumber)

vals := []*types.Validator{}
epochValSetStore := k.valSetStore(ctx, epoch.EpochNumber)
pageRes, err := query.Paginate(epochValSetStore, req.Pagination, func(key, value []byte) error {
// Here key is the validator's ValAddress, and value is the voting power
var power sdk.Int
if err := power.Unmarshal(value); err != nil {
panic(sdkerrors.Wrap(types.ErrUnmarshal, err.Error())) // this only happens upon a programming error
}
val := types.Validator{
Addr: key,
Power: power.Int64(),
}
// append to msgs
vals = append(vals, &val)
return nil
})
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

resp := &types.QueryEpochValSetResponse{
Validators: vals,
TotalVotingPower: totalVotingPower,
Pagination: pageRes,
}
return resp, nil
}
67 changes: 52 additions & 15 deletions x/epoching/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package keeper_test

import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"math/rand"
"testing"

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

"github.com/babylonchain/babylon/testutil/datagen"
"github.com/babylonchain/babylon/x/epoching/testepoching"
"github.com/babylonchain/babylon/x/epoching/types"
Expand All @@ -18,15 +19,14 @@ import (
// 2. When EpochInterval is 0, ensure `Validate` returns an error
// 3. Randomly set the param via query and check if the param has been updated
func FuzzParamsQuery(f *testing.F) {
f.Add(uint64(11111), int64(23))
f.Add(uint64(22222), int64(330))
f.Add(uint64(22222), int64(101))
datagen.AddRandomSeedsToFuzzer(f, 10)

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

// params generated by fuzzer
params := types.DefaultParams()
epochInterval := datagen.RandomInt(20)
params.EpochInterval = epochInterval

// test the case of EpochInterval == 0
Expand Down Expand Up @@ -62,11 +62,13 @@ func FuzzParamsQuery(f *testing.F) {
// 2. query the current epoch and boundary
// 3. compare them with the correctly calculated ones
func FuzzCurrentEpoch(f *testing.F) {
f.Add(uint64(1111))
f.Add(uint64(2222))
f.Add(uint64(3333))
datagen.AddRandomSeedsToFuzzer(f, 10)

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

increment := datagen.RandomInt(100)

f.Fuzz(func(t *testing.T, increment uint64) {
helper := testepoching.NewHelper(t)
ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient
wctx := sdk.WrapSDKContext(ctx)
Expand All @@ -81,15 +83,12 @@ func FuzzCurrentEpoch(f *testing.F) {
})
}

// FuzzEpochMsgs fuzzes queryClient.EpochMsgs
// FuzzEpochMsgsQuery fuzzes queryClient.EpochMsgs
// 1. randomly generate msgs and limit in pagination
// 2. check the returned msg was previously enqueued
// NOTE: Msgs in QueryEpochMsgsResponse are out-of-roder
func FuzzEpochMsgs(f *testing.F) {
f.Add(int64(12))
f.Add(int64(44))
f.Add(int64(422))
f.Add(int64(4222))
func FuzzEpochMsgsQuery(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)

f.Fuzz(func(t *testing.T, seed int64) {
rand.Seed(seed)
Expand Down Expand Up @@ -137,3 +136,41 @@ func FuzzEpochMsgs(f *testing.F) {
require.Error(t, err)
})
}

// FuzzEpochMsgs fuzzes queryClient.EpochValSet
// TODO (stateful tests): create some random validators and check if the resulting validator set is consistent or not (require mocking MsgWrappedCreateValidator)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this would be helpful if you want to add more tests in a future PR

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the reference! Will do in a subsequent PR

func FuzzEpochValSetQuery(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)

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

helper := testepoching.NewHelperWithValSet(t)
ctx, queryClient := helper.Ctx, helper.QueryClient

limit := uint64(rand.Int() % 100)
req := &types.QueryEpochValSetRequest{
EpochNum: 0,
Pagination: &query.PageRequest{
Limit: limit,
},
}

resp, err := queryClient.EpochValSet(ctx, req)
require.NoError(t, err)

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

// check whether the validator set remains the same or not
resp2, err := queryClient.EpochValSet(ctx, req)
require.NoError(t, err)
require.Equal(t, len(resp.Validators), len(resp2.Validators))
for i := range resp2.Validators {
require.Equal(t, resp.Validators[i].Addr, resp2.Validators[i].Addr)
}
})
}
Loading