Skip to content

Commit

Permalink
epoching: API for querying the validator set of a given epoch (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianElvis authored Dec 7, 2022
1 parent d20d09f commit 158ad68
Show file tree
Hide file tree
Showing 13 changed files with 1,108 additions and 140 deletions.
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)
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

0 comments on commit 158ad68

Please sign in to comment.