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

feat: Add bls pubkey set api #232

Merged
merged 1 commit into from
Dec 5, 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
43 changes: 42 additions & 1 deletion x/checkpointing/keeper/grpc_query_bls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

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

I remember Cosmos SDK provides functionalities for getting the paginated response given a pagination request, e.g., something like https://github.com/babylonchain/babylon/blob/dev/x/epoching/keeper/grpc_query.go#L67-L92

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, thanks for the pointer. Thing is that the set is obtained from epoching store and sorted. I'm not sure how to use this functionality in our case

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])
Copy link
Member

Choose a reason for hiding this comment

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

Why do we want to copy it rather than using the slice directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want a deep copy since we don't want the passed slice to be affected by the original one. But, in this specific case, we do not need to copy it

if err != nil {
return nil, err
}

return &types.QueryBlsPublicKeyListResponse{
ValidatorWithBlsKeys: copiedValBLSKeys,
}, nil
}
116 changes: 116 additions & 0 deletions x/checkpointing/keeper/grpc_query_bls_test.go
Original file line number Diff line number Diff line change
@@ -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)
Comment on lines +28 to +34
Copy link
Member

Choose a reason for hiding this comment

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

It seems that L28-34 will be used quite frequently. Maybe they can be a part of the response of testepoching.NewHelper?

Copy link
Contributor Author

@gitferry gitferry Dec 5, 2022

Choose a reason for hiding this comment

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

Would that be strange that we have checkpointing stuff inside an epoching test helper? I have thought about having a helper specifically for checkpointing, but I think that would be a little bit heavy for now

// 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,
},
Comment on lines +108 to +110
Copy link
Member

Choose a reason for hiding this comment

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

Great, you even have a test for the pagination 👍

}
resp, err = queryClient.BlsPublicKeyList(ctx, &req)
require.NoError(t, err)
require.Len(t, resp.ValidatorWithBlsKeys, n)
})
}
18 changes: 18 additions & 0 deletions x/checkpointing/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down