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

auto update clients to prevent expiry #412

Merged
merged 10 commits into from
Feb 12, 2021
14 changes: 14 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ var (
flagTimeoutHeightOffset = "timeout-height-offset"
flagTimeoutTimeOffset = "timeout-time-offset"
flagMaxRetries = "max-retries"
flagTimeInterval = "time-interval"
flagThresholdTime = "time-threshold"
)

func ibcDenomFlags(cmd *cobra.Command) *cobra.Command {
Expand Down Expand Up @@ -259,3 +261,15 @@ func retryFlag(cmd *cobra.Command) *cobra.Command {
}
return cmd
}

func updateTimeFlags(cmd *cobra.Command) *cobra.Command {
cmd.Flags().DurationP(flagTimeInterval, "i", 60*time.Second, "time interval to run update client process")
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
cmd.Flags().Float64P(flagThresholdTime, "n", 10, "update client n minutes before to expiry time")
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
if err := viper.BindPFlag(flagTimeInterval, cmd.Flags().Lookup(flagTimeInterval)); err != nil {
panic(err)
}
if err := viper.BindPFlag(flagThresholdTime, cmd.Flags().Lookup(flagThresholdTime)); err != nil {
panic(err)
}
return cmd
}
116 changes: 115 additions & 1 deletion cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ import (
"os/signal"
"strings"
"syscall"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
clientutils "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/client/utils"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/07-tendermint/types"
"github.com/cosmos/relayer/relayer"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"
)

// startCmd represents the start command
Expand Down Expand Up @@ -70,11 +77,23 @@ $ %s start demo-path2 --max-tx-size 10`, appName, appName)),
return err
}

timeInterval := viper.GetDuration(flagTimeInterval)

go func() {
for {
// run for every 60 seconds
if err := UpdateClientsFromChains(c[src], c[dst]); err != nil {
fmt.Printf("Error in updating clients....%v", err)
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
}
time.Sleep(timeInterval)
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
}
}()

trapSignal(done)
return nil
},
}
return strategyFlag(cmd)
return strategyFlag(updateTimeFlags(cmd))
}

// trap signal waits for a SIGINT or SIGTERM and then sends down the done channel
Expand All @@ -91,3 +110,98 @@ func trapSignal(done func()) {
// call the cleanup func
done()
}

// UpdateClientsFromChains takes src, dst chains and update clients based on expiry time
func UpdateClientsFromChains(src, dst *relayer.Chain) (err error) {
if src.PathEnd.ClientID == "" || dst.PathEnd.ClientID == "" {
return nil
}
eg := new(errgroup.Group)
eg.Go(func() error {
err = GetClientAndUpdate(src, dst)
return err
})
eg.Go(func() error {
err = GetClientAndUpdate(dst, src)
return err
})
err = eg.Wait()
return err
}

// GetClientAndUpdate update clients to prevent expiry
func GetClientAndUpdate(src, dst *relayer.Chain) error {
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
height, err := src.QueryLatestHeight()
if err != nil {
return err
}

clientStateRes, err := src.QueryClientState(height)
if err != nil {
return err
}

// unpack any into ibc tendermint client state
clientStateExported, err := clienttypes.UnpackClientState(clientStateRes.ClientState)
if err != nil {
return err
}

// cast from interface to concrete type
clientState, ok := clientStateExported.(*ibctmtypes.ClientState)
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return fmt.Errorf("error when casting exported clientstate on chain: %s", src.PathEnd.ClientID)
}

// query the latest consensus state of the potential matching client
consensusStateResp, err := clientutils.QueryConsensusStateABCI(src.CLIContext(0),
src.PathEnd.ClientID, clientState.GetLatestHeight())
if err != nil {
return err
}

exportedConsState, err := clienttypes.UnpackConsensusState(consensusStateResp.ConsensusState)
if err != nil {
return err
}

consensusState, ok := exportedConsState.(*ibctmtypes.ConsensusState)
if !ok {
return fmt.Errorf("error: consensus state is not tendermint type on chain %s", src.PathEnd.ChainID)
}

expirationTime := consensusState.Timestamp.Add(clientState.TrustingPeriod)

checkExpiryTime := viper.GetFloat64(flagThresholdTime)

// Checking expiration time is below 10minutes to current time
if expirationTime.After(time.Now()) && time.Until(expirationTime).Minutes() <= checkExpiryTime {
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
sh, err := relayer.NewSyncHeaders(src, dst)
if err != nil {
return err
}

dstUH, err := sh.GetTrustedHeader(dst, src)
if err != nil {
return err
}

msgs := []sdk.Msg{
src.UpdateClient(dstUH),
}

_, _, err = src.SendMsgs(msgs)
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
src.Log(fmt.Sprintf("★ Client updated: [%s]client(%s) {%d}->{%d}",
src.ChainID,
src.PathEnd.ClientID,
relayer.MustGetHeight(dstUH.TrustedHeight),
dstUH.Header.Height,
))
} else if !expirationTime.After(time.Now()) {
fmt.Printf("Client is already expired on chain: %s", src.ChainID)
}
return nil
}