diff --git a/go.mod b/go.mod index 9fa47e467..f39de929c 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/cosmos/cosmos-proto v1.0.0-alpha8 github.com/cosmos/ibc-go/v5 v5.1.0 github.com/golang/mock v1.6.0 + github.com/jinzhu/copier v0.3.5 google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 ) diff --git a/go.sum b/go.sum index 3c4bf2dd8..a45f8216b 100644 --- a/go.sum +++ b/go.sum @@ -605,6 +605,8 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b h1:izTof8BKh/nE1wrKOrloNA5q4odOarjf+Xpe+4qow98= +github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= +github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= diff --git a/x/checkpointing/keeper/grpc_query_bls.go b/x/checkpointing/keeper/grpc_query_bls.go index 2bc015966..ce9dfe4ac 100644 --- a/x/checkpointing/keeper/grpc_query_bls.go +++ b/x/checkpointing/keeper/grpc_query_bls.go @@ -3,8 +3,49 @@ package keeper import ( "context" "github.com/babylonchain/babylon/x/checkpointing/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/jinzhu/copier" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func (k Keeper) BlsPublicKeyList(c context.Context, req *types.QueryBlsPublicKeyListRequest) (*types.QueryBlsPublicKeyListResponse, error) { - panic("TODO: implement this") + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + sdkCtx := sdk.UnwrapSDKContext(c) + valBLSKeys, err := k.GetBLSPubKeySet(sdkCtx, req.EpochNum) + if err != nil { + return nil, err + } + + if req.Pagination == nil { + return &types.QueryBlsPublicKeyListResponse{ + ValidatorWithBlsKeys: valBLSKeys, + }, nil + } + + total := uint64(len(valBLSKeys)) + start := req.Pagination.Offset + if start > total-1 { + return nil, status.Error(codes.InvalidArgument, "pagination offset out of range") + } + var end uint64 + if req.Pagination.Limit == 0 { + end = total + } else { + end = req.Pagination.Limit + start + } + if end > total { + end = total + } + var copiedValBLSKeys []*types.ValidatorWithBlsKey + err = copier.Copy(&copiedValBLSKeys, valBLSKeys[start:end]) + if err != nil { + return nil, err + } + + return &types.QueryBlsPublicKeyListResponse{ + ValidatorWithBlsKeys: copiedValBLSKeys, + }, nil } diff --git a/x/checkpointing/keeper/grpc_query_bls_test.go b/x/checkpointing/keeper/grpc_query_bls_test.go new file mode 100644 index 000000000..2e16fbd63 --- /dev/null +++ b/x/checkpointing/keeper/grpc_query_bls_test.go @@ -0,0 +1,116 @@ +package keeper_test + +import ( + "github.com/babylonchain/babylon/app" + "github.com/babylonchain/babylon/crypto/bls12381" + "github.com/babylonchain/babylon/testutil/datagen" + checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" + "github.com/babylonchain/babylon/x/checkpointing/types" + "github.com/babylonchain/babylon/x/epoching/testepoching" + "github.com/cosmos/cosmos-sdk/baseapp" + 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 +// 1. check the query when there's only a genesis validator +// 2. check the query when there are n+1 validators without pagination +// 3. check the query when there are n+1 validators with pagination +func FuzzQueryBLSKeySet(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + rand.Seed(seed) + // a genesis validator is generated for setup + helper := testepoching.NewHelper(t) + ek := helper.EpochingKeeper + ck := helper.App.CheckpointingKeeper + querier := checkpointingkeeper.Querier{Keeper: ck} + queryHelper := baseapp.NewQueryServerTestHelper(helper.Ctx, helper.App.InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, querier) + queryClient := types.NewQueryClient(queryHelper) + msgServer := checkpointingkeeper.NewMsgServerImpl(ck) + // add BLS pubkey to the genesis validator + valSet := ek.GetValidatorSet(helper.Ctx, 0) + require.Len(t, valSet, 1) + genesisVal := valSet[0] + genesisBLSPubkey := bls12381.GenPrivKey().PubKey() + err := ck.CreateRegistration(helper.Ctx, genesisBLSPubkey, genesisVal.Addr) + require.NoError(t, err) + + // BeginBlock of block 1, and thus entering epoch 1 + ctx := helper.BeginBlock() + epoch := ek.GetEpoch(ctx) + require.Equal(t, uint64(1), epoch.EpochNumber) + + // 1. query public key list when there's only a genesis validator + queryRequest := &types.QueryBlsPublicKeyListRequest{ + EpochNum: 1, + } + res, err := queryClient.BlsPublicKeyList(ctx, queryRequest) + 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()) + + // add n new validators via MsgWrappedCreateValidator + n := rand.Intn(3) + 1 + addrs := app.AddTestAddrs(helper.App, helper.Ctx, n, sdk.NewInt(100000000)) + + wcvMsgs := make([]*types.MsgWrappedCreateValidator, n) + for i := 0; i < n; i++ { + msg, err := buildMsgWrappedCreateValidator(addrs[i]) + require.NoError(t, err) + wcvMsgs[i] = msg + _, err = msgServer.WrappedCreateValidator(ctx, msg) + require.NoError(t, err) + } + + // EndBlock of block 1 + ctx = helper.EndBlock() + + // go to BeginBlock of block 11, and thus entering epoch 2 + for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { + ctx = helper.GenAndApplyEmptyBlock() + } + epoch = ek.GetEpoch(ctx) + require.Equal(t, uint64(2), epoch.EpochNumber) + + // 2. query BLS public keys when there are n+1 validators without pagination + req := types.QueryBlsPublicKeyListRequest{ + EpochNum: 2, + } + resp, err := queryClient.BlsPublicKeyList(ctx, &req) + require.NoError(t, err) + require.Len(t, resp.ValidatorWithBlsKeys, n+1) + 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) + } + + // 3.1 query BLS public keys when there are n+1 validators with limit pagination + req = types.QueryBlsPublicKeyListRequest{ + EpochNum: 2, + Pagination: &query.PageRequest{ + Limit: 1, + }, + } + resp, err = queryClient.BlsPublicKeyList(ctx, &req) + require.NoError(t, err) + require.Len(t, resp.ValidatorWithBlsKeys, 1) + + // 3.2 query BLS public keys when there are n+1 validators with offset pagination + req = types.QueryBlsPublicKeyListRequest{ + EpochNum: 2, + Pagination: &query.PageRequest{ + Offset: 1, + }, + } + resp, err = queryClient.BlsPublicKeyList(ctx, &req) + require.NoError(t, err) + require.Len(t, resp.ValidatorWithBlsKeys, n) + }) +} diff --git a/x/checkpointing/keeper/keeper.go b/x/checkpointing/keeper/keeper.go index 65ea58d9c..7711e39e8 100644 --- a/x/checkpointing/keeper/keeper.go +++ b/x/checkpointing/keeper/keeper.go @@ -337,6 +337,24 @@ func (k Keeper) CreateRegistration(ctx sdk.Context, blsPubKey bls12381.PublicKey return k.RegistrationState(ctx).CreateRegistration(blsPubKey, valAddr) } +// GetBLSPubKeySet returns the set of BLS public keys in the same order of the validator set for a given epoch +func (k Keeper) GetBLSPubKeySet(ctx sdk.Context, epochNumber uint64) ([]*types.ValidatorWithBlsKey, error) { + valset := k.GetValidatorSet(ctx, epochNumber) + valWithblsKeys := make([]*types.ValidatorWithBlsKey, len(valset)) + for i, val := range valset { + pubkey, err := k.GetBlsPubKey(ctx, val.Addr) + if err != nil { + return nil, err + } + valWithblsKeys[i] = &types.ValidatorWithBlsKey{ + ValidatorAddress: val.Addr.String(), + BlsPubKey: pubkey, + } + } + + return valWithblsKeys, nil +} + func (k Keeper) GetBlsPubKey(ctx sdk.Context, address sdk.ValAddress) (bls12381.PublicKey, error) { return k.RegistrationState(ctx).GetBlsPubKey(address) }