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 support for multiple chain ids in EpochChainsInfo API #369

Merged
merged 26 commits into from
May 3, 2023
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
2,145 changes: 1,090 additions & 1,055 deletions client/docs/swagger-ui/swagger.yaml

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions proto/babylon/zoneconcierge/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ service Query {
rpc ChainList(QueryChainListRequest) returns (QueryChainListResponse) {
option (google.api.http).get = "/babylon/zoneconcierge/v1/chains";
}
// ChainInfo queries the latest info of a chain in Babylon's view
// ChainsInfo queries the latest info for a given list of chains in Babylon's view
rpc ChainsInfo(QueryChainsInfoRequest) returns (QueryChainsInfoResponse) {
option (google.api.http).get =
"/babylon/zoneconcierge/v1/chains_info";
}
// EpochChainInfo queries the latest info of a chain in a given epoch of
// Babylon's view
rpc EpochChainInfo(QueryEpochChainInfoRequest)
returns (QueryEpochChainInfoResponse) {
// EpochChainsInfo queries the latest info for a list of chains
// in a given epoch in Babylon's view
rpc EpochChainsInfo(QueryEpochChainsInfoRequest)
returns (QueryEpochChainsInfoResponse) {
option (google.api.http).get =
"/babylon/zoneconcierge/v1/chain_info/{chain_id}/epochs/{epoch_num}";
"/babylon/zoneconcierge/v1/epoch_chains_info";
}
// ListHeaders queries the headers of a chain in Babylon's view, with
// pagination support
Expand Down Expand Up @@ -96,18 +96,18 @@ message QueryChainsInfoResponse {
repeated babylon.zoneconcierge.v1.ChainInfo chains_info = 1;
}

// QueryEpochChainInfoRequest is request type for the Query/EpochChainInfo RPC
// QueryEpochChainsInfoRequest is request type for the Query/EpochChainsInfo RPC
// method.
message QueryEpochChainInfoRequest {
message QueryEpochChainsInfoRequest {
uint64 epoch_num = 1;
string chain_id = 2;
repeated string chain_ids = 2;
}

// QueryEpochChainInfoResponse is response type for the Query/EpochChainInfo RPC
// QueryEpochChainsInfoResponse is response type for the Query/EpochChainsInfo RPC
// method.
message QueryEpochChainInfoResponse {
message QueryEpochChainsInfoResponse {
// chain_info is the info of the CZ
babylon.zoneconcierge.v1.ChainInfo chain_info = 1;
repeated babylon.zoneconcierge.v1.ChainInfo chains_info = 1;
}

// QueryListHeadersRequest is request type for the Query/ListHeaders RPC method.
Expand Down
32 changes: 25 additions & 7 deletions test/e2e/configurer/chain/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (n *NodeConfig) QueryTip() (*blc.BTCHeaderInfo, error) {
return blcResponse.Header, nil
}

func (n *NodeConfig) QueryFinalizedChainsInfo(chainIDs []string) (*zctypes.QueryFinalizedChainsInfoResponse, error) {
func (n *NodeConfig) QueryFinalizedChainsInfo(chainIDs []string) ([]*zctypes.FinalizedChainInfo, error) {
queryParams := url.Values{}
for _, chainId := range chainIDs {
queryParams.Add("chain_ids", chainId)
Expand All @@ -193,10 +193,28 @@ func (n *NodeConfig) QueryFinalizedChainsInfo(chainIDs []string) (*zctypes.Query
return nil, err
}

return &resp, nil
return resp.FinalizedChainsInfo, nil
}

func (n *NodeConfig) QueryCheckpointChains() (*[]string, error) {
func (n *NodeConfig) QueryEpochChainsInfo(epochNum uint64, chainIDs []string) ([]*zctypes.ChainInfo, error) {
queryParams := url.Values{}
for _, chainId := range chainIDs {
queryParams.Add("epoch_num", fmt.Sprintf("%d", epochNum))
queryParams.Add("chain_ids", chainId)
}

bz, err := n.QueryGRPCGateway("babylon/zoneconcierge/v1/epoch_chains_info", queryParams)
require.NoError(n.t, err)

var resp zctypes.QueryEpochChainsInfoResponse
if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil {
return nil, err
}

return resp.ChainsInfo, nil
}

func (n *NodeConfig) QueryChains() (*[]string, error) {
bz, err := n.QueryGRPCGateway("babylon/zoneconcierge/v1/chains", url.Values{})
require.NoError(n.t, err)
var chainsResponse zctypes.QueryChainListResponse
Expand All @@ -206,19 +224,19 @@ func (n *NodeConfig) QueryCheckpointChains() (*[]string, error) {
return &chainsResponse.ChainIds, nil
}

func (n *NodeConfig) QueryCheckpointChainsInfo(chainIDs []string) ([]*zctypes.ChainInfo, error) {
func (n *NodeConfig) QueryChainsInfo(chainIDs []string) ([]*zctypes.ChainInfo, error) {
queryParams := url.Values{}
for _, chainId := range chainIDs {
queryParams.Add("chain_ids", chainId)
}

bz, err := n.QueryGRPCGateway("/babylon/zoneconcierge/v1/chains_info", queryParams)
require.NoError(n.t, err)
var infoResponse zctypes.QueryChainsInfoResponse
if err := util.Cdc.UnmarshalJSON(bz, &infoResponse); err != nil {
var resp zctypes.QueryChainsInfoResponse
if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil {
return nil, err
}
return infoResponse.ChainsInfo, nil
return resp.ChainsInfo, nil
}

func (n *NodeConfig) QueryCurrentEpoch() (uint64, error) {
Expand Down
29 changes: 18 additions & 11 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,35 @@ func (s *IntegrationTestSuite) TestIbcCheckpointing() {
s.NoError(err)

// Query checkpoint chain info for opposing chain
chainInfo, err := nonValidatorNode.QueryCheckpointChainsInfo([]string{initialization.ChainBID})
chainsInfo, err := nonValidatorNode.QueryChainsInfo([]string{initialization.ChainBID})
s.NoError(err)
s.Equal(chainInfo[0].ChainId, initialization.ChainBID)
s.Equal(chainsInfo[0].ChainId, initialization.ChainBID)

// Finalize epoch 1,2,3 , as first headers of opposing chain are in epoch 3
nonValidatorNode.FinalizeSealedEpochs(1, 3)
var (
startEpochNum uint64 = 1
endEpochNum uint64 = 3
)

epoch3, err := nonValidatorNode.QueryCheckpointForEpoch(3)
nonValidatorNode.FinalizeSealedEpochs(startEpochNum, endEpochNum)

endEpoch, err := nonValidatorNode.QueryCheckpointForEpoch(endEpochNum)
s.NoError(err)
s.Equal(endEpoch.Status, ct.Finalized)

if epoch3.Status != ct.Finalized {
s.FailNow("Epoch 2 should be finalized")
}
// Check we have epoch info for opposing chain and some basic assertions
epochChainsInfo, err := nonValidatorNode.QueryEpochChainsInfo(endEpochNum, []string{initialization.ChainBID})
s.NoError(err)
s.Equal(epochChainsInfo[0].ChainId, initialization.ChainBID)
s.Equal(epochChainsInfo[0].LatestHeader.BabylonEpoch, endEpochNum)

// Check we have finalized epoch info for opposing chain and some basic assertions
finalizedResp, err := nonValidatorNode.QueryFinalizedChainsInfo([]string{initialization.ChainBID})
finalizedChainsInfo, err := nonValidatorNode.QueryFinalizedChainsInfo([]string{initialization.ChainBID})
s.NoError(err)

finalizedInfo := finalizedResp.FinalizedChainsInfo[0]
// TODO Add more assertion here. Maybe check proofs ?
s.Equal(finalizedInfo.FinalizedChainInfo.ChainId, initialization.ChainBID)
s.Equal(finalizedInfo.EpochInfo.EpochNumber, uint64(3))
s.Equal(finalizedChainsInfo[0].FinalizedChainInfo.ChainId, initialization.ChainBID)
s.Equal(finalizedChainsInfo[0].EpochInfo.EpochNumber, endEpochNum)

currEpoch, err := nonValidatorNode.QueryCurrentEpoch()
s.NoError(err)
Expand Down
33 changes: 31 additions & 2 deletions x/zoneconcierge/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"strconv"

"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/spf13/cobra"
Expand All @@ -24,13 +25,14 @@ func GetQueryCmd(queryRoute string) *cobra.Command {

cmd.AddCommand(CmdChainsInfo())
cmd.AddCommand(CmdFinalizedChainsInfo())
cmd.AddCommand(CmdEpochChainsInfoInfo())
return cmd
}

func CmdChainsInfo() *cobra.Command {
cmd := &cobra.Command{
Use: "chains-info <chain-ids>",
Short: "retrieves the latest info for a list of chains with given IDs",
Short: "retrieve the latest info for a given list of chains",
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
Expand All @@ -52,7 +54,7 @@ func CmdChainsInfo() *cobra.Command {
func CmdFinalizedChainsInfo() *cobra.Command {
cmd := &cobra.Command{
Use: "finalized-chains-info <chain-ids>",
Short: "retrieves the finalized info for a list of chains with given IDs",
Short: "retrieve the finalized info for a given list of chains",
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
Expand All @@ -70,3 +72,30 @@ func CmdFinalizedChainsInfo() *cobra.Command {
flags.AddQueryFlagsToCmd(cmd)
return cmd
}

func CmdEpochChainsInfoInfo() *cobra.Command {
cmd := &cobra.Command{
Use: "epoch-chains-info <epoch-num> <chain-ids>",
Short: "retrieve the latest info for a list of chains in a given epoch",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

vitsalis marked this conversation as resolved.
Show resolved Hide resolved
epoch, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return err
}
req := types.QueryEpochChainsInfoRequest{EpochNum: epoch, ChainIds: args[1:]}
resp, err := queryClient.EpochChainsInfo(cmd.Context(), &req)
if err != nil {
return err
}

return clientCtx.PrintProto(resp)
},
}

flags.AddQueryFlagsToCmd(cmd)
return cmd
}
11 changes: 6 additions & 5 deletions x/zoneconcierge/keeper/chain_info_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"fmt"

errorsmod "cosmossdk.io/errors"
"github.com/babylonchain/babylon/x/zoneconcierge/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/babylonchain/babylon/x/zoneconcierge/types"
)

func (k Keeper) setChainInfo(ctx sdk.Context, chainInfo *types.ChainInfo) {
Expand Down Expand Up @@ -48,11 +49,11 @@ func (k Keeper) HasChainInfo(ctx sdk.Context, chainID string) bool {
// Since IBC does not provide API that allows to initialise chain info right before creating an IBC connection,
// we can only check its existence every time, and return an empty one if it's not initialised yet.
func (k Keeper) GetChainInfo(ctx sdk.Context, chainID string) (*types.ChainInfo, error) {
store := k.chainInfoStore(ctx)

if !store.Has([]byte(chainID)) {
return nil, types.ErrEpochChainInfoNotFound
if !k.HasChainInfo(ctx, chainID) {
return nil, types.ErrChainInfoNotFound
}

store := k.chainInfoStore(ctx)
chainInfoBytes := store.Get([]byte(chainID))
var chainInfo types.ChainInfo
k.cdc.MustUnmarshal(chainInfoBytes, &chainInfo)
Expand Down
56 changes: 44 additions & 12 deletions x/zoneconcierge/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (k Keeper) ChainList(c context.Context, req *types.QueryChainListRequest) (
return resp, nil
}

// ChainsInfo returns the latest info for a list of chains with given IDs
// ChainsInfo returns the latest info for a given list of chains
func (k Keeper) ChainsInfo(c context.Context, req *types.QueryChainsInfoRequest) (*types.QueryChainsInfoResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
Expand Down Expand Up @@ -103,24 +103,51 @@ func (k Keeper) Header(c context.Context, req *types.QueryHeaderRequest) (*types
return resp, nil
}

// EpochChainInfo returns the info of a chain with given ID in a given epoch
func (k Keeper) EpochChainInfo(c context.Context, req *types.QueryEpochChainInfoRequest) (*types.QueryEpochChainInfoResponse, error) {
// EpochChainsInfo returns the latest info for list of chains in a given epoch
func (k Keeper) EpochChainsInfo(c context.Context, req *types.QueryEpochChainsInfoRequest) (*types.QueryEpochChainsInfoResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if len(req.ChainId) == 0 {
return nil, status.Error(codes.InvalidArgument, "chain ID cannot be empty")
// return if no chain IDs are provided
if len(req.ChainIds) == 0 {
return nil, status.Error(codes.InvalidArgument, "chain IDs cannot be empty")
}

// return if chain IDs exceed the limit
if len(req.ChainIds) > maxQueryChainsInfoLimit {
return nil, status.Errorf(codes.InvalidArgument, "cannot query more than %d chains", maxQueryChainsInfoLimit)
}

// return if chain IDs contain duplicates or empty strings
if err := bbntypes.CheckForDuplicatesAndEmptyStrings(req.ChainIds); err != nil {
return nil, status.Error(codes.InvalidArgument, types.ErrInvalidChainIDs.Wrap(err.Error()).Error())
}

ctx := sdk.UnwrapSDKContext(c)
var chainsInfo []*types.ChainInfo
for _, chainID := range req.ChainIds {
// check if chain ID is valid
if !k.HasChainInfo(ctx, chainID) {
return nil, status.Error(codes.InvalidArgument, types.ErrChainInfoNotFound.Wrapf("chain ID %s", chainID).Error())
}

// find the chain info of the given epoch
chainInfo, err := k.GetEpochChainInfo(ctx, req.ChainId, req.EpochNum)
if err != nil {
return nil, err
// if the chain info is not found in the given epoch, return with empty fields
if !k.EpochChainInfoExists(ctx, chainID, req.EpochNum) {
chainsInfo = append(chainsInfo, &types.ChainInfo{ChainId: chainID})
continue
}

// find the chain info of the given epoch
chainInfo, err := k.GetEpochChainInfo(ctx, chainID, req.EpochNum)
if err != nil {
return nil, err
}

chainsInfo = append(chainsInfo, chainInfo)
}
resp := &types.QueryEpochChainInfoResponse{ChainInfo: chainInfo}

resp := &types.QueryEpochChainsInfoResponse{ChainsInfo: chainsInfo}
return resp, nil
}

Expand Down Expand Up @@ -179,7 +206,7 @@ func (k Keeper) ListEpochHeaders(c context.Context, req *types.QueryListEpochHea
return resp, nil
}

// FinalizedChainsInfo returns the finalized info of chains with given IDs
// FinalizedChainsInfo returns the finalized info for a given list of chains
func (k Keeper) FinalizedChainsInfo(c context.Context, req *types.QueryFinalizedChainsInfoRequest) (*types.QueryFinalizedChainsInfoResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
Expand Down Expand Up @@ -210,9 +237,14 @@ func (k Keeper) FinalizedChainsInfo(c context.Context, req *types.QueryFinalized
}

for _, chainID := range req.ChainIds {
// check if chain ID is valid
if !k.HasChainInfo(ctx, chainID) {
return nil, status.Error(codes.InvalidArgument, types.ErrChainInfoNotFound.Wrapf("chain ID %s", chainID).Error())
}

data := &types.FinalizedChainInfo{ChainId: chainID}

// if the chain info is not found in the last finalised epoch, return the chain info with empty fields
// if the chain info is not found in the last finalised epoch, return with empty fields
if !k.EpochChainInfoExists(ctx, chainID, lastFinalizedEpoch) {
resp.FinalizedChainsInfo = append(resp.FinalizedChainsInfo, data)
continue
Expand Down
Loading