From eaccc2959fed9d21569e9e701da7c331c676335d Mon Sep 17 00:00:00 2001 From: Le Biller Guillaume Date: Wed, 26 Oct 2022 11:18:51 -0400 Subject: [PATCH] feat: add new clients-expiration query returning time before expiration --- cmd/query.go | 52 ++++++++++++++++++++++++-- relayer/query.go | 34 +++++++++++++++++ relayer/query_test.go | 87 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 relayer/query_test.go diff --git a/cmd/query.go b/cmd/query.go index 8e811b1d9..543547a11 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -3,11 +3,10 @@ package cmd import ( "encoding/json" "fmt" - "strconv" - "strings" - "github.com/cosmos/relayer/v2/relayer" "github.com/spf13/cobra" + "strconv" + "strings" ) // queryCmd represents the chain command @@ -31,6 +30,7 @@ func queryCmd(a *appState) *cobra.Command { lineBreakCommand(), queryClientCmd(a), queryClientsCmd(a), + queryClientsExpiration(a), queryConnection(a), queryConnections(a), queryConnectionsUsingClient(a), @@ -870,3 +870,49 @@ $ %s query unrelayed-acks demo-path channel-0`, return cmd } + +func queryClientsExpiration(a *appState) *cobra.Command { + cmd := &cobra.Command{ + Use: "clients-expiration path", + Short: "query for light clients expiration date", + Args: withUsage(cobra.ExactArgs(1)), + Example: strings.TrimSpace(fmt.Sprintf(` +$ %s query clients-expiration demo-path`, + appName, + )), + RunE: func(cmd *cobra.Command, args []string) error { + path, err := a.Config.Paths.Get(args[0]) + if err != nil { + return err + } + src, dst := path.Src.ChainID, path.Dst.ChainID + c, err := a.Config.Chains.Gets(src, dst) + if err != nil { + return err + } + + if err = c[src].SetPath(path.Src); err != nil { + return err + } + if err = c[dst].SetPath(path.Dst); err != nil { + return err + } + + srcExpiration, err := relayer.QueryClientExpiration(cmd.Context(), c[src], c[dst]) + if err != nil { + return err + } + dstExpiration, err := relayer.QueryClientExpiration(cmd.Context(), c[dst], c[src]) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), relayer.SPrintClientExpiration(c[src], srcExpiration)) + fmt.Fprintf(cmd.OutOrStdout(), relayer.SPrintClientExpiration(c[dst], dstExpiration)) + + return nil + }, + } + + return cmd +} diff --git a/relayer/query.go b/relayer/query.go index e9f67372a..e5befc3ea 100644 --- a/relayer/query.go +++ b/relayer/query.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "time" "github.com/avast/retry-go/v4" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -301,3 +302,36 @@ func QueryBalance(ctx context.Context, chain *Chain, address string, showDenoms } return out, nil } + +func QueryClientExpiration(ctx context.Context, src, dst *Chain) (time.Time, error) { + latestHeight, err := src.ChainProvider.QueryLatestHeight(ctx) + if err != nil { + return time.Time{}, err + } + + clientState, err := src.QueryTMClientState(ctx, latestHeight) + if err != nil { + return time.Time{}, err + } + + trustingPeriod := clientState.TrustingPeriod + clientTime, err := dst.ChainProvider.BlockTime(ctx, int64(clientState.GetLatestHeight().GetRevisionHeight())) + if err != nil { + return time.Time{}, err + } + + return clientTime.Add(trustingPeriod), nil +} + +func SPrintClientExpiration(chain *Chain, expiration time.Time) string { + now := time.Now() + remainingTime := expiration.Sub(now) + expirationFormatted := expiration.Format(time.RFC822) + + if remainingTime < 0 { + return fmt.Sprintf("client %s (%s) is already expired (%s)\n", + chain.ClientID(), chain.ChainID(), expirationFormatted) + } + return fmt.Sprintf("client %s (%s) expires in %s (%s)\n", + chain.ClientID(), chain.ChainID(), remainingTime.Round(time.Second), expirationFormatted) +} diff --git a/relayer/query_test.go b/relayer/query_test.go new file mode 100644 index 000000000..fb67c60d1 --- /dev/null +++ b/relayer/query_test.go @@ -0,0 +1,87 @@ +package relayer + +import ( + "github.com/cosmos/relayer/v2/relayer/chains/cosmos" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestSPrintClientExpiration_PrintChainId(t *testing.T) { + previousTime := time.Now().Add(10 * time.Hour) + + chain := mockChain("expected-chain-id", "test-client-id") + expiration := SPrintClientExpiration(chain, previousTime) + + require.Contains(t, expiration, "expected-chain-id") +} + +func TestSPrintClientExpiration_PrintClientId(t *testing.T) { + previousTime := time.Now().Add(10 * time.Hour) + + chain := mockChain("test-chain-id", "expected-client-id") + expiration := SPrintClientExpiration(chain, previousTime) + + require.Contains(t, expiration, "expected-client-id") +} + +func TestSPrintClientExpiration_PrintIsAlreadyExpired_WhenTimeIsInPast(t *testing.T) { + previousTime := time.Now().Add(-10 * time.Hour) + + chain := mockChain("test-chain-id", "test-client-id") + expiration := SPrintClientExpiration(chain, previousTime) + + require.Contains(t, expiration, "is already expired") +} + +func TestSPrintClientExpiration_PrintRFC822FormattedTime_WhenTimeIsInPast(t *testing.T) { + pastTime := time.Now().Add(-10 * time.Hour) + + chain := mockChain("test-chain-id", "test-client-id") + expiration := SPrintClientExpiration(chain, pastTime) + + require.Contains(t, expiration, pastTime.Format(time.RFC822)) +} + +func TestSPrintClientExpiration_PrintExpiresIn_WhenTimeIsInFuture(t *testing.T) { + previousTime := time.Now().Add(10 * time.Hour) + + chain := mockChain("test-chain-id", "test-client-id") + expiration := SPrintClientExpiration(chain, previousTime) + + require.Contains(t, expiration, "expires in") +} + +func TestSPrintClientExpiration_PrintRFC822FormattedTime_WhenTimeIsInFuture(t *testing.T) { + futureTime := time.Now().Add(10 * time.Hour) + + chain := mockChain("test-chain-id", "test-client-id") + expiration := SPrintClientExpiration(chain, futureTime) + + require.Contains(t, expiration, futureTime.Format(time.RFC822)) +} + +func TestSPrintClientExpiration_PrintRemainingTime_WhenTimeIsInFuture(t *testing.T) { + futureTime := time.Now().Add(10 * time.Hour) + + chain := mockChain("test-chain-id", "test-client-id") + expiration := SPrintClientExpiration(chain, futureTime) + + require.Contains(t, expiration, "10h0m0s") +} + +func mockChain(chainId string, clientId string) *Chain { + return &Chain{ + Chainid: chainId, + ChainProvider: &cosmos.CosmosProvider{ + PCfg: cosmos.CosmosProviderConfig{ + ChainID: chainId, + }, + }, + PathEnd: &PathEnd{ + ChainID: chainId, + ClientID: clientId, + }, + } +}