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: support multiple chain ids in FinalizedChainsInfo #365

Merged
merged 31 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from 21 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
3,632 changes: 2,219 additions & 1,413 deletions client/docs/swagger-ui/swagger.yaml

Large diffs are not rendered by default.

38 changes: 13 additions & 25 deletions proto/babylon/zoneconcierge/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ service Query {
option (google.api.http).get =
"/babylon/zoneconcierge/v1/headers/{chain_id}/epochs/{epoch_num}";
}
// FinalizedChainInfo queries the BTC-finalised info of a chain, with proofs
rpc FinalizedChainInfo(QueryFinalizedChainInfoRequest)
returns (QueryFinalizedChainInfoResponse) {
// FinalizedChainsInfo queries the BTC-finalised info of chains with given IDs, with proofs
rpc FinalizedChainsInfo(QueryFinalizedChainsInfoRequest)
returns (QueryFinalizedChainsInfoResponse) {
option (google.api.http).get =
"/babylon/zoneconcierge/v1/finalized_chain_info/{chain_id}";
"/babylon/zoneconcierge/v1/finalized_chains_info";
}
// FinalizedChainInfoUntilHeight queries the BTC-finalised info no later than
// the provided CZ height, with proofs
Expand Down Expand Up @@ -140,31 +140,19 @@ message QueryListEpochHeadersResponse {
repeated babylon.zoneconcierge.v1.IndexedHeader headers = 1;
}

// QueryFinalizedChainInfoRequest is request type for the
// Query/FinalizedChainInfo RPC method.
message QueryFinalizedChainInfoRequest {
// chain_id is the ID of the CZ
string chain_id = 1;
// QueryFinalizedChainsInfoRequest is request type for the
// Query/FinalizedChainsInfo RPC method.
message QueryFinalizedChainsInfoRequest {
// chain_ids is the list of ids of CZs
repeated string chain_ids = 1;
// prove indicates whether the querier wants to get proofs of this timestamp
bool prove = 2;
}

// QueryFinalizedChainInfoResponse is response type for the
// Query/FinalizedChainInfo RPC method.
message QueryFinalizedChainInfoResponse {
// finalized_chain_info is the info of the CZ
babylon.zoneconcierge.v1.ChainInfo finalized_chain_info = 1;

// epoch_info is the metadata of the last BTC-finalised epoch
babylon.epoching.v1.Epoch epoch_info = 2;
// raw_checkpoint is the raw checkpoint of this epoch
babylon.checkpointing.v1.RawCheckpoint raw_checkpoint = 3;
// btc_submission_key is position of two BTC txs that include the raw
// checkpoint of this epoch
babylon.btccheckpoint.v1.SubmissionKey btc_submission_key = 4;

// proof is the proof that the chain info is finalized
babylon.zoneconcierge.v1.ProofFinalizedChainInfo proof = 5;
// QueryFinalizedChainsInfoResponse is response type for the
// Query/FinalizedChainsInfo RPC method.
message QueryFinalizedChainsInfoResponse {
repeated babylon.zoneconcierge.v1.FinalizedChainInfo finalized_chains_info = 1;
}

// QueryFinalizedChainInfoUntilHeightRequest is request type for the
Expand Down
21 changes: 21 additions & 0 deletions proto/babylon/zoneconcierge/v1/zoneconcierge.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import "tendermint/types/types.proto";
import "tendermint/crypto/proof.proto";
import "babylon/btccheckpoint/v1/btccheckpoint.proto";
import "babylon/checkpointing/v1/bls_key.proto";
import "babylon/checkpointing/v1/checkpoint.proto";
import "babylon/epoching/v1/epoching.proto";

option go_package = "github.com/babylonchain/babylon/x/zoneconcierge/types";

Expand Down Expand Up @@ -61,6 +63,25 @@ message ChainInfo {
uint64 timestamped_headers_count = 4;
}

// FinalizedChainInfo is the information of a CZ that is BTC-finalised
message FinalizedChainInfo {
// chain_id is the ID of the chain
string chain_id = 1;
// finalized_chain_info is the info of the CZ
babylon.zoneconcierge.v1.ChainInfo finalized_chain_info = 2;

// epoch_info is the metadata of the last BTC-finalised epoch
babylon.epoching.v1.Epoch epoch_info = 3;
// raw_checkpoint is the raw checkpoint of this epoch
babylon.checkpointing.v1.RawCheckpoint raw_checkpoint = 4;
// btc_submission_key is position of two BTC txs that include the raw
// checkpoint of this epoch
babylon.btccheckpoint.v1.SubmissionKey btc_submission_key = 5;

// proof is the proof that the chain info is finalized
babylon.zoneconcierge.v1.ProofFinalizedChainInfo proof = 6;
}

// ProofEpochSealed is the proof that an epoch is sealed by the sealer header,
// i.e., the 2nd header of the next epoch With the access of metadata
// - Metadata of this epoch, which includes the sealer header
Expand Down
62 changes: 32 additions & 30 deletions test/e2e/configurer/chain/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"time"

sdkmath "cosmossdk.io/math"
Expand All @@ -23,12 +24,8 @@ import (
zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types"
)

func (n *NodeConfig) QueryGRPCGateway(path string, parameters ...string) ([]byte, error) {
if len(parameters)%2 != 0 {
return nil, fmt.Errorf("invalid number of parameters, must follow the format of key + value")
}

// add the URL for the given validator ID, and pre-pend to to path.
func (n *NodeConfig) QueryGRPCGateway(path string, queryParams url.Values) ([]byte, error) {
// add the URL for the given validator ID, and pre-pend to path.
hostPort, err := n.containerManager.GetHostPort(n.Name, "1317/tcp")
require.NoError(n.t, err)
endpoint := fmt.Sprintf("http://%s", hostPort)
Expand All @@ -41,12 +38,8 @@ func (n *NodeConfig) QueryGRPCGateway(path string, parameters ...string) ([]byte
return false
}

if len(parameters) > 0 {
q := req.URL.Query()
for i := 0; i < len(parameters); i += 2 {
q.Add(parameters[i], parameters[i+1])
}
req.URL.RawQuery = q.Encode()
if len(queryParams) > 0 {
req.URL.RawQuery = queryParams.Encode()
}

resp, err = http.DefaultClient.Do(req)
Expand All @@ -70,10 +63,10 @@ func (n *NodeConfig) QueryGRPCGateway(path string, parameters ...string) ([]byte
return bz, nil
}

// QueryBalancer returns balances at the address.
// QueryBalances returns balances at the address.
func (n *NodeConfig) QueryBalances(address string) (sdk.Coins, error) {
path := fmt.Sprintf("cosmos/bank/v1beta1/balances/%s", address)
bz, err := n.QueryGRPCGateway(path)
bz, err := n.QueryGRPCGateway(path, url.Values{})
require.NoError(n.t, err)

var balancesResp banktypes.QueryAllBalancesResponse
Expand All @@ -85,7 +78,7 @@ func (n *NodeConfig) QueryBalances(address string) (sdk.Coins, error) {

func (n *NodeConfig) QuerySupplyOf(denom string) (sdkmath.Int, error) {
path := fmt.Sprintf("cosmos/bank/v1beta1/supply/%s", denom)
bz, err := n.QueryGRPCGateway(path)
bz, err := n.QueryGRPCGateway(path, url.Values{})
require.NoError(n.t, err)

var supplyResp banktypes.QuerySupplyOfResponse
Expand Down Expand Up @@ -151,7 +144,7 @@ func (n *NodeConfig) QueryListSnapshots() ([]*tmabcitypes.Snapshot, error) {

func (n *NodeConfig) QueryCheckpointForEpoch(epoch uint64) (*ct.RawCheckpointWithMeta, error) {
path := fmt.Sprintf("babylon/checkpointing/v1/raw_checkpoint/%d", epoch)
bz, err := n.QueryGRPCGateway(path)
bz, err := n.QueryGRPCGateway(path, url.Values{})
require.NoError(n.t, err)

var checkpointingResponse ct.QueryRawCheckpointResponse
Expand All @@ -163,7 +156,7 @@ func (n *NodeConfig) QueryCheckpointForEpoch(epoch uint64) (*ct.RawCheckpointWit
}

func (n *NodeConfig) QueryBtcBaseHeader() (*blc.BTCHeaderInfo, error) {
bz, err := n.QueryGRPCGateway("babylon/btclightclient/v1/baseheader")
bz, err := n.QueryGRPCGateway("babylon/btclightclient/v1/baseheader", url.Values{})
require.NoError(n.t, err)

var blcResponse blc.QueryBaseHeaderResponse
Expand All @@ -175,7 +168,7 @@ func (n *NodeConfig) QueryBtcBaseHeader() (*blc.BTCHeaderInfo, error) {
}

func (n *NodeConfig) QueryTip() (*blc.BTCHeaderInfo, error) {
bz, err := n.QueryGRPCGateway("babylon/btclightclient/v1/tip")
bz, err := n.QueryGRPCGateway("babylon/btclightclient/v1/tip", url.Values{})
require.NoError(n.t, err)

var blcResponse blc.QueryTipResponse
Expand All @@ -186,21 +179,25 @@ func (n *NodeConfig) QueryTip() (*blc.BTCHeaderInfo, error) {
return blcResponse.Header, nil
}

func (n *NodeConfig) QueryFinalizedChainInfo(chainId string) (*zctypes.QueryFinalizedChainInfoResponse, error) {
finalizedPath := fmt.Sprintf("babylon/zoneconcierge/v1/finalized_chain_info/%s", chainId)
bz, err := n.QueryGRPCGateway(finalizedPath)
func (n *NodeConfig) QueryFinalizedChainsInfo(chainIDs []string) (*zctypes.QueryFinalizedChainsInfoResponse, error) {
queryParams := url.Values{}
for _, chainId := range chainIDs {
queryParams.Add("chain_ids", chainId)
}

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

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

return &finalizedResponse, nil
return &resp, nil
}

func (n *NodeConfig) QueryCheckpointChains() (*[]string, error) {
bz, err := n.QueryGRPCGateway("babylon/zoneconcierge/v1/chains")
bz, err := n.QueryGRPCGateway("babylon/zoneconcierge/v1/chains", url.Values{})
require.NoError(n.t, err)
var chainsResponse zctypes.QueryChainListResponse
if err := util.Cdc.UnmarshalJSON(bz, &chainsResponse); err != nil {
Expand All @@ -209,8 +206,13 @@ func (n *NodeConfig) QueryCheckpointChains() (*[]string, error) {
return &chainsResponse.ChainIds, nil
}

func (n *NodeConfig) QueryCheckpointChainsInfo(chainID string) ([]*zctypes.ChainInfo, error) {
bz, err := n.QueryGRPCGateway("/babylon/zoneconcierge/v1/chains_info", "chain_ids", chainID)
func (n *NodeConfig) QueryCheckpointChainsInfo(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 {
Expand All @@ -220,7 +222,7 @@ func (n *NodeConfig) QueryCheckpointChainsInfo(chainID string) ([]*zctypes.Chain
}

func (n *NodeConfig) QueryCurrentEpoch() (uint64, error) {
bz, err := n.QueryGRPCGateway("/babylon/epoching/v1/current_epoch")
bz, err := n.QueryGRPCGateway("/babylon/epoching/v1/current_epoch", url.Values{})
require.NoError(n.t, err)
var epochResponse etypes.QueryCurrentEpochResponse
if err := util.Cdc.UnmarshalJSON(bz, &epochResponse); err != nil {
Expand All @@ -231,7 +233,7 @@ func (n *NodeConfig) QueryCurrentEpoch() (uint64, error) {

func (n *NodeConfig) QueryLightClientHeightEpochEnd(epoch uint64) (uint64, error) {
monitorPath := fmt.Sprintf("/babylon/monitor/v1/epochs/%d", epoch)
bz, err := n.QueryGRPCGateway(monitorPath)
bz, err := n.QueryGRPCGateway(monitorPath, url.Values{})
require.NoError(n.t, err)
var mResponse mtypes.QueryEndedEpochBtcHeightResponse
if err := util.Cdc.UnmarshalJSON(bz, &mResponse); err != nil {
Expand All @@ -242,7 +244,7 @@ func (n *NodeConfig) QueryLightClientHeightEpochEnd(epoch uint64) (uint64, error

func (n *NodeConfig) QueryLightClientHeightCheckpointReported(ckptHash []byte) (uint64, error) {
monitorPath := fmt.Sprintf("/babylon/monitor/v1/checkpoints/%x", ckptHash)
bz, err := n.QueryGRPCGateway(monitorPath)
bz, err := n.QueryGRPCGateway(monitorPath, url.Values{})
require.NoError(n.t, err)
var mResponse mtypes.QueryReportedCheckpointBtcHeightResponse
if err := util.Cdc.UnmarshalJSON(bz, &mResponse); err != nil {
Expand Down
10 changes: 6 additions & 4 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (s *IntegrationTestSuite) TestIbcCheckpointing() {
s.NoError(err)

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

Expand All @@ -45,11 +45,13 @@ func (s *IntegrationTestSuite) TestIbcCheckpointing() {
}

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

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

currEpoch, err := nonValidatorNode.QueryCurrentEpoch()
s.NoError(err)
Expand Down
18 changes: 18 additions & 0 deletions types/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"fmt"
"reflect"
)

Expand All @@ -11,3 +12,20 @@ func Reverse(s interface{}) {
swap(i, j)
}
}

func CheckForDuplicatesAndEmptyStrings(input []string) error {
encountered := map[string]bool{}
for _, str := range input {
if len(str) == 0 {
return fmt.Errorf("string cannot be empty")
}

if encountered[str] {
return fmt.Errorf("duplicate string: %s", str)
}

encountered[str] = true
}

return nil
}
23 changes: 23 additions & 0 deletions x/zoneconcierge/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command {
}

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

Expand All @@ -47,3 +48,25 @@ func CmdChainsInfo() *cobra.Command {
flags.AddQueryFlagsToCmd(cmd)
return cmd
}

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",
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)
req := types.QueryFinalizedChainsInfoRequest{ChainIds: args}
resp, err := queryClient.FinalizedChainsInfo(cmd.Context(), &req)
if err != nil {
return err
}

return clientCtx.PrintProto(resp)
},
}

flags.AddQueryFlagsToCmd(cmd)
return cmd
}
33 changes: 31 additions & 2 deletions x/zoneconcierge/keeper/epoch_chain_info_indexer.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package keeper

import (
bbn "github.com/babylonchain/babylon/types"
"github.com/babylonchain/babylon/x/zoneconcierge/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"

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

// GetEpochChainInfo gets the latest chain info of a given epoch for a given chain ID
Expand All @@ -20,6 +21,23 @@ func (k Keeper) GetEpochChainInfo(ctx sdk.Context, chainID string, epochNumber u
return &chainInfo, nil
}

// HasFinalizedChainInfo checks if chain info exists for a given chain ID in the last finalised epoch
func (k Keeper) HasFinalizedChainInfo(ctx sdk.Context, chainID string) (bool, error) {
Copy link
Member

Choose a reason for hiding this comment

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

This method is not that performant and does more than it should. I suggest we remove this and use something similar to this method to check for existence given a chain ID and epoch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure how we will only check for existence as we have to perform this check inside that function which will involve a storage read.

// find the last finalised epoch
finalizedEpoch, err := k.GetFinalizedEpoch(ctx)
if err != nil {
return false, err
}

// find the chain info of this finalised epoch
_, _, err = k.lastFinalizedChainInfoHelper(ctx, chainID, finalizedEpoch)
if err != nil {
return false, nil
}

return true, nil
}

// GetLastFinalizedChainInfo gets the last finalised chain info recorded for a given chain ID
// and the earliest epoch that snapshots this chain info
func (k Keeper) GetLastFinalizedChainInfo(ctx sdk.Context, chainID string) (uint64, *types.ChainInfo, error) {
Expand All @@ -29,6 +47,17 @@ func (k Keeper) GetLastFinalizedChainInfo(ctx sdk.Context, chainID string) (uint
return 0, nil, err
}

// find the chain info of this finalised epoch
finalizedEpoch, chainInfo, err := k.lastFinalizedChainInfoHelper(ctx, chainID, finalizedEpoch)
if err != nil {
return finalizedEpoch, nil, err
}

return finalizedEpoch, chainInfo, nil
}

// lastFinalizedChainInfoHelper is a helper function to find the last finalised chain info
func (k Keeper) lastFinalizedChainInfoHelper(ctx sdk.Context, chainID string, finalizedEpoch uint64) (uint64, *types.ChainInfo, error) {
// find the chain info of this epoch
chainInfo, err := k.GetEpochChainInfo(ctx, chainID, finalizedEpoch)
if err != nil {
Expand Down
Loading