From aa2fff32a5f40976ca8746e6c71470c2553fc308 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 30 May 2024 10:10:13 +0900 Subject: [PATCH 01/47] refactor connection/channel handshake implementations Signed-off-by: Masanori Yoshida --- core/channel.go | 50 ++++++++++++++++++++-------------------------- core/connection.go | 49 +++++++++++++++++++-------------------------- core/relayMsgs.go | 9 +-------- 3 files changed, 44 insertions(+), 64 deletions(-) diff --git a/core/channel.go b/core/channel.go index cfbec6d3..a4721546 100644 --- a/core/channel.go +++ b/core/channel.go @@ -2,6 +2,7 @@ package core import ( "context" + "errors" "fmt" "log/slog" "time" @@ -41,39 +42,32 @@ func CreateChannel(pathName string, src, dst *ProvableChain, to time.Duration) e } chanSteps.Send(src, dst) + if chanSteps.Success() { + // In the case of success, synchronize the config file from generated channel identifiers if err := SyncChainConfigsFromEvents(pathName, chanSteps.SrcMsgIDs, chanSteps.DstMsgIDs, src, dst); err != nil { return err } - } - switch { - // In the case of success and this being the last transaction - // debug logging, log created connection and break - case chanSteps.Success() && chanSteps.Last: - logger.Info( - "★ Channel created", - ) - return nil - // In the case of success, reset the failures counter - case chanSteps.Success(): + // In the case of success and this being the last transaction + // debug logging, log created connection and break + if chanSteps.Last { + logger.Info("★ Channel created") + return nil + } + + // In the case of success, reset the failures counter failures = 0 - continue - // In the case of failure, increment the failures counter and exit if this is the 3rd failure - case !chanSteps.Success(): - failures++ - logger.Info("retrying transaction...") - time.Sleep(5 * time.Second) - if failures > 2 { - logger.Error( - "! Channel failed", - err, - ) - return fmt.Errorf("! Channel failed: [%s]chan{%s}port{%s} -> [%s]chan{%s}port{%s}", - src.ChainID(), src.Path().ChannelID, src.Path().PortID, - dst.ChainID(), dst.Path().ChannelID, dst.Path().PortID, - ) + } else { + // In the case of failure, increment the failures counter and exit if this is the 3rd failure + if failures++; failures > 2 { + err := errors.New("Channel handshake failed") + logger.Error(err.Error(), err) + return err } + + logger.Warn("Retrying transaction...") + time.Sleep(5 * time.Second) } } @@ -115,10 +109,10 @@ func checkChannelCreateReady(src, dst *ProvableChain, logger *log.RelayLogger) ( } if srcID != "" && srcState == chantypes.UNINITIALIZED { - return false, fmt.Errorf("src channel id is given but that channel does not exist: %s", srcID); + return false, fmt.Errorf("src channel id is given but that channel does not exist: %s", srcID) } if dstID != "" && dstState == chantypes.UNINITIALIZED { - return false, fmt.Errorf("dst channel id is given but that channel does not exist: %s", dstID); + return false, fmt.Errorf("dst channel id is given but that channel does not exist: %s", dstID) } if srcState == chantypes.OPEN && dstState == chantypes.OPEN { diff --git a/core/connection.go b/core/connection.go index 0a9c4f96..05fabd15 100644 --- a/core/connection.go +++ b/core/connection.go @@ -50,39 +50,32 @@ func CreateConnection(pathName string, src, dst *ProvableChain, to time.Duration } connSteps.Send(src, dst) + if connSteps.Success() { + // In the case of success, synchronize the config file from generated connection identifiers. if err := SyncChainConfigsFromEvents(pathName, connSteps.SrcMsgIDs, connSteps.DstMsgIDs, src, dst); err != nil { return err } - } - switch { - // In the case of success and this being the last transaction - // debug logging, log created connection and break - case connSteps.Success() && connSteps.Last: - logger.Info( - "★ Connection created", - ) - return nil - // In the case of success, reset the failures counter - case connSteps.Success(): + // In the case of success and this being the last transaction + // debug logging, log created connection and break + if connSteps.Last { + logger.Info("★ Connection created") + return nil + } + + // In the case of success, reset the failures counter failed = 0 - continue - // In the case of failure, increment the failures counter and exit if this is the 3rd failure - case !connSteps.Success(): - failed++ - logger.Info("retrying transaction...") - time.Sleep(5 * time.Second) - if failed > 2 { - logger.Error( - "! Connection failed", - errors.New("failed 3 times"), - ) - return fmt.Errorf("! Connection failed: [%s]client{%s}conn{%s} -> [%s]client{%s}conn{%s}", - src.ChainID(), src.Path().ClientID, src.Path().ConnectionID, - dst.ChainID(), dst.Path().ClientID, dst.Path().ConnectionID, - ) + } else { + // In the case of failure, increment the failures counter and exit if this is the 3rd failure + if failed++; failed > 2 { + err := errors.New("Connection handshake failed") + logger.Error(err.Error(), err) + return err } + + logger.Warn("Retrying transaction...") + time.Sleep(5 * time.Second) } } @@ -125,10 +118,10 @@ func checkConnectionCreateReady(src, dst *ProvableChain, logger *log.RelayLogger } if srcID != "" && srcState == conntypes.UNINITIALIZED { - return false, fmt.Errorf("src connection id is given but that connection does not exist: %s", srcID); + return false, fmt.Errorf("src connection id is given but that connection does not exist: %s", srcID) } if dstID != "" && dstState == conntypes.UNINITIALIZED { - return false, fmt.Errorf("dst connection id is given but that connection does not exist: %s", dstID); + return false, fmt.Errorf("dst connection id is given but that connection does not exist: %s", dstID) } if srcState == conntypes.OPEN && dstState == conntypes.OPEN { diff --git a/core/relayMsgs.go b/core/relayMsgs.go index cff5edee..9081267f 100644 --- a/core/relayMsgs.go +++ b/core/relayMsgs.go @@ -28,14 +28,7 @@ func NewRelayMsgs() *RelayMsgs { // Ready returns true if there are messages to relay func (r *RelayMsgs) Ready() bool { - if r == nil { - return false - } - - if len(r.Src) == 0 && len(r.Dst) == 0 { - return false - } - return true + return r != nil && (len(r.Src) > 0 || len(r.Dst) > 0) } // Success returns the success var From 0232e18ae4d3ef700d6bbc90a110917a1a390eaf Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 10 Jun 2024 11:39:29 +0900 Subject: [PATCH 02/47] add `connectionID` argument to `Chain::QueryConnection` method Signed-off-by: Masanori Yoshida --- chains/tendermint/query.go | 8 ++++---- cmd/query.go | 2 +- core/chain.go | 2 +- core/connection.go | 2 +- core/query.go | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/chains/tendermint/query.go b/chains/tendermint/query.go index a1f5cc8d..edccdbe5 100644 --- a/chains/tendermint/query.go +++ b/chains/tendermint/query.go @@ -55,12 +55,12 @@ var emptyConnRes = conntypes.NewQueryConnectionResponse( ) // QueryConnection returns the remote end of a given connection -func (c *Chain) QueryConnection(ctx core.QueryContext) (*conntypes.QueryConnectionResponse, error) { - return c.queryConnection(int64(ctx.Height().GetRevisionHeight()), false) +func (c *Chain) QueryConnection(ctx core.QueryContext, connectionID string) (*conntypes.QueryConnectionResponse, error) { + return c.queryConnection(int64(ctx.Height().GetRevisionHeight()), connectionID, false) } -func (c *Chain) queryConnection(height int64, prove bool) (*conntypes.QueryConnectionResponse, error) { - res, err := connutils.QueryConnection(c.CLIContext(height), c.PathEnd.ConnectionID, prove) +func (c *Chain) queryConnection(height int64, connectionID string, prove bool) (*conntypes.QueryConnectionResponse, error) { + res, err := connutils.QueryConnection(c.CLIContext(height), connectionID, prove) if err != nil && strings.Contains(err.Error(), "not found") { return emptyConnRes, nil } else if err != nil { diff --git a/cmd/query.go b/cmd/query.go index 6d3ed90f..2a79b262 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -101,7 +101,7 @@ func queryConnection(ctx *config.Context) *cobra.Command { return err } queryHeight := clienttypes.NewHeight(latestHeight.GetRevisionNumber(), uint64(height)) - res, err := c.QueryConnection(core.NewQueryContext(context.TODO(), queryHeight)) + res, err := c.QueryConnection(core.NewQueryContext(context.TODO(), queryHeight), c.Path().ConnectionID) if err != nil { return err } diff --git a/core/chain.go b/core/chain.go index b5018d03..18c92b99 100644 --- a/core/chain.go +++ b/core/chain.go @@ -136,7 +136,7 @@ type ICS02Querier interface { // ICS03Querier is an interface to the state of ICS-03 type ICS03Querier interface { // QueryConnection returns the remote end of a given connection - QueryConnection(ctx QueryContext) (*conntypes.QueryConnectionResponse, error) + QueryConnection(ctx QueryContext, connectionID string) (*conntypes.QueryConnectionResponse, error) } // ICS04Querier is an interface to the state of ICS-04 diff --git a/core/connection.go b/core/connection.go index 05fabd15..da9208c3 100644 --- a/core/connection.go +++ b/core/connection.go @@ -100,7 +100,7 @@ func checkConnectionCreateReady(src, dst *ProvableChain, logger *log.RelayLogger if err != nil { return conntypes.UNINITIALIZED, err } - res, err2 := pc.QueryConnection(NewQueryContext(context.TODO(), latestHeight)) + res, err2 := pc.QueryConnection(NewQueryContext(context.TODO(), latestHeight), pc.Path().ConnectionID) if err2 != nil { return conntypes.UNINITIALIZED, err2 } diff --git a/core/query.go b/core/query.go index b93d0a81..674b096e 100644 --- a/core/query.go +++ b/core/query.go @@ -127,7 +127,7 @@ func QueryConnectionPair( return nil } var err error - srcConn, err = src.QueryConnection(srcCtx) + srcConn, err = src.QueryConnection(srcCtx, src.Path().ConnectionID) if err != nil { return err } else if srcConn.Connection.State == conntypes.UNINITIALIZED { @@ -154,7 +154,7 @@ func QueryConnectionPair( return nil } var err error - dstConn, err = dst.QueryConnection(dstCtx) + dstConn, err = dst.QueryConnection(dstCtx, dst.Path().ConnectionID) if err != nil { return err } else if dstConn.Connection.State == conntypes.UNINITIALIZED { From 38c14123a4f6deda641d9f6748f48d4fbf8482f7 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 10 Jun 2024 13:56:33 +0900 Subject: [PATCH 03/47] add methods for PathEnd to create MsgChannelUpgradeXxx messages Signed-off-by: Masanori Yoshida --- core/pathEnd.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/core/pathEnd.go b/core/pathEnd.go index 4ffba512..e5ec4ad2 100644 --- a/core/pathEnd.go +++ b/core/pathEnd.go @@ -237,6 +237,117 @@ func (pe *PathEnd) ChanCloseConfirm(dstChanState *chantypes.QueryChannelResponse ) } +// ChanUpgradeInit creates a MsgChannelUpgradeInit +func (pe *PathEnd) ChanUpgradeInit(upgradeFields chantypes.UpgradeFields, signer sdk.AccAddress) sdk.Msg { + return chantypes.NewMsgChannelUpgradeInit( + pe.PortID, + pe.ChannelID, + upgradeFields, + signer.String(), + ) +} + +// ChanUpgradeTry creates a MsgChannelUpgradeTry +func (pe *PathEnd) ChanUpgradeTry( + newConnectionID string, + counterpartyChan *chantypes.QueryChannelResponse, + counterpartyUpg *chantypes.QueryUpgradeResponse, + signer sdk.AccAddress, +) sdk.Msg { + return chantypes.NewMsgChannelUpgradeTry( + pe.PortID, + pe.ChannelID, + []string{newConnectionID}, + counterpartyUpg.Upgrade.Fields, + counterpartyChan.Channel.UpgradeSequence, + counterpartyChan.Proof, + counterpartyUpg.Proof, + counterpartyChan.ProofHeight, + signer.String(), + ) +} + +// ChanUpgradeAck creates a MsgChannelUpgradeAck +func (pe *PathEnd) ChanUpgradeAck( + counterpartyChan *chantypes.QueryChannelResponse, + counterpartyUpg *chantypes.QueryUpgradeResponse, + signer sdk.AccAddress, +) sdk.Msg { + return chantypes.NewMsgChannelUpgradeAck( + pe.PortID, + pe.ChannelID, + counterpartyUpg.Upgrade, + counterpartyChan.Proof, + counterpartyUpg.Proof, + counterpartyChan.ProofHeight, + signer.String(), + ) +} + +// ChanUpgradeConfirm creates a MsgChannelUpgradeConfirm +func (pe *PathEnd) ChanUpgradeConfirm( + counterpartyChan *chantypes.QueryChannelResponse, + counterpartyUpg *chantypes.QueryUpgradeResponse, + signer sdk.AccAddress, +) sdk.Msg { + return chantypes.NewMsgChannelUpgradeConfirm( + pe.PortID, + pe.ChannelID, + counterpartyChan.Channel.State, + counterpartyUpg.Upgrade, + counterpartyChan.Proof, + counterpartyUpg.Proof, + counterpartyChan.ProofHeight, + signer.String(), + ) +} + +// ChanUpgradeOpen creates a MsgChannelUpgradeOpen +func (pe *PathEnd) ChanUpgradeOpen( + counterpartyChan *chantypes.QueryChannelResponse, + signer sdk.AccAddress, +) sdk.Msg { + return chantypes.NewMsgChannelUpgradeOpen( + pe.PortID, + pe.ChannelID, + counterpartyChan.Channel.State, + counterpartyChan.Channel.UpgradeSequence, + counterpartyChan.Proof, + counterpartyChan.ProofHeight, + signer.String(), + ) +} + +// ChanUpgradeCancel creates a MsgChannelUpgradeCancel +func (pe *PathEnd) ChanUpgradeCancel( + counterpartyChanUpgErr *chantypes.QueryUpgradeErrorResponse, + signer sdk.AccAddress, +) sdk.Msg { + return chantypes.NewMsgChannelUpgradeCancel( + pe.PortID, + pe.ChannelID, + counterpartyChanUpgErr.ErrorReceipt, + counterpartyChanUpgErr.Proof, + counterpartyChanUpgErr.ProofHeight, + signer.String(), + ) +} + +// ChanUpgradeTimeout creates a MsgChannelUpgradeTimeout +func (pe *PathEnd) ChanUpgradeTimeout( + counterpartyChan *chantypes.QueryChannelResponse, + signer sdk.AccAddress, +) sdk.Msg { + return chantypes.NewMsgChannelUpgradeTimeout( + pe.PortID, + pe.ChannelID, + *counterpartyChan.Channel, + counterpartyChan.Proof, + counterpartyChan.ProofHeight, + signer.String(), + ) +} + // MsgTransfer creates a new transfer message func (pe *PathEnd) MsgTransfer(dst *PathEnd, amount sdk.Coin, dstAddr string, signer sdk.AccAddress, timeoutHeight, timeoutTimestamp uint64, memo string) sdk.Msg { From bc2695c64bc365028e4788b494d90ed1bdc1f265 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 10 Jun 2024 14:02:30 +0900 Subject: [PATCH 04/47] add QueryChannelUpgrade and QueryChannelUpgradeError to Chain interface Signed-off-by: Masanori Yoshida --- chains/tendermint/query.go | 44 ++++++++++++++++++++++++++++++++++++++ core/chain.go | 6 ++++++ 2 files changed, 50 insertions(+) diff --git a/chains/tendermint/query.go b/chains/tendermint/query.go index edccdbe5..aeddf658 100644 --- a/chains/tendermint/query.go +++ b/chains/tendermint/query.go @@ -27,6 +27,8 @@ import ( committypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" "github.com/hyperledger-labs/yui-relayer/core" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) // QueryClientState retrevies the latest consensus state for a client in state at a given height @@ -374,6 +376,48 @@ func (c *Chain) QueryTxs(height int64, page, limit int, events []string) ([]*cty return res.Txs, nil } +func (c *Chain) QueryChannelUpgrade(ctx core.QueryContext) (*chantypes.QueryUpgradeResponse, error) { + return c.queryChannelUpgrade(int64(ctx.Height().GetRevisionHeight()), false) +} + +func (c *Chain) queryChannelUpgrade(height int64, prove bool) (chanRes *chantypes.QueryUpgradeResponse, err error) { + if res, err := chanutils.QueryUpgrade( + c.CLIContext(height), + c.PathEnd.PortID, + c.PathEnd.ChannelID, + prove, + ); err != nil { + if st, ok := status.FromError(err); ok && st.Code() == codes.NotFound { + return nil, nil + } else { + return nil, err + } + } else { + return res, nil + } +} + +func (c *Chain) QueryChannelUpgradeError(ctx core.QueryContext) (*chantypes.QueryUpgradeErrorResponse, error) { + return c.queryChannelUpgradeError(int64(ctx.Height().GetRevisionHeight()), false) +} + +func (c *Chain) queryChannelUpgradeError(height int64, prove bool) (chanRes *chantypes.QueryUpgradeErrorResponse, err error) { + if res, err := chanutils.QueryUpgradeError( + c.CLIContext(height), + c.PathEnd.PortID, + c.PathEnd.ChannelID, + prove, + ); err != nil { + if st, ok := status.FromError(err); ok && st.Code() == codes.NotFound { + return nil, nil + } else { + return nil, err + } + } else { + return res, nil + } +} + ///////////////////////////////////// // STAKING -> HistoricalInfo // ///////////////////////////////////// diff --git a/core/chain.go b/core/chain.go index 18c92b99..c93de43c 100644 --- a/core/chain.go +++ b/core/chain.go @@ -155,6 +155,12 @@ type ICS04Querier interface { // QueryUnfinalizedRelayedAcknowledgements returns acks and heights that are sent but not received at the latest finalized block on the counterpartychain QueryUnfinalizedRelayAcknowledgements(ctx QueryContext, counterparty LightClientICS04Querier) (PacketInfoList, error) + + // QueryChannelUpgrade returns the channel upgrade associated with a channelID + QueryChannelUpgrade(ctx QueryContext) (*chantypes.QueryUpgradeResponse, error) + + // QueryChannelUpgradeError returns the channel upgrade error receipt associated with a channelID + QueryChannelUpgradeError(ctx QueryContext) (*chantypes.QueryUpgradeErrorResponse, error) } // ICS20Querier is an interface to the state of ICS-20 From 901aecf16644d8395dff1a7c7828ae62a9db649f Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 20 Jun 2024 12:42:02 +0900 Subject: [PATCH 05/47] add Chain::QueryCanTransitionToFlushComplete Signed-off-by: Masanori Yoshida --- chains/tendermint/query.go | 17 +++++++++++++++++ core/chain.go | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/chains/tendermint/query.go b/chains/tendermint/query.go index aeddf658..80f8c45f 100644 --- a/chains/tendermint/query.go +++ b/chains/tendermint/query.go @@ -418,6 +418,23 @@ func (c *Chain) queryChannelUpgradeError(height int64, prove bool) (chanRes *cha } } +func (c *Chain) QueryCanTransitionToFlushComplete(ctx core.QueryContext) (bool, error) { + return c.queryCanTransitionToFlushComplete(int64(ctx.Height().GetRevisionHeight())) +} + +func (c *Chain) queryCanTransitionToFlushComplete(height int64) (bool, error) { + queryClient := chantypes.NewQueryClient(c.CLIContext(height)) + req := chantypes.QueryPacketCommitmentsRequest{ + PortId: c.PathEnd.PortID, + ChannelId: c.PathEnd.ChannelID, + } + if res, err := queryClient.PacketCommitments(context.TODO(), &req); err != nil { + return false, err + } else { + return len(res.Commitments) == 0, nil + } +} + ///////////////////////////////////// // STAKING -> HistoricalInfo // ///////////////////////////////////// diff --git a/core/chain.go b/core/chain.go index c93de43c..3ef0e7bf 100644 --- a/core/chain.go +++ b/core/chain.go @@ -161,6 +161,11 @@ type ICS04Querier interface { // QueryChannelUpgradeError returns the channel upgrade error receipt associated with a channelID QueryChannelUpgradeError(ctx QueryContext) (*chantypes.QueryUpgradeErrorResponse, error) + + // QueryCanTransitionToFlushComplete returns the channel can transition to FLUSHCOMPLETE state. + // Basically it requires that there remains no inflight packets. + // Maybe additional condition for transition is required by the IBC/APP module. + QueryCanTransitionToFlushComplete(ctx QueryContext) (bool, error) } // ICS20Querier is an interface to the state of ICS-20 From 77600cbd5f4f2f77547a7098075f9a1c641e88e7 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 20 Jun 2024 13:27:04 +0900 Subject: [PATCH 06/47] implement channel upgrade Signed-off-by: Masanori Yoshida --- cmd/tx.go | 124 +++++++++ core/channel-upgrade.go | 579 ++++++++++++++++++++++++++++++++++++++++ core/query.go | 110 ++++++++ 3 files changed, 813 insertions(+) create mode 100644 core/channel-upgrade.go diff --git a/cmd/tx.go b/cmd/tx.go index cb554a20..14990494 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -4,9 +4,11 @@ import ( "context" "fmt" "strings" + "time" "github.com/cosmos/cosmos-sdk/client/flags" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + chantypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" "github.com/cosmos/ibc-go/v8/modules/core/exported" "github.com/hyperledger-labs/yui-relayer/config" "github.com/hyperledger-labs/yui-relayer/core" @@ -35,6 +37,7 @@ func transactionCmd(ctx *config.Context) *cobra.Command { updateClientsCmd(ctx), createConnectionCmd(ctx), createChannelCmd(ctx), + channelUpgradeCmd(ctx), ) return cmd @@ -197,6 +200,127 @@ func createChannelCmd(ctx *config.Context) *cobra.Command { return timeoutFlag(cmd) } +func channelUpgradeCmd(ctx *config.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "channel-upgrade", + Short: "execute operations related to IBC channel upgrade", + Long: "This command is meant to be used to upgrade a channel between two chains with a configured path in the config file", + RunE: noCommand, + } + + cmd.AddCommand( + channelUpgradeInitCmd(ctx), + channelUpgradeExecuteCmd(ctx), + //channelUpgradeCancel(ctx), + ) + + return cmd +} + +func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { + const ( + flagOrdering = "ordering" + flagConnectionHops = "connection-hops" + flagVersion = "version" + ) + + cmd := cobra.Command{ + Use: "init [path-name] [chain-id]", + Short: "execute chanOpenInit", + Long: "This command is meant to be used to initialize an IBC channel upgrade on a configured chain", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + pathName := args[0] + chainID := args[1] + if _, _, _, err := ctx.Config.ChainsFromPath(pathName); err != nil { + return nil + } + chain, err := ctx.Config.GetChain(chainID) + if err != nil { + return err + } + + // get ordering from flags + var ordering chantypes.Order + if s, err := cmd.Flags().GetString(flagOrdering); err != nil { + return err + } else if n, ok := chantypes.Order_value[s]; !ok || n == int32(chantypes.NONE) { + return fmt.Errorf("invalid ordering flag: %s", s) + } else { + ordering = chantypes.Order(n) + } + + // get connection hops from flags + connHops, err := cmd.Flags().GetStringSlice(flagConnectionHops) + if err != nil { + return err + } + + // get version from flags + version, err := cmd.Flags().GetString(flagVersion) + if err != nil { + return err + } + + return core.InitChannelUpgrade(chain, chantypes.UpgradeFields{ + Ordering: ordering, + ConnectionHops: connHops, + Version: version, + }) + }, + } + + cmd.Flags().String(flagOrdering, "", "channel ordering applied for the new channel") + cmd.Flags().StringSlice(flagConnectionHops, nil, "connection hops applied for the new channel") + cmd.Flags().String(flagVersion, "", "channel version applied for the new channel") + + return &cmd +} + +func channelUpgradeExecuteCmd(ctx *config.Context) *cobra.Command { + const ( + flagInterval = "interval" + ) + + const ( + defaultInterval = time.Second + ) + + cmd := cobra.Command{ + Use: "execute [path-name]", + Short: "execute channel upgrade handshake", + Long: "This command is meant to be used to execute an IBC channel upgrade handshake between two chains with a configured path in the config file", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + pathName := args[0] + chains, srcChainID, dstChainID, err := ctx.Config.ChainsFromPath(pathName) + if err != nil { + return err + } + + src, ok := chains[srcChainID] + if !ok { + panic("src chain not found") + } + dst, ok := chains[dstChainID] + if !ok { + panic("dst chain not found") + } + + interval, err := cmd.Flags().GetDuration(flagInterval) + if err != nil { + return err + } + + return core.ExecuteChannelUpgrade(src, dst, interval) + }, + } + + cmd.Flags().Duration(flagInterval, defaultInterval, "interval between attempts to proceed channel upgrade steps") + + return &cmd +} + func relayMsgsCmd(ctx *config.Context) *cobra.Command { const ( flagDoRefresh = "do-refresh" diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go new file mode 100644 index 00000000..b453ede4 --- /dev/null +++ b/core/channel-upgrade.go @@ -0,0 +1,579 @@ +package core + +import ( + "errors" + "fmt" + "math" + "time" + + retry "github.com/avast/retry-go" + sdk "github.com/cosmos/cosmos-sdk/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + chantypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" +) + +// InitChannelUpgrade builds `MsgChannelUpgradeInit` based on the specified UpgradeFields and sends it to the specified chain. +func InitChannelUpgrade(chain *ProvableChain, upgradeFields chantypes.UpgradeFields) error { + logger := GetChannelLogger(chain.Chain) + defer logger.TimeTrack(time.Now(), "InitChannelUpgrade") + + addr, err := chain.GetAddress() + if err != nil { + logger.Error("failed to get address", err) + return err + } + + msg := chain.Path().ChanUpgradeInit(upgradeFields, addr) + + msgIDs, err := chain.SendMsgs([]sdk.Msg{msg}) + if err != nil { + logger.Error("failed to send MsgChannelUpgradeInit", err) + return err + } else if len(msgIDs) != 1 { + panic(fmt.Sprintf("len(msgIDs) == %d", len(msgIDs))) + } else if result, err := GetFinalizedMsgResult(*chain, msgIDs[0]); err != nil { + logger.Error("failed to the finalized result of MsgChannelUpgradeInit", err) + return err + } else if ok, desc := result.Status(); !ok { + err := fmt.Errorf("failed to initialize channel upgrade: %s", desc) + logger.Error(err.Error(), err) + } else { + logger.Info("successfully initialized channel upgrade") + } + + return nil +} + +// ExecuteChannelUpgrade carries out channel upgrade handshake until both chains transition to the OPEN state. +// This function repeatedly checks the states of both chains and decides the next action. +func ExecuteChannelUpgrade(src, dst *ProvableChain, interval time.Duration) error { + logger := GetChannelPairLogger(src, dst) + defer logger.TimeTrack(time.Now(), "ExecuteChannelUpgrade") + + tick := time.Tick(interval) + failures := 0 + for { + <-tick + + steps, err := upgradeChannelStep(src, dst) + if err != nil { + logger.Error("failed to create channel upgrade step", err) + return err + } + + if !steps.Ready() { + logger.Debug("Waiting for next channel upgrade step ...") + continue + } + + steps.Send(src, dst) + + if steps.Success() { + if steps.Last { + logger.Info("Channel upgrade completed") + return nil + } + + failures = 0 + } else { + if failures++; failures > 2 { + err := errors.New("Channel upgrade failed") + logger.Error(err.Error(), err) + return err + } + + logger.Warn("Retrying transaction...") + time.Sleep(5 * time.Second) + } + } +} + +func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { + logger := GetChannelPairLogger(src, dst) + + out := NewRelayMsgs() + if err := validatePaths(src, dst); err != nil { + logger.Error("failed to validate paths", err) + return nil, err + } + + // First, update the light clients to the latest header and return the header + sh, err := NewSyncHeaders(src, dst) + if err != nil { + logger.Error("failed to create SyncHeaders", err) + return nil, err + } + + // Query a number of things all at once + var srcUpdateHeaders, dstUpdateHeaders []Header + if err := retry.Do(func() error { + srcUpdateHeaders, dstUpdateHeaders, err = sh.SetupBothHeadersForUpdate(src, dst) + return err + }, rtyAtt, rtyDel, rtyErr, retry.OnRetry(func(uint, error) { + if err := sh.Updates(src, dst); err != nil { + panic(err) + } + })); err != nil { + logger.Error("failed to set up headers for LC update on both chains", err) + return nil, err + } + + // prepare query contexts + var srcCtxFinalized, dstCtxFinalized, srcCtxLatest, dstCtxLatest QueryContext + if srcCtxFinalized, err = getQueryContext(src, sh, true); err != nil { + return nil, err + } + if dstCtxFinalized, err = getQueryContext(dst, sh, true); err != nil { + return nil, err + } + if srcCtxLatest, err = getQueryContext(src, sh, false); err != nil { + return nil, err + } + if dstCtxLatest, err = getQueryContext(dst, sh, false); err != nil { + return nil, err + } + + // query finalized channels with proofs + srcChan, dstChan, err := QueryChannelPair( + srcCtxFinalized, + dstCtxFinalized, + src, + dst, + true, + ) + if err != nil { + return nil, err + } else if finalized, err := checkChannelFinality(src, dst, srcChan.Channel, dstChan.Channel); err != nil { + return nil, err + } else if !finalized { + return out, nil + } + + // query finalized channel upgrades with proofs + srcChanUpg, dstChanUpg, finalized, err := queryFinalizedChannelUpgradePair( + srcCtxFinalized, + dstCtxFinalized, + srcCtxLatest, + dstCtxLatest, + src, + dst, + ) + if err != nil { + return nil, err + } else if !finalized { + return out, nil + } + + // translate channel state to channel upgrade state + type UpgradeState chantypes.State + const ( + UPGRADEUNINIT = UpgradeState(chantypes.OPEN) + UPGRADEINIT = UpgradeState(math.MaxInt32) + FLUSHING = UpgradeState(chantypes.FLUSHING) + FLUSHCOMPLETE = UpgradeState(chantypes.FLUSHCOMPLETE) + ) + srcState := UpgradeState(srcChan.Channel.State) + if srcState == UPGRADEUNINIT && srcChanUpg != nil { + srcState = UPGRADEINIT + } + dstState := UpgradeState(dstChan.Channel.State) + if dstState == UPGRADEUNINIT && dstChanUpg != nil { + dstState = UPGRADEINIT + } + + // query finalized channel upgrade error receipts with proofs + srcChanUpgErr, dstChanUpgErr, finalized, err := queryFinalizedChannelUpgradeErrorPair( + srcCtxFinalized, + dstCtxFinalized, + srcCtxLatest, + dstCtxLatest, + src, + dst, + ) + if err != nil { + return nil, err + } else if !finalized { + return out, nil + } + + doTry := func(chain *ProvableChain, cpCtx QueryContext, cp *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse, cpChanUpg *chantypes.QueryUpgradeResponse) ([]sdk.Msg, error) { + proposedConnectionID, err := queryProposedConnectionID(cpCtx, cp, cpChanUpg) + if err != nil { + return nil, err + } + var msgs []sdk.Msg + addr := mustGetAddress(chain) + if len(cpHeaders) > 0 { + msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) + } + msgs = append(msgs, chain.Path().ChanUpgradeTry(proposedConnectionID, cpChan, cpChanUpg, addr)) + return msgs, nil + } + + doAck := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse, cpChanUpg *chantypes.QueryUpgradeResponse) []sdk.Msg { + var msgs []sdk.Msg + addr := mustGetAddress(chain) + if len(cpHeaders) > 0 { + msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) + } + msgs = append(msgs, chain.Path().ChanUpgradeAck(cpChan, cpChanUpg, addr)) + return msgs + } + + doConfirm := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse, cpChanUpg *chantypes.QueryUpgradeResponse) []sdk.Msg { + var msgs []sdk.Msg + addr := mustGetAddress(chain) + if len(cpHeaders) > 0 { + msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) + } + msgs = append(msgs, chain.Path().ChanUpgradeConfirm(cpChan, cpChanUpg, addr)) + return msgs + } + + doOpen := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse) []sdk.Msg { + var msgs []sdk.Msg + addr := mustGetAddress(chain) + if len(cpHeaders) > 0 { + msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) + } + msgs = append(msgs, chain.Path().ChanUpgradeOpen(cpChan, addr)) + return msgs + } + + doCancel := func(chain *ProvableChain, cpHeaders []Header, cpChanUpgErr *chantypes.QueryUpgradeErrorResponse) []sdk.Msg { + var msgs []sdk.Msg + addr := mustGetAddress(chain) + if len(cpHeaders) > 0 { + msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) + } + msgs = append(msgs, chain.Path().ChanUpgradeCancel(cpChanUpgErr, addr)) + return msgs + } + + doTimeout := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse) []sdk.Msg { + var msgs []sdk.Msg + addr := mustGetAddress(chain) + if len(cpHeaders) > 0 { + msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) + } + msgs = append(msgs, chain.Path().ChanUpgradeTimeout(cpChan, addr)) + return msgs + } + + switch { + case srcState == UPGRADEUNINIT && dstState == UPGRADEUNINIT: + return nil, errors.New("channel upgrade is not initialized") + case srcState == UPGRADEINIT && dstState == UPGRADEUNINIT: + if dstChan.Channel.UpgradeSequence >= srcChan.Channel.UpgradeSequence { + out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + } else { + if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { + return nil, err + } + } + case srcState == UPGRADEUNINIT && dstState == UPGRADEINIT: + if srcChan.Channel.UpgradeSequence >= dstChan.Channel.UpgradeSequence { + out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + } else { + if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { + return nil, err + } + } + case srcState == UPGRADEUNINIT && dstState == FLUSHING: + out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + case srcState == FLUSHING && dstState == UPGRADEUNINIT: + out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + case srcState == UPGRADEUNINIT && dstState == FLUSHCOMPLETE: + if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { + return nil, err + } else if complete { + out.Dst = doOpen(dst, srcUpdateHeaders, srcChan) + } else if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + return nil, err + } else if timedout { + out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) + } else { + out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + } + case srcState == FLUSHCOMPLETE && dstState == UPGRADEUNINIT: + if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { + return nil, err + } else if complete { + out.Src = doOpen(src, dstUpdateHeaders, dstChan) + } else if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + return nil, err + } else if timedout { + out.Src = doTimeout(src, dstUpdateHeaders, dstChan) + } else { + out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + } + case srcState == UPGRADEINIT && dstState == UPGRADEINIT: // crossing hellos + // it is intentional to execute chanUpgradeTry on both sides if upgrade sequences + // are identical to each other. this is for testing purpose. + if srcChan.Channel.UpgradeSequence >= dstChan.Channel.UpgradeSequence { + if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { + return nil, err + } + } + if srcChan.Channel.UpgradeSequence <= dstChan.Channel.UpgradeSequence { + if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { + return nil, err + } + } + case srcState == UPGRADEINIT && dstState == FLUSHING: + if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { + out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + } else { + // chanUpgradeAck checks if counterparty-specified timeout has exceeded. + // if it has, chanUpgradeAck aborts the upgrade handshake. + // Therefore the relayer need not check timeout by itself. + out.Src = doAck(src, dstUpdateHeaders, dstChan, dstChanUpg) + } + case srcState == FLUSHING && dstState == UPGRADEINIT: + if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { + out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + } else { + // chanUpgradeAck checks if counterparty-specified timeout has exceeded. + // if it has, chanUpgradeAck aborts the upgrade handshake. + // Therefore the relayer need not check timeout by itself. + out.Dst = doAck(dst, srcUpdateHeaders, srcChan, srcChanUpg) + } + case srcState == UPGRADEINIT && dstState == FLUSHCOMPLETE: + if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { + return nil, err + } else if complete { + out.Dst = doOpen(dst, srcUpdateHeaders, srcChan) + } else if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + return nil, err + } else if timedout { + out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) + } else { + out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + } + case srcState == FLUSHCOMPLETE && dstState == UPGRADEINIT: + if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { + return nil, err + } else if complete { + out.Src = doOpen(src, dstUpdateHeaders, dstChan) + } else if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + return nil, err + } else if timedout { + out.Src = doTimeout(src, dstUpdateHeaders, dstChan) + } else { + out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + } + case srcState == FLUSHING && dstState == FLUSHING: + nTimedout := 0 + if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + return nil, err + } else if timedout { + nTimedout += 1 + out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) + } + if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + return nil, err + } else if timedout { + nTimedout += 1 + out.Src = doTimeout(src, dstUpdateHeaders, dstChan) + } + + // if any chains have exceeded timeout, never execute chanUpgradeConfirm + if nTimedout > 0 { + break + } + + if completable, err := src.QueryCanTransitionToFlushComplete(srcCtxFinalized); err != nil { + return nil, err + } else if completable { + out.Src = doConfirm(src, dstUpdateHeaders, dstChan, dstChanUpg) + } + if completable, err := dst.QueryCanTransitionToFlushComplete(dstCtxFinalized); err != nil { + return nil, err + } else if completable { + out.Dst = doConfirm(dst, srcUpdateHeaders, srcChan, srcChanUpg) + } + case srcState == FLUSHING && dstState == FLUSHCOMPLETE: + if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + return nil, err + } else if timedout { + out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) + } else if completable, err := src.QueryCanTransitionToFlushComplete(srcCtxFinalized); err != nil { + return nil, err + } else if completable { + out.Src = doConfirm(src, dstUpdateHeaders, dstChan, dstChanUpg) + } + case srcState == FLUSHCOMPLETE && dstState == FLUSHING: + if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + return nil, err + } else if timedout { + out.Src = doTimeout(src, dstUpdateHeaders, dstChan) + } else if completable, err := dst.QueryCanTransitionToFlushComplete(dstCtxFinalized); err != nil { + return nil, err + } else if completable { + out.Dst = doConfirm(dst, srcUpdateHeaders, srcChan, srcChanUpg) + } + case srcState == FLUSHCOMPLETE && dstState == FLUSHCOMPLETE: + out.Src = doOpen(src, dstUpdateHeaders, dstChan) + out.Dst = doOpen(dst, srcUpdateHeaders, srcChan) + default: + return nil, errors.New("unexpected state") + } + + return out, nil +} + +func queryProposedConnectionID(cpCtx QueryContext, cp *ProvableChain, cpChanUpg *chantypes.QueryUpgradeResponse) (string, error) { + if cpConn, err := cp.QueryConnection( + cpCtx, + cpChanUpg.Upgrade.Fields.ConnectionHops[0], + ); err != nil { + return "", err + } else { + return cpConn.Connection.Counterparty.ConnectionId, nil + } +} + +func queryFinalizedChannelUpgradePair( + srcCtxFinalized, + dstCtxFinalized, + srcCtxLatest, + dstCtxLatest QueryContext, + src, + dst *ProvableChain, +) (srcChanUpg, dstChanUpg *chantypes.QueryUpgradeResponse, finalized bool, err error) { + logger := GetChannelPairLogger(src, dst) + + // query channel upgrade pair at latest finalized heights + srcChanUpgF, dstChanUpgF, err := QueryChannelUpgradePair( + srcCtxFinalized, + dstCtxFinalized, + src, + dst, + true, + ) + if err != nil { + logger.Error("failed to query a channel upgrade pair at the latest finalized heights", err) + return nil, nil, false, err + } + + // query channel upgrade pair at latest heights + srcChanUpgL, dstChanUpgL, err := QueryChannelUpgradePair( + srcCtxLatest, + dstCtxLatest, + src, + dst, + false, + ) + if err != nil { + logger.Error("failed to query a channel upgrade pair at the latest heights", err) + return nil, nil, false, err + } + + if !compareUpgrades(srcChanUpgF, srcChanUpgL) { + logger.Debug("channel upgrade is not finalized on src chain") + return nil, nil, false, nil + } + if !compareUpgrades(dstChanUpgF, dstChanUpgL) { + logger.Debug("channel upgrade is not finalized on dst chain") + return nil, nil, false, nil + } + + return srcChanUpgF, dstChanUpgF, true, nil +} + +func compareUpgrades(a, b *chantypes.QueryUpgradeResponse) bool { + if a == nil { + return b == nil + } + if b == nil { + return false + } + return a.Upgrade.String() == b.Upgrade.String() +} + +func queryFinalizedChannelUpgradeErrorPair( + srcCtxFinalized, + dstCtxFinalized, + srcCtxLatest, + dstCtxLatest QueryContext, + src, + dst *ProvableChain, +) (srcChanUpg, dstChanUpg *chantypes.QueryUpgradeErrorResponse, finalized bool, err error) { + logger := GetChannelPairLogger(src, dst) + + // query channel upgrade pair at latest finalized heights + srcChanUpgErrF, dstChanUpgErrF, err := QueryChannelUpgradeErrorPair( + srcCtxFinalized, + dstCtxFinalized, + src, + dst, + true, + ) + if err != nil { + logger.Error("failed to query a channel upgrade error pair at the latest finalized heights", err) + return nil, nil, false, err + } + + // query channel upgrade pair at latest heights + srcChanUpgErrL, dstChanUpgErrL, err := QueryChannelUpgradeErrorPair( + srcCtxFinalized, + dstCtxFinalized, + src, + dst, + false, + ) + if err != nil { + logger.Error("failed to query a channel upgrade error pair at the latest heights", err) + return nil, nil, false, err + } + + if !compareUpgradeErrors(srcChanUpgErrF, srcChanUpgErrL) { + logger.Debug("channel upgrade error is not finalized on src chain") + return nil, nil, false, nil + } + if !compareUpgradeErrors(dstChanUpgErrF, dstChanUpgErrL) { + logger.Debug("channel upgrade error is not finalized on dst chain") + return nil, nil, false, nil + } + + return srcChanUpgErrF, dstChanUpgErrF, true, nil +} + +func compareUpgradeErrors(a, b *chantypes.QueryUpgradeErrorResponse) bool { + if a == nil { + return b == nil + } + if b == nil { + return false + } + return a.ErrorReceipt.String() == b.ErrorReceipt.String() +} + +func upgradeAlreadyComplete( + channel *chantypes.QueryChannelResponse, + cpCtx QueryContext, + cp *ProvableChain, + cpChanUpg *chantypes.QueryUpgradeResponse, +) (bool, error) { + proposedConnectionID, err := queryProposedConnectionID(cpCtx, cp, cpChanUpg) + if err != nil { + return false, err + } + result := channel.Channel.Version == cpChanUpg.Upgrade.Fields.Version && + channel.Channel.Ordering == cpChanUpg.Upgrade.Fields.Ordering && + channel.Channel.ConnectionHops[0] == proposedConnectionID + return result, nil +} + +func upgradeAlreadyTimedOut( + ctx QueryContext, + chain *ProvableChain, + cpChanUpg *chantypes.QueryUpgradeResponse, +) (bool, error) { + height := ctx.Height().(clienttypes.Height) + timestamp, err := chain.Timestamp(height) + if err != nil { + return false, err + } + return cpChanUpg.Upgrade.Timeout.Elapsed(height, uint64(timestamp.UnixNano())), nil +} diff --git a/core/query.go b/core/query.go index 674b096e..0c1164b3 100644 --- a/core/query.go +++ b/core/query.go @@ -238,3 +238,113 @@ func QueryChannelPair(srcCtx, dstCtx QueryContext, src, dst interface { err = eg.Wait() return } + +func QueryChannelUpgradePair(srcCtx, dstCtx QueryContext, src, dst interface { + Chain + StateProver +}, prove bool) (srcChanUpg, dstChanUpg *chantypes.QueryUpgradeResponse, err error) { + eg := new(errgroup.Group) + + // get channel upgrade from src chain + eg.Go(func() error { + var err error + srcChanUpg, err = src.QueryChannelUpgrade(srcCtx) + if err != nil { + return err + } else if srcChanUpg == nil { + return nil + } + + if !prove { + return nil + } + + if value, err := src.Codec().Marshal(&srcChanUpg.Upgrade); err != nil { + return err + } else { + path := host.ChannelUpgradePath(src.Path().PortID, src.Path().ChannelID) + srcChanUpg.Proof, srcChanUpg.ProofHeight, err = src.ProveState(srcCtx, path, value) + return err + } + }) + + // get channel upgrade from dst chain + eg.Go(func() error { + var err error + dstChanUpg, err = dst.QueryChannelUpgrade(dstCtx) + if err != nil { + return err + } else if dstChanUpg == nil { + return nil + } + + if !prove { + return nil + } + + if value, err := dst.Codec().Marshal(&dstChanUpg.Upgrade); err != nil { + return err + } else { + path := host.ChannelUpgradePath(dst.Path().PortID, dst.Path().ChannelID) + dstChanUpg.Proof, dstChanUpg.ProofHeight, err = dst.ProveState(dstCtx, path, value) + return err + } + }) + err = eg.Wait() + return +} + +func QueryChannelUpgradeErrorPair(srcCtx, dstCtx QueryContext, src, dst interface { + Chain + StateProver +}, prove bool) (srcChanUpgErr, dstChanUpgErr *chantypes.QueryUpgradeErrorResponse, err error) { + eg := new(errgroup.Group) + + // get channel upgrade from src chain + eg.Go(func() error { + var err error + srcChanUpgErr, err = src.QueryChannelUpgradeError(srcCtx) + if err != nil { + return err + } else if srcChanUpgErr == nil { + return nil + } + + if !prove { + return nil + } + + if value, err := src.Codec().Marshal(&srcChanUpgErr.ErrorReceipt); err != nil { + return err + } else { + path := host.ChannelUpgradeErrorPath(src.Path().PortID, src.Path().ChannelID) + srcChanUpgErr.Proof, srcChanUpgErr.ProofHeight, err = src.ProveState(srcCtx, path, value) + return err + } + }) + + // get channel upgrade from dst chain + eg.Go(func() error { + var err error + dstChanUpgErr, err = dst.QueryChannelUpgradeError(dstCtx) + if err != nil { + return err + } else if dstChanUpgErr == nil { + return nil + } + + if !prove { + return nil + } + + if value, err := dst.Codec().Marshal(&dstChanUpgErr.ErrorReceipt); err != nil { + return err + } else { + path := host.ChannelUpgradeErrorPath(dst.Path().PortID, dst.Path().ChannelID) + dstChanUpgErr.Proof, dstChanUpgErr.ProofHeight, err = dst.ProveState(dstCtx, path, value) + return err + } + }) + err = eg.Wait() + return +} From 1b740b044c89bf09ad20bb32e3fd5a6eddc5a298 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 26 Jun 2024 10:49:58 +0900 Subject: [PATCH 07/47] fix simapp to use the relayer account as authority for IBC Signed-off-by: Masanori Yoshida --- tests/chains/tendermint/Dockerfile | 2 ++ tests/chains/tendermint/scripts/entrypoint.sh | 2 ++ tests/chains/tendermint/simapp/app.go | 6 +++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/chains/tendermint/Dockerfile b/tests/chains/tendermint/Dockerfile index a97b4869..7d1cbaa5 100644 --- a/tests/chains/tendermint/Dockerfile +++ b/tests/chains/tendermint/Dockerfile @@ -28,6 +28,8 @@ RUN ./scripts/tm-chain simd $CHAINID $CHAINDIR $RPCPORT $P2PPORT $PROFPORT $GRPC FROM alpine:${ALPINE_VER} +RUN apk add jq + WORKDIR /root ARG CHAINID diff --git a/tests/chains/tendermint/scripts/entrypoint.sh b/tests/chains/tendermint/scripts/entrypoint.sh index eaf13871..b34f52e0 100755 --- a/tests/chains/tendermint/scripts/entrypoint.sh +++ b/tests/chains/tendermint/scripts/entrypoint.sh @@ -1,3 +1,5 @@ #!/bin/sh +export IBC_AUTHORITY=`jq -r .address /root/${CHAINDIR}/${CHAINID}/key_seed.json` + simd --home /root/${CHAINDIR}/${CHAINID} start --pruning=nothing --grpc.address="0.0.0.0:${GRPCPORT}" diff --git a/tests/chains/tendermint/simapp/app.go b/tests/chains/tendermint/simapp/app.go index 31565db7..7eadff7b 100644 --- a/tests/chains/tendermint/simapp/app.go +++ b/tests/chains/tendermint/simapp/app.go @@ -411,8 +411,12 @@ func NewSimApp( // set the governance module account as the authority for conducting upgrades app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, runtime.NewKVStoreService(keys[upgradetypes.StoreKey]), appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + ibcAuthority := authtypes.NewModuleAddress(govtypes.ModuleName).String() + if authority, found := os.LookupEnv("IBC_AUTHORITY"); found { + ibcAuthority = authority + } app.IBCKeeper = ibckeeper.NewKeeper( - appCodec, keys[ibcexported.StoreKey], app.GetSubspace(ibcexported.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, keys[ibcexported.StoreKey], app.GetSubspace(ibcexported.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, ibcAuthority, ) if _, found := os.LookupEnv("USE_MOCK_CLIENT"); found { // this is a workaround in case the counterparty chain uses mock-client From cb90c15051dedc24b81a6a6eeb958c0528b965c6 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 5 Jul 2024 15:25:02 +0900 Subject: [PATCH 08/47] add `upgradeSequence` parameter to the parameter list of `Chain::QueryChannelUpgradeError` Signed-off-by: Masanori Yoshida --- chains/tendermint/query.go | 37 +++++++++-- core/chain.go | 4 +- core/channel-upgrade.go | 122 +++++++++++-------------------------- core/query.go | 55 ----------------- 4 files changed, 71 insertions(+), 147 deletions(-) diff --git a/chains/tendermint/query.go b/chains/tendermint/query.go index 80f8c45f..3d9fab4b 100644 --- a/chains/tendermint/query.go +++ b/chains/tendermint/query.go @@ -356,11 +356,13 @@ func (c *Chain) queryWrittenAcknowledgement(ctx core.QueryContext, seq uint64) ( } // QueryTxs returns an array of transactions given a tag -func (c *Chain) QueryTxs(height int64, page, limit int, events []string) ([]*ctypes.ResultTx, error) { +func (c *Chain) QueryTxs(maxHeight int64, page, limit int, events []string) ([]*ctypes.ResultTx, error) { if len(events) == 0 { return nil, errors.New("must declare at least one event to search") } + events = append(events, fmt.Sprintf("tx.height<=%d", maxHeight)) + if page <= 0 { return nil, errors.New("page must greater than 0") } @@ -397,13 +399,23 @@ func (c *Chain) queryChannelUpgrade(height int64, prove bool) (chanRes *chantype } } -func (c *Chain) QueryChannelUpgradeError(ctx core.QueryContext) (*chantypes.QueryUpgradeErrorResponse, error) { - return c.queryChannelUpgradeError(int64(ctx.Height().GetRevisionHeight()), false) +func (c *Chain) QueryChannelUpgradeError(ctx core.QueryContext, upgradeSequence uint64) (*chantypes.QueryUpgradeErrorResponse, error) { + return c.queryChannelUpgradeError(int64(ctx.Height().GetRevisionHeight()), upgradeSequence, false) } -func (c *Chain) queryChannelUpgradeError(height int64, prove bool) (chanRes *chantypes.QueryUpgradeErrorResponse, err error) { +func (c *Chain) queryChannelUpgradeError(maxHeight int64, upgradeSequence uint64, prove bool) (chanRes *chantypes.QueryUpgradeErrorResponse, err error) { + txs, err := c.QueryTxs(maxHeight, 1, 2, channelUpgradeErrorQuery(c.Path().ChannelID, upgradeSequence)) + switch { + case err != nil: + return nil, err + case len(txs) == 0: + return nil, fmt.Errorf("no transactions returned with query") + case len(txs) > 1: + return nil, fmt.Errorf("more than one transaction returned with query") + } + if res, err := chanutils.QueryUpgradeError( - c.CLIContext(height), + c.CLIContext(txs[0].Height), c.PathEnd.PortID, c.PathEnd.ChannelID, prove, @@ -531,3 +543,18 @@ func recvPacketQuery(channelID string, seq int) []string { func writeAckQuery(channelID string, seq int) []string { return []string{fmt.Sprintf("%s.packet_dst_channel='%s'", waTag, channelID), fmt.Sprintf("%s.packet_sequence='%d'", waTag, seq)} } + +func channelUpgradeErrorQuery(channelID string, upgradeSequence uint64) []string { + return []string{ + fmt.Sprintf("%s.%s='%s'", + chantypes.EventTypeChannelUpgradeError, + chantypes.AttributeKeyChannelID, + channelID, + ), + fmt.Sprintf("%s.%s='%d'", + chantypes.EventTypeChannelUpgradeError, + chantypes.AttributeKeyUpgradeSequence, + upgradeSequence, + ), + } +} diff --git a/core/chain.go b/core/chain.go index 3ef0e7bf..9981e230 100644 --- a/core/chain.go +++ b/core/chain.go @@ -159,8 +159,8 @@ type ICS04Querier interface { // QueryChannelUpgrade returns the channel upgrade associated with a channelID QueryChannelUpgrade(ctx QueryContext) (*chantypes.QueryUpgradeResponse, error) - // QueryChannelUpgradeError returns the channel upgrade error receipt associated with a channelID - QueryChannelUpgradeError(ctx QueryContext) (*chantypes.QueryUpgradeErrorResponse, error) + // QueryChannelUpgradeError iterates through chain events in reverse chronological order and returns the error receipt that matches `upgradeSequence`. + QueryChannelUpgradeError(ctx QueryContext, upgradeSequence uint64) (*chantypes.QueryUpgradeErrorResponse, error) // QueryCanTransitionToFlushComplete returns the channel can transition to FLUSHCOMPLETE state. // Basically it requires that there remains no inflight packets. diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index b453ede4..bab33350 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -181,21 +181,6 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { dstState = UPGRADEINIT } - // query finalized channel upgrade error receipts with proofs - srcChanUpgErr, dstChanUpgErr, finalized, err := queryFinalizedChannelUpgradeErrorPair( - srcCtxFinalized, - dstCtxFinalized, - srcCtxLatest, - dstCtxLatest, - src, - dst, - ) - if err != nil { - return nil, err - } else if !finalized { - return out, nil - } - doTry := func(chain *ProvableChain, cpCtx QueryContext, cp *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse, cpChanUpg *chantypes.QueryUpgradeResponse) ([]sdk.Msg, error) { proposedConnectionID, err := queryProposedConnectionID(cpCtx, cp, cpChanUpg) if err != nil { @@ -240,14 +225,19 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { return msgs } - doCancel := func(chain *ProvableChain, cpHeaders []Header, cpChanUpgErr *chantypes.QueryUpgradeErrorResponse) []sdk.Msg { + doCancel := func(chain *ProvableChain, cpCtx QueryContext, cp *ProvableChain, cpHeaders []Header, upgradeSequence uint64) ([]sdk.Msg, error) { + cpChanUpgErr, err := cp.QueryChannelUpgradeError(cpCtx, upgradeSequence) + if err != nil { + return nil, err + } + var msgs []sdk.Msg addr := mustGetAddress(chain) if len(cpHeaders) > 0 { msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) } msgs = append(msgs, chain.Path().ChanUpgradeCancel(cpChanUpgErr, addr)) - return msgs + return msgs, nil } doTimeout := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse) []sdk.Msg { @@ -265,7 +255,9 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { return nil, errors.New("channel upgrade is not initialized") case srcState == UPGRADEINIT && dstState == UPGRADEUNINIT: if dstChan.Channel.UpgradeSequence >= srcChan.Channel.UpgradeSequence { - out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { + return nil, err + } } else { if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { return nil, err @@ -273,16 +265,22 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } case srcState == UPGRADEUNINIT && dstState == UPGRADEINIT: if srcChan.Channel.UpgradeSequence >= dstChan.Channel.UpgradeSequence { - out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { + return nil, err + } } else { if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { return nil, err } } case srcState == UPGRADEUNINIT && dstState == FLUSHING: - out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { + return nil, err + } case srcState == FLUSHING && dstState == UPGRADEUNINIT: - out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { + return nil, err + } case srcState == UPGRADEUNINIT && dstState == FLUSHCOMPLETE: if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { return nil, err @@ -293,7 +291,9 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } else if timedout { out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) } else { - out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { + return nil, err + } } case srcState == FLUSHCOMPLETE && dstState == UPGRADEUNINIT: if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { @@ -305,7 +305,9 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } else if timedout { out.Src = doTimeout(src, dstUpdateHeaders, dstChan) } else { - out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { + return nil, err + } } case srcState == UPGRADEINIT && dstState == UPGRADEINIT: // crossing hellos // it is intentional to execute chanUpgradeTry on both sides if upgrade sequences @@ -322,7 +324,9 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } case srcState == UPGRADEINIT && dstState == FLUSHING: if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { - out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence-1); err != nil { + return nil, err + } } else { // chanUpgradeAck checks if counterparty-specified timeout has exceeded. // if it has, chanUpgradeAck aborts the upgrade handshake. @@ -331,7 +335,9 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } case srcState == FLUSHING && dstState == UPGRADEINIT: if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { - out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence-1); err != nil { + return nil, err + } } else { // chanUpgradeAck checks if counterparty-specified timeout has exceeded. // if it has, chanUpgradeAck aborts the upgrade handshake. @@ -348,7 +354,9 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } else if timedout { out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) } else { - out.Dst = doCancel(dst, srcUpdateHeaders, srcChanUpgErr) + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence-1); err != nil { + return nil, err + } } case srcState == FLUSHCOMPLETE && dstState == UPGRADEINIT: if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { @@ -360,7 +368,9 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } else if timedout { out.Src = doTimeout(src, dstUpdateHeaders, dstChan) } else { - out.Src = doCancel(src, dstUpdateHeaders, dstChanUpgErr) + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence-1); err != nil { + return nil, err + } } case srcState == FLUSHING && dstState == FLUSHING: nTimedout := 0 @@ -491,64 +501,6 @@ func compareUpgrades(a, b *chantypes.QueryUpgradeResponse) bool { return a.Upgrade.String() == b.Upgrade.String() } -func queryFinalizedChannelUpgradeErrorPair( - srcCtxFinalized, - dstCtxFinalized, - srcCtxLatest, - dstCtxLatest QueryContext, - src, - dst *ProvableChain, -) (srcChanUpg, dstChanUpg *chantypes.QueryUpgradeErrorResponse, finalized bool, err error) { - logger := GetChannelPairLogger(src, dst) - - // query channel upgrade pair at latest finalized heights - srcChanUpgErrF, dstChanUpgErrF, err := QueryChannelUpgradeErrorPair( - srcCtxFinalized, - dstCtxFinalized, - src, - dst, - true, - ) - if err != nil { - logger.Error("failed to query a channel upgrade error pair at the latest finalized heights", err) - return nil, nil, false, err - } - - // query channel upgrade pair at latest heights - srcChanUpgErrL, dstChanUpgErrL, err := QueryChannelUpgradeErrorPair( - srcCtxFinalized, - dstCtxFinalized, - src, - dst, - false, - ) - if err != nil { - logger.Error("failed to query a channel upgrade error pair at the latest heights", err) - return nil, nil, false, err - } - - if !compareUpgradeErrors(srcChanUpgErrF, srcChanUpgErrL) { - logger.Debug("channel upgrade error is not finalized on src chain") - return nil, nil, false, nil - } - if !compareUpgradeErrors(dstChanUpgErrF, dstChanUpgErrL) { - logger.Debug("channel upgrade error is not finalized on dst chain") - return nil, nil, false, nil - } - - return srcChanUpgErrF, dstChanUpgErrF, true, nil -} - -func compareUpgradeErrors(a, b *chantypes.QueryUpgradeErrorResponse) bool { - if a == nil { - return b == nil - } - if b == nil { - return false - } - return a.ErrorReceipt.String() == b.ErrorReceipt.String() -} - func upgradeAlreadyComplete( channel *chantypes.QueryChannelResponse, cpCtx QueryContext, diff --git a/core/query.go b/core/query.go index 0c1164b3..01982234 100644 --- a/core/query.go +++ b/core/query.go @@ -293,58 +293,3 @@ func QueryChannelUpgradePair(srcCtx, dstCtx QueryContext, src, dst interface { err = eg.Wait() return } - -func QueryChannelUpgradeErrorPair(srcCtx, dstCtx QueryContext, src, dst interface { - Chain - StateProver -}, prove bool) (srcChanUpgErr, dstChanUpgErr *chantypes.QueryUpgradeErrorResponse, err error) { - eg := new(errgroup.Group) - - // get channel upgrade from src chain - eg.Go(func() error { - var err error - srcChanUpgErr, err = src.QueryChannelUpgradeError(srcCtx) - if err != nil { - return err - } else if srcChanUpgErr == nil { - return nil - } - - if !prove { - return nil - } - - if value, err := src.Codec().Marshal(&srcChanUpgErr.ErrorReceipt); err != nil { - return err - } else { - path := host.ChannelUpgradeErrorPath(src.Path().PortID, src.Path().ChannelID) - srcChanUpgErr.Proof, srcChanUpgErr.ProofHeight, err = src.ProveState(srcCtx, path, value) - return err - } - }) - - // get channel upgrade from dst chain - eg.Go(func() error { - var err error - dstChanUpgErr, err = dst.QueryChannelUpgradeError(dstCtx) - if err != nil { - return err - } else if dstChanUpgErr == nil { - return nil - } - - if !prove { - return nil - } - - if value, err := dst.Codec().Marshal(&dstChanUpgErr.ErrorReceipt); err != nil { - return err - } else { - path := host.ChannelUpgradeErrorPath(dst.Path().PortID, dst.Path().ChannelID) - dstChanUpgErr.Proof, dstChanUpgErr.ProofHeight, err = dst.ProveState(dstCtx, path, value) - return err - } - }) - err = eg.Wait() - return -} From 1cb6e4b88e796e5335527ed18a3c57ad4485e039 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 5 Jul 2024 21:01:00 +0900 Subject: [PATCH 09/47] fix the "tx channel-upgrade execute" command to gracefully exit after successful operation Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index bab33350..fc7fee2f 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -258,6 +258,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { return nil, err } + out.Last = true } else { if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { return nil, err @@ -268,6 +269,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { return nil, err } + out.Last = true } else { if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { return nil, err @@ -277,10 +279,12 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { return nil, err } + out.Last = true case srcState == FLUSHING && dstState == UPGRADEUNINIT: if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { return nil, err } + out.Last = true case srcState == UPGRADEUNINIT && dstState == FLUSHCOMPLETE: if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { return nil, err @@ -295,6 +299,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { return nil, err } } + out.Last = true case srcState == FLUSHCOMPLETE && dstState == UPGRADEUNINIT: if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { return nil, err @@ -309,6 +314,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { return nil, err } } + out.Last = true case srcState == UPGRADEINIT && dstState == UPGRADEINIT: // crossing hellos // it is intentional to execute chanUpgradeTry on both sides if upgrade sequences // are identical to each other. this is for testing purpose. @@ -425,6 +431,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { case srcState == FLUSHCOMPLETE && dstState == FLUSHCOMPLETE: out.Src = doOpen(src, dstUpdateHeaders, dstChan) out.Dst = doOpen(dst, srcUpdateHeaders, srcChan) + out.Last = true default: return nil, errors.New("unexpected state") } From 600f614f0782eae3485a34fa664b093cc6060f3d Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 5 Jul 2024 21:04:09 +0900 Subject: [PATCH 10/47] add test-channel-upgrade to tm2tm Signed-off-by: Masanori Yoshida --- tests/cases/tm2tm/Makefile | 1 + .../cases/tm2tm/scripts/test-channel-upgrade | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100755 tests/cases/tm2tm/scripts/test-channel-upgrade diff --git a/tests/cases/tm2tm/Makefile b/tests/cases/tm2tm/Makefile index fd5f539d..7f853af5 100644 --- a/tests/cases/tm2tm/Makefile +++ b/tests/cases/tm2tm/Makefile @@ -12,6 +12,7 @@ test: ./scripts/fixture ./scripts/init-rly ./scripts/handshake + ./scripts/test-channel-upgrade ./scripts/test-create-client-success-single ./scripts/test-create-client-fail-already-created ./scripts/test-create-client-fail-unexist diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade new file mode 100755 index 00000000..d760c694 --- /dev/null +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -0,0 +1,44 @@ +#!/bin/bash + +set -eux + +source $(cd $(dirname "$0"); pwd)/../../../scripts/util + +SCRIPT_DIR=$(cd $(dirname $0); pwd) +RELAYER_CONF="$HOME/.yui-relayer" +RLY_BINARY=${SCRIPT_DIR}/../../../../build/yrly +RLY="${RLY_BINARY} --debug" + +CHAINID_ONE=ibc0 +CHAINID_TWO=ibc1 +RLYKEY=testkey +PATH_NAME=ibc01 + +# back up the original connection identifiers +srcOrigConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') +dstOrigConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') + +# back up the original config.json and make connection identifiers empty +origconfig=`mktemp` +cp "$RELAYER_CONF/config/config.json" $origconfig +$RLY paths edit $PATH_NAME src connection-id '' +$RLY paths edit $PATH_NAME dst connection-id '' + +# create a new connection and save the new connection identifiers +retry 5 $RLY tx connection $PATH_NAME +srcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') +dstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') + +# resume the original config.json +mv $origconfig "$RELAYER_CONF/config/config.json" + +# test channel upgrade (crossing-hello) +$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstConnectionId --version ics20-1 +$RLY tx channel-upgrade execute ibc01 + +# test channel upgrade (non-crossing-hello) +$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstOrigConnectionId --version ics20-1 +$RLY tx channel-upgrade execute ibc01 From 4604b20b47842f30f51b8a59fb0f19be709e033a Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 5 Jul 2024 22:22:56 +0900 Subject: [PATCH 11/47] fix Chain::QueryChannelUpgradeError to return the latest error receipt if 0 is given as upgrade sequence parameter Signed-off-by: Masanori Yoshida --- chains/tendermint/query.go | 26 ++++++++++++++++---------- core/chain.go | 3 ++- core/channel-upgrade.go | 20 ++++++++++---------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/chains/tendermint/query.go b/chains/tendermint/query.go index 3d9fab4b..4ce8bbd5 100644 --- a/chains/tendermint/query.go +++ b/chains/tendermint/query.go @@ -403,19 +403,25 @@ func (c *Chain) QueryChannelUpgradeError(ctx core.QueryContext, upgradeSequence return c.queryChannelUpgradeError(int64(ctx.Height().GetRevisionHeight()), upgradeSequence, false) } -func (c *Chain) queryChannelUpgradeError(maxHeight int64, upgradeSequence uint64, prove bool) (chanRes *chantypes.QueryUpgradeErrorResponse, err error) { - txs, err := c.QueryTxs(maxHeight, 1, 2, channelUpgradeErrorQuery(c.Path().ChannelID, upgradeSequence)) - switch { - case err != nil: - return nil, err - case len(txs) == 0: - return nil, fmt.Errorf("no transactions returned with query") - case len(txs) > 1: - return nil, fmt.Errorf("more than one transaction returned with query") +func (c *Chain) queryChannelUpgradeError(height int64, upgradeSequence uint64, prove bool) (chanRes *chantypes.QueryUpgradeErrorResponse, err error) { + var errReceiptHeight int64 + if upgradeSequence == 0 { + errReceiptHeight = height + } else { + txs, err := c.QueryTxs(height, 1, 2, channelUpgradeErrorQuery(c.Path().ChannelID, upgradeSequence)) + switch { + case err != nil: + return nil, err + case len(txs) == 0: + return nil, fmt.Errorf("no transactions returned with query") + case len(txs) > 1: + return nil, fmt.Errorf("more than one transaction returned with query") + } + errReceiptHeight = txs[0].Height } if res, err := chanutils.QueryUpgradeError( - c.CLIContext(txs[0].Height), + c.CLIContext(errReceiptHeight), c.PathEnd.PortID, c.PathEnd.ChannelID, prove, diff --git a/core/chain.go b/core/chain.go index 9981e230..568873d3 100644 --- a/core/chain.go +++ b/core/chain.go @@ -159,7 +159,8 @@ type ICS04Querier interface { // QueryChannelUpgrade returns the channel upgrade associated with a channelID QueryChannelUpgrade(ctx QueryContext) (*chantypes.QueryUpgradeResponse, error) - // QueryChannelUpgradeError iterates through chain events in reverse chronological order and returns the error receipt that matches `upgradeSequence`. + // QueryChannelUpgradeError iterates through chain events in reverse chronological order from the height of `ctx` and returns the error receipt that matches `upgradeSequence`. + // If zero is specified as `upgradeSequence`, this function simply returns the error receipt stored at the height of `ctx`. QueryChannelUpgradeError(ctx QueryContext, upgradeSequence uint64) (*chantypes.QueryUpgradeErrorResponse, error) // QueryCanTransitionToFlushComplete returns the channel can transition to FLUSHCOMPLETE state. diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index fc7fee2f..fe66758b 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -255,7 +255,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { return nil, errors.New("channel upgrade is not initialized") case srcState == UPGRADEINIT && dstState == UPGRADEUNINIT: if dstChan.Channel.UpgradeSequence >= srcChan.Channel.UpgradeSequence { - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, 0); err != nil { return nil, err } out.Last = true @@ -266,7 +266,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } case srcState == UPGRADEUNINIT && dstState == UPGRADEINIT: if srcChan.Channel.UpgradeSequence >= dstChan.Channel.UpgradeSequence { - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, 0); err != nil { return nil, err } out.Last = true @@ -276,12 +276,12 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } } case srcState == UPGRADEUNINIT && dstState == FLUSHING: - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, 0); err != nil { return nil, err } out.Last = true case srcState == FLUSHING && dstState == UPGRADEUNINIT: - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, 0); err != nil { return nil, err } out.Last = true @@ -295,7 +295,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } else if timedout { out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) } else { - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { return nil, err } } @@ -310,7 +310,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } else if timedout { out.Src = doTimeout(src, dstUpdateHeaders, dstChan) } else { - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { return nil, err } } @@ -330,7 +330,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } case srcState == UPGRADEINIT && dstState == FLUSHING: if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence-1); err != nil { + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, 0); err != nil { return nil, err } } else { @@ -341,7 +341,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } case srcState == FLUSHING && dstState == UPGRADEINIT: if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence-1); err != nil { + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, 0); err != nil { return nil, err } } else { @@ -360,7 +360,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } else if timedout { out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) } else { - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan.Channel.UpgradeSequence-1); err != nil { + if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { return nil, err } } @@ -374,7 +374,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } else if timedout { out.Src = doTimeout(src, dstUpdateHeaders, dstChan) } else { - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan.Channel.UpgradeSequence-1); err != nil { + if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { return nil, err } } From 20e8dd19d57b7f0831d11b4b06dd5ad756a5a17a Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 8 Jul 2024 21:39:37 +0900 Subject: [PATCH 12/47] add core.QueryChannelUpgradeError to query an error receipt with proof Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 2 +- core/query.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index fe66758b..32eae354 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -226,7 +226,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } doCancel := func(chain *ProvableChain, cpCtx QueryContext, cp *ProvableChain, cpHeaders []Header, upgradeSequence uint64) ([]sdk.Msg, error) { - cpChanUpgErr, err := cp.QueryChannelUpgradeError(cpCtx, upgradeSequence) + cpChanUpgErr, err := QueryChannelUpgradeError(cpCtx, cp, upgradeSequence, true) if err != nil { return nil, err } diff --git a/core/query.go b/core/query.go index 01982234..e14919b0 100644 --- a/core/query.go +++ b/core/query.go @@ -293,3 +293,21 @@ func QueryChannelUpgradePair(srcCtx, dstCtx QueryContext, src, dst interface { err = eg.Wait() return } + +func QueryChannelUpgradeError(ctx QueryContext, chain interface { + Chain + StateProver +}, upgradeSequence uint64, prove bool) (*chantypes.QueryUpgradeErrorResponse, error) { + if chanUpgErr, err := chain.QueryChannelUpgradeError(ctx, upgradeSequence); err != nil { + return nil, err + } else if !prove { + return chanUpgErr, nil + } else if value, err := chain.Codec().Marshal(&chanUpgErr.ErrorReceipt); err != nil { + return nil, err + } else { + proveCtx := NewQueryContext(ctx.Context(), chanUpgErr.ProofHeight) + path := host.ChannelUpgradeErrorPath(chain.Path().PortID, chain.Path().ChannelID) + chanUpgErr.Proof, chanUpgErr.ProofHeight, err = chain.ProveState(proveCtx, path, value) + return chanUpgErr, err + } +} From cc2e1c96934b0fd06c0ee119c1e1d7f2c32512f0 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 8 Jul 2024 21:42:44 +0900 Subject: [PATCH 13/47] add `tx channel-upgrade cancel` command Signed-off-by: Masanori Yoshida --- cmd/tx.go | 43 +++++++++++++++++++++++++-- core/channel-upgrade.go | 65 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/cmd/tx.go b/cmd/tx.go index 14990494..98b9ed10 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -211,7 +211,7 @@ func channelUpgradeCmd(ctx *config.Context) *cobra.Command { cmd.AddCommand( channelUpgradeInitCmd(ctx), channelUpgradeExecuteCmd(ctx), - //channelUpgradeCancel(ctx), + channelUpgradeCancelCmd(ctx), ) return cmd @@ -226,7 +226,7 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { cmd := cobra.Command{ Use: "init [path-name] [chain-id]", - Short: "execute chanOpenInit", + Short: "execute chanUpgradeInit", Long: "This command is meant to be used to initialize an IBC channel upgrade on a configured chain", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { @@ -321,6 +321,45 @@ func channelUpgradeExecuteCmd(ctx *config.Context) *cobra.Command { return &cmd } +func channelUpgradeCancelCmd(ctx *config.Context) *cobra.Command { + return &cobra.Command{ + Use: "cancel [path-name] [chain-id]", + Short: "execute chanUpgradeCancel", + Long: "This command is meant to be used to cancel an IBC channel upgrade on a configured chain", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + pathName := args[0] + chainID := args[1] + + _, srcChainID, dstChainID, err := ctx.Config.ChainsFromPath(pathName) + if err != nil { + return err + } + + var cpChainID string + switch { + case chainID == srcChainID: + cpChainID = dstChainID + case chainID == dstChainID: + cpChainID = srcChainID + default: + return fmt.Errorf("invalid chain ID: %s or %s was expected, but %s was given", srcChainID, dstChainID, chainID) + } + + chain, err := ctx.Config.GetChain(chainID) + if err != nil { + return err + } + cp, err := ctx.Config.GetChain(cpChainID) + if err != nil { + return err + } + + return core.CancelChannelUpgrade(chain, cp) + }, + } +} + func relayMsgsCmd(ctx *config.Context) *cobra.Command { const ( flagDoRefresh = "do-refresh" diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 32eae354..fa935a17 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -32,7 +32,7 @@ func InitChannelUpgrade(chain *ProvableChain, upgradeFields chantypes.UpgradeFie } else if len(msgIDs) != 1 { panic(fmt.Sprintf("len(msgIDs) == %d", len(msgIDs))) } else if result, err := GetFinalizedMsgResult(*chain, msgIDs[0]); err != nil { - logger.Error("failed to the finalized result of MsgChannelUpgradeInit", err) + logger.Error("failed to get the finalized result of MsgChannelUpgradeInit", err) return err } else if ok, desc := result.Status(); !ok { err := fmt.Errorf("failed to initialize channel upgrade: %s", desc) @@ -88,6 +88,69 @@ func ExecuteChannelUpgrade(src, dst *ProvableChain, interval time.Duration) erro } } +// CancelChannelUpgrade executes chanUpgradeCancel on `chain`. +func CancelChannelUpgrade(chain, cp *ProvableChain) error { + logger := GetChannelPairLogger(chain, cp) + defer logger.TimeTrack(time.Now(), "CancelChannelUpgrade") + + addr, err := chain.GetAddress() + if err != nil { + logger.Error("failed to get address", err) + return err + } + + var ctx, cpCtx QueryContext + if sh, err := NewSyncHeaders(chain, cp); err != nil { + logger.Error("failed to create a SyncHeaders", err) + return err + } else { + ctx = sh.GetQueryContext(chain.ChainID()) + cpCtx = sh.GetQueryContext(cp.ChainID()) + } + + chann, err := chain.QueryChannel(ctx) + if err != nil { + logger.Error("failed to get the channel state", err) + return err + } + + var errReceiptSequence uint64 = 0 + if chann.Channel.State == chantypes.FLUSHCOMPLETE { + errReceiptSequence = chann.Channel.UpgradeSequence + } + + var msg sdk.Msg + if upgErr, err := cp.QueryChannelUpgradeError(cpCtx, errReceiptSequence); err != nil { + logger.Error("failed to query the channel upgrade error receipt", err) + return err + } else if upgErr == nil { + // NOTE: Even if an error receipt is not found, anyway try to execute ChanUpgradeCancel. + // If the sender is authority and the channel state is anything other than FLUSHCOMPLETE, + // the cancellation will be successful. + msg = chain.Path().ChanUpgradeCancel(&chantypes.QueryUpgradeErrorResponse{}, addr) + } else { + msg = chain.Path().ChanUpgradeCancel(upgErr, addr) + } + + msgIDs, err := chain.SendMsgs([]sdk.Msg{msg}) + if err != nil { + logger.Error("failed to send MsgChannelUpgradeCancel", err) + return err + } else if len(msgIDs) != 1 { + panic(fmt.Sprintf("len(msgIDs) == %d", len(msgIDs))) + } else if result, err := GetFinalizedMsgResult(*chain, msgIDs[0]); err != nil { + logger.Error("failed to get the finalized result of MsgChannelUpgradeCancel", err) + return err + } else if ok, desc := result.Status(); !ok { + err := fmt.Errorf("failed to cancel the channel upgrade: %s", desc) + logger.Error(err.Error(), err) + } else { + logger.Info("successfully cancelled the channel upgrade") + } + + return nil +} + func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { logger := GetChannelPairLogger(src, dst) From f8b4da7a2d52e23316a33fff076698f4bf287e65 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 8 Jul 2024 22:33:24 +0900 Subject: [PATCH 14/47] add a case for testing chanUpgradeCancel to the test-channel-upgrade script Signed-off-by: Masanori Yoshida --- tests/cases/tm2tm/scripts/test-channel-upgrade | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade index d760c694..7a7ed6f3 100755 --- a/tests/cases/tm2tm/scripts/test-channel-upgrade +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -42,3 +42,9 @@ $RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection- $RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 $RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstOrigConnectionId --version ics20-1 $RLY tx channel-upgrade execute ibc01 + +# test channel upgrade cancel +$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstConnectionId --version ics20-1 +$RLY tx channel-upgrade cancel ibc01 ibc0 # create situation where ibc0.error_receipt.sequence >= ibc1.channel.upgrade_sequence +$RLY tx channel-upgrade execute ibc01 # the channel upgrade of ibc1 should be cancelled From d6aece2c9b7c2240ce68bac23d1558d4e906c559 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 12 Jul 2024 22:44:32 +0900 Subject: [PATCH 15/47] fix core.QueryChannelUpgradeError to return (nil,nil) if no error receipt found Signed-off-by: Masanori Yoshida --- core/query.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/query.go b/core/query.go index e14919b0..41ff4784 100644 --- a/core/query.go +++ b/core/query.go @@ -300,6 +300,8 @@ func QueryChannelUpgradeError(ctx QueryContext, chain interface { }, upgradeSequence uint64, prove bool) (*chantypes.QueryUpgradeErrorResponse, error) { if chanUpgErr, err := chain.QueryChannelUpgradeError(ctx, upgradeSequence); err != nil { return nil, err + } else if chanUpgErr == nil { + return nil, nil } else if !prove { return chanUpgErr, nil } else if value, err := chain.Codec().Marshal(&chanUpgErr.ErrorReceipt); err != nil { From 15ff1b25925cc3b4e2a2d723e7642ca30e21a8d4 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 12 Jul 2024 23:00:25 +0900 Subject: [PATCH 16/47] output a WARN log when cancelling a channel upgrade Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index fa935a17..c51b6935 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -318,6 +318,10 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { return nil, errors.New("channel upgrade is not initialized") case srcState == UPGRADEINIT && dstState == UPGRADEUNINIT: if dstChan.Channel.UpgradeSequence >= srcChan.Channel.UpgradeSequence { + logger.Warn("the initialized channel upgrade is outdated", + "src_seq", srcChan.Channel.UpgradeSequence, + "dst_seq", dstChan.Channel.UpgradeSequence, + ) if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, 0); err != nil { return nil, err } @@ -329,6 +333,10 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } case srcState == UPGRADEUNINIT && dstState == UPGRADEINIT: if srcChan.Channel.UpgradeSequence >= dstChan.Channel.UpgradeSequence { + logger.Warn("the initialized channel upgrade is outdated", + "src_seq", srcChan.Channel.UpgradeSequence, + "dst_seq", dstChan.Channel.UpgradeSequence, + ) if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, 0); err != nil { return nil, err } From 3e589ad5a11ec90fbfd48234e5e581d18e7693f3 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 19 Jul 2024 10:55:24 +0900 Subject: [PATCH 17/47] add the --until-flushing flag to the `tx channel-upgrade execute` subcommand Signed-off-by: Masanori Yoshida --- cmd/tx.go | 11 +++++++++-- core/channel-upgrade.go | 25 +++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/cmd/tx.go b/cmd/tx.go index 98b9ed10..53c95cd0 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -279,7 +279,8 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { func channelUpgradeExecuteCmd(ctx *config.Context) *cobra.Command { const ( - flagInterval = "interval" + flagInterval = "interval" + flagUntilFlushing = "until-flushing" ) const ( @@ -312,11 +313,17 @@ func channelUpgradeExecuteCmd(ctx *config.Context) *cobra.Command { return err } - return core.ExecuteChannelUpgrade(src, dst, interval) + untilFlushing, err := cmd.Flags().GetBool(flagUntilFlushing) + if err != nil { + return err + } + + return core.ExecuteChannelUpgrade(src, dst, interval, untilFlushing) }, } cmd.Flags().Duration(flagInterval, defaultInterval, "interval between attempts to proceed channel upgrade steps") + cmd.Flags().Bool(flagUntilFlushing, false, "the process exits when both chains have started flushing") return &cmd } diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index c51b6935..a2e3666a 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -46,7 +46,7 @@ func InitChannelUpgrade(chain *ProvableChain, upgradeFields chantypes.UpgradeFie // ExecuteChannelUpgrade carries out channel upgrade handshake until both chains transition to the OPEN state. // This function repeatedly checks the states of both chains and decides the next action. -func ExecuteChannelUpgrade(src, dst *ProvableChain, interval time.Duration) error { +func ExecuteChannelUpgrade(src, dst *ProvableChain, interval time.Duration, untilFlushing bool) error { logger := GetChannelPairLogger(src, dst) defer logger.TimeTrack(time.Now(), "ExecuteChannelUpgrade") @@ -55,7 +55,7 @@ func ExecuteChannelUpgrade(src, dst *ProvableChain, interval time.Duration) erro for { <-tick - steps, err := upgradeChannelStep(src, dst) + steps, err := upgradeChannelStep(src, dst, untilFlushing) if err != nil { logger.Error("failed to create channel upgrade step", err) return err @@ -151,7 +151,7 @@ func CancelChannelUpgrade(chain, cp *ProvableChain) error { return nil } -func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { +func upgradeChannelStep(src, dst *ProvableChain, untilFlushing bool) (*RelayMsgs, error) { logger := GetChannelPairLogger(src, dst) out := NewRelayMsgs() @@ -387,17 +387,24 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { } out.Last = true case srcState == UPGRADEINIT && dstState == UPGRADEINIT: // crossing hellos - // it is intentional to execute chanUpgradeTry on both sides if upgrade sequences - // are identical to each other. this is for testing purpose. - if srcChan.Channel.UpgradeSequence >= dstChan.Channel.UpgradeSequence { + if srcChan.Channel.UpgradeSequence > dstChan.Channel.UpgradeSequence { + if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { + return nil, err + } + } else if srcChan.Channel.UpgradeSequence < dstChan.Channel.UpgradeSequence { + if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { + return nil, err + } + } else { + // it is intentional to execute chanUpgradeTry on both sides if upgrade sequences + // are identical to each other. this is for testing purpose. if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { return nil, err } - } - if srcChan.Channel.UpgradeSequence <= dstChan.Channel.UpgradeSequence { if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { return nil, err } + out.Last = untilFlushing } case srcState == UPGRADEINIT && dstState == FLUSHING: if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { @@ -409,6 +416,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { // if it has, chanUpgradeAck aborts the upgrade handshake. // Therefore the relayer need not check timeout by itself. out.Src = doAck(src, dstUpdateHeaders, dstChan, dstChanUpg) + out.Last = untilFlushing } case srcState == FLUSHING && dstState == UPGRADEINIT: if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { @@ -420,6 +428,7 @@ func upgradeChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { // if it has, chanUpgradeAck aborts the upgrade handshake. // Therefore the relayer need not check timeout by itself. out.Dst = doAck(dst, srcUpdateHeaders, srcChan, srcChanUpg) + out.Last = true } case srcState == UPGRADEINIT && dstState == FLUSHCOMPLETE: if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { From 85a2543f6997fd9a6d9c32ded9ba6a7cde1c41f4 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 19 Jul 2024 10:24:15 +0900 Subject: [PATCH 18/47] improve logging in RelayMsgs::Send Signed-off-by: Masanori Yoshida --- core/relayMsgs.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/core/relayMsgs.go b/core/relayMsgs.go index 9081267f..de98b59a 100644 --- a/core/relayMsgs.go +++ b/core/relayMsgs.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/gogoproto/proto" ) @@ -69,10 +71,14 @@ func (r *RelayMsgs) Send(src, dst Chain) { txSize += uint64(len(bz)) if r.IsMaxTx(msgLen, txSize) { + logger := logger.With("msgs", msgsToLoggable(msgs)) + // Submit the transactions to src chain and update its status msgIDs, err := src.SendMsgs(msgs) if err != nil { - logger.Error("failed to send msgs", err, "msgs", msgs) + logger.Error("failed to send msgs", err) + } else { + logger.Info("successfully sent msgs", "side", "src") } r.Succeeded = r.Succeeded && (err == nil) if err == nil { @@ -90,9 +96,13 @@ func (r *RelayMsgs) Send(src, dst Chain) { // submit leftover msgs if len(msgs) > 0 { + logger := logger.With("msgs", msgsToLoggable(msgs)) + msgIDs, err := src.SendMsgs(msgs) if err != nil { - logger.Error("failed to send msgs", err, "msgs", msgs) + logger.Error("failed to send msgs", err) + } else { + logger.Info("successfully sent msgs", "side", "src") } r.Succeeded = r.Succeeded && (err == nil) if err == nil { @@ -118,10 +128,14 @@ func (r *RelayMsgs) Send(src, dst Chain) { txSize += uint64(len(bz)) if r.IsMaxTx(msgLen, txSize) { + logger := logger.With("msgs", msgsToLoggable(msgs)) + // Submit the transaction to dst chain and update its status msgIDs, err := dst.SendMsgs(msgs) if err != nil { - logger.Error("failed to send msgs", err, "msgs", msgs) + logger.Error("failed to send msgs", err) + } else { + logger.Info("successfully sent msgs", "side", "dst") } r.Succeeded = r.Succeeded && (err == nil) if err == nil { @@ -139,9 +153,13 @@ func (r *RelayMsgs) Send(src, dst Chain) { // submit leftover msgs if len(msgs) > 0 { + logger := logger.With("msgs", msgsToLoggable(msgs)) + msgIDs, err := dst.SendMsgs(msgs) if err != nil { - logger.Error("failed to send msgs", err, "msgs", msgs) + logger.Error("failed to send msgs", err) + } else { + logger.Info("successfully sent msgs", "side", "dst") } r.Succeeded = r.Succeeded && (err == nil) if err == nil { @@ -154,6 +172,14 @@ func (r *RelayMsgs) Send(src, dst Chain) { r.DstMsgIDs = dstMsgIDs } +func msgsToLoggable(msgs []sdk.Msg) []string { + var ret []string + for _, msg := range msgs { + ret = append(ret, fmt.Sprintf("%T", msg)) + } + return ret +} + // Merge merges the argument into the receiver func (r *RelayMsgs) Merge(other *RelayMsgs) { r.Src = append(r.Src, other.Src...) From 57869ee8575735370714913d3748a2d72dfc9b1c Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 24 Jul 2024 11:08:57 +0900 Subject: [PATCH 19/47] fix core.ConfigI to allow to update the "order" and "version" fields Signed-off-by: Masanori Yoshida --- config/core.go | 38 ++++++++++++++++++++++---------------- core/config.go | 34 +++++++++++++++++----------------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/config/core.go b/config/core.go index 08055b99..d3d928e2 100644 --- a/config/core.go +++ b/config/core.go @@ -19,31 +19,37 @@ func initCoreConfig(c *Config) { core.SetCoreConfig(config) } -func (c CoreConfig) UpdateConfigID(pathName string, chainID string, configID core.ConfigIDType, id string) error { +func (c CoreConfig) UpdatePathConfig(pathName string, chainID string, kv map[core.PathConfigKey]string) error { configPath, err := c.config.Paths.Get(pathName) if err != nil { return err } + var pathEnd *core.PathEnd if chainID == configPath.Src.ChainID { pathEnd = configPath.Src - } - if chainID == configPath.Dst.ChainID { + } else if chainID == configPath.Dst.ChainID { pathEnd = configPath.Dst - } - if pathEnd == nil { + } else { return fmt.Errorf("pathEnd is nil") } - switch configID { - case core.ConfigIDClient: - pathEnd.ClientID = id - case core.ConfigIDConnection: - pathEnd.ConnectionID = id - case core.ConfigIDChannel: - pathEnd.ChannelID = id - } - if err := c.config.OverWriteConfig(); err != nil { - return err + + for k, v := range kv { + switch k { + case core.PathConfigClientID: + pathEnd.ClientID = v + case core.PathConfigConnectionID: + pathEnd.ConnectionID = v + case core.PathConfigChannelID: + pathEnd.ChannelID = v + case core.PathConfigOrder: + pathEnd.Order = v + case core.PathConfigVersion: + pathEnd.Version = v + default: + panic(fmt.Sprintf("unexpected path config key: %s", k)) + } } - return nil + + return c.config.OverWriteConfig() } diff --git a/core/config.go b/core/config.go index ce1b8c9c..5b229d39 100644 --- a/core/config.go +++ b/core/config.go @@ -13,16 +13,18 @@ import ( var config ConfigI -type ConfigIDType string +type PathConfigKey string const ( - ConfigIDClient ConfigIDType = "client" - ConfigIDConnection ConfigIDType = "connection" - ConfigIDChannel ConfigIDType = "channel" + PathConfigClientID PathConfigKey = "client-id" + PathConfigConnectionID PathConfigKey = "connection-id" + PathConfigChannelID PathConfigKey = "channel-id" + PathConfigOrder PathConfigKey = "order" + PathConfigVersion PathConfigKey = "version" ) type ConfigI interface { - UpdateConfigID(pathName string, chainID string, configID ConfigIDType, id string) error + UpdatePathConfig(pathName string, chainID string, kv map[PathConfigKey]string) error } func SetCoreConfig(c ConfigI) { @@ -156,23 +158,21 @@ func SyncChainConfigFromEvents(pathName string, msgIDs []MsgID, chain *ProvableC } for _, event := range msgRes.Events() { - var id string - var configID ConfigIDType + kv := make(map[PathConfigKey]string) + switch event := event.(type) { case *EventGenerateClientIdentifier: - configID = ConfigIDClient - id = event.ID + kv[PathConfigClientID] = event.ID case *EventGenerateConnectionIdentifier: - configID = ConfigIDConnection - id = event.ID + kv[PathConfigConnectionID] = event.ID case *EventGenerateChannelIdentifier: - configID = ConfigIDChannel - id = event.ID + kv[PathConfigChannelID] = event.ID + default: + continue } - if id != "" { - if err := config.UpdateConfigID(pathName, chain.ChainID(), configID, id); err != nil { - return err - } + + if err := config.UpdatePathConfig(pathName, chain.ChainID(), kv); err != nil { + return err } } } From 49f72d48c9b2a33c1466d3fc2490f2c0ae307c72 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 25 Jul 2024 22:28:01 +0900 Subject: [PATCH 20/47] update the path config fields (connection-id, version and order) when channel upgrade is successfully completed Signed-off-by: Masanori Yoshida --- chains/tendermint/msg.go | 7 +++++++ cmd/tx.go | 2 +- core/channel-upgrade.go | 6 +++++- core/client.go | 4 ++-- core/config.go | 32 ++++++++++++++++++++++++++++++++ core/msg.go | 10 ++++++++++ 6 files changed, 57 insertions(+), 4 deletions(-) diff --git a/chains/tendermint/msg.go b/chains/tendermint/msg.go index 532bb3b3..f412119b 100644 --- a/chains/tendermint/msg.go +++ b/chains/tendermint/msg.go @@ -146,6 +146,13 @@ func parseMsgEventLog(ev abcitypes.Event) (core.MsgEventLog, error) { return nil, err } return &event, nil + case chantypes.EventTypeChannelUpgradeOpen: + var event core.EventUpgradeChannel + var err0, err1, err2 error + event.PortID, err0 = getAttributeString(ev, chantypes.AttributeKeyPortID) + event.ChannelID, err1 = getAttributeString(ev, chantypes.AttributeKeyChannelID) + event.UpgradeSequence, err2 = getAttributeUint64(ev, chantypes.AttributeKeyUpgradeSequence) + return &event, errors.Join(err0, err1, err2) default: return &core.EventUnknown{Value: ev}, nil } diff --git a/cmd/tx.go b/cmd/tx.go index 53c95cd0..ef5d1157 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -318,7 +318,7 @@ func channelUpgradeExecuteCmd(ctx *config.Context) *cobra.Command { return err } - return core.ExecuteChannelUpgrade(src, dst, interval, untilFlushing) + return core.ExecuteChannelUpgrade(pathName, src, dst, interval, untilFlushing) }, } diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index a2e3666a..3781b2cf 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -46,7 +46,7 @@ func InitChannelUpgrade(chain *ProvableChain, upgradeFields chantypes.UpgradeFie // ExecuteChannelUpgrade carries out channel upgrade handshake until both chains transition to the OPEN state. // This function repeatedly checks the states of both chains and decides the next action. -func ExecuteChannelUpgrade(src, dst *ProvableChain, interval time.Duration, untilFlushing bool) error { +func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval time.Duration, untilFlushing bool) error { logger := GetChannelPairLogger(src, dst) defer logger.TimeTrack(time.Now(), "ExecuteChannelUpgrade") @@ -69,6 +69,10 @@ func ExecuteChannelUpgrade(src, dst *ProvableChain, interval time.Duration, unti steps.Send(src, dst) if steps.Success() { + if err := SyncChainConfigsFromEvents(pathName, steps.SrcMsgIDs, steps.DstMsgIDs, src, dst); err != nil { + return err + } + if steps.Last { logger.Info("Channel upgrade completed") return nil diff --git a/core/client.go b/core/client.go index 055d813d..e42e2af7 100644 --- a/core/client.go +++ b/core/client.go @@ -12,8 +12,8 @@ import ( ) func checkCreateClientsReady(src, dst *ProvableChain, logger *log.RelayLogger) (bool, error) { - srcID := src.Chain.Path().ClientID; - dstID := dst.Chain.Path().ClientID; + srcID := src.Chain.Path().ClientID + dstID := dst.Chain.Path().ClientID if srcID == "" && dstID == "" { return true, nil diff --git a/core/config.go b/core/config.go index 5b229d39..f7152108 100644 --- a/core/config.go +++ b/core/config.go @@ -1,12 +1,14 @@ package core import ( + "context" "encoding/json" "errors" "fmt" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/gogoproto/proto" + chantypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" "github.com/hyperledger-labs/yui-relayer/log" "github.com/hyperledger-labs/yui-relayer/utils" ) @@ -167,6 +169,36 @@ func SyncChainConfigFromEvents(pathName string, msgIDs []MsgID, chain *ProvableC kv[PathConfigConnectionID] = event.ID case *EventGenerateChannelIdentifier: kv[PathConfigChannelID] = event.ID + case *EventUpgradeChannel: + chann, err := chain.QueryChannel(NewQueryContext(context.TODO(), msgRes.BlockHeight())) + if err != nil { + return fmt.Errorf("failed to query a channel corresponding to the EventUpgradeChannel event: %v", err) + } + + if chann.Channel.UpgradeSequence != event.UpgradeSequence { + return fmt.Errorf("unexpected mismatch of upgrade sequence: channel.upgradeSequence=%d, event.upgradeSequence=%d", + chann.Channel.UpgradeSequence, + event.UpgradeSequence, + ) + } + + if len(chann.Channel.ConnectionHops) != 1 { + return fmt.Errorf("unexpected length of connectionHops: %d", len(chann.Channel.ConnectionHops)) + } + + var order string + switch chann.Channel.Ordering { + case chantypes.ORDERED: + order = "ordered" + case chantypes.UNORDERED: + order = "unordered" + default: + return fmt.Errorf("unexpected channel ordering: %d", chann.Channel.Ordering) + } + + kv[PathConfigConnectionID] = chann.Channel.ConnectionHops[0] + kv[PathConfigOrder] = order + kv[PathConfigVersion] = chann.Channel.Version default: continue } diff --git a/core/msg.go b/core/msg.go index bdf9f6d9..8068059b 100644 --- a/core/msg.go +++ b/core/msg.go @@ -46,6 +46,7 @@ var ( _ MsgEventLog = (*EventRecvPacket)(nil) _ MsgEventLog = (*EventWriteAcknowledgement)(nil) _ MsgEventLog = (*EventAcknowledgePacket)(nil) + _ MsgEventLog = (*EventUpgradeChannel)(nil) _ MsgEventLog = (*EventUnknown)(nil) ) @@ -115,6 +116,15 @@ type EventAcknowledgePacket struct { TimeoutTimestamp time.Time } +// EventUpgradeChannel is an implementation of `MsgEventLog` that notifies the completion of a channel upgrade +type EventUpgradeChannel struct { + isMsgEventLog + + PortID string + ChannelID string + UpgradeSequence uint64 +} + // EventUnknown is an implementation of `MsgEventLog` that represents another event. type EventUnknown struct { isMsgEventLog From 2be31c2b801341403b4d1eab2a858defc2ed30d6 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 25 Jul 2024 23:17:20 +0900 Subject: [PATCH 21/47] fix test-channel-upgrade to test the path config update feature Signed-off-by: Masanori Yoshida --- tests/cases/tm2tm/scripts/test-channel-upgrade | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade index 7a7ed6f3..ed3a9cae 100755 --- a/tests/cases/tm2tm/scripts/test-channel-upgrade +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -36,15 +36,27 @@ mv $origconfig "$RELAYER_CONF/config/config.json" $RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcConnectionId --version ics20-1 $RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstConnectionId --version ics20-1 $RLY tx channel-upgrade execute ibc01 +pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') +pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') +[ "$pathSrcConnectionId" = "$srcConnectionId" ] || { echo 'src path config update failed'; exit 1; } +[ "$pathDstConnectionId" = "$dstConnectionId" ] || { echo 'dst path config update failed'; exit 1; } # test channel upgrade (non-crossing-hello) $RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 $RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 $RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstOrigConnectionId --version ics20-1 $RLY tx channel-upgrade execute ibc01 +pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') +pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') +[ "$pathSrcConnectionId" = "$srcOrigConnectionId" ] || { echo 'src path config update failed'; exit 1; } +[ "$pathDstConnectionId" = "$dstOrigConnectionId" ] || { echo 'dst path config update failed'; exit 1; } # test channel upgrade cancel $RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcConnectionId --version ics20-1 $RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstConnectionId --version ics20-1 $RLY tx channel-upgrade cancel ibc01 ibc0 # create situation where ibc0.error_receipt.sequence >= ibc1.channel.upgrade_sequence $RLY tx channel-upgrade execute ibc01 # the channel upgrade of ibc1 should be cancelled +pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') +pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') +[ "$pathSrcConnectionId" = "$srcOrigConnectionId" ] || { echo 'src path config update failed'; exit 1; } +[ "$pathDstConnectionId" = "$dstOrigConnectionId" ] || { echo 'dst path config update failed'; exit 1; } From e802418cf463ef4277c69ed516ac3ba6cccd6b4f Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 25 Jul 2024 23:17:55 +0900 Subject: [PATCH 22/47] minor fix of test-create-xxx Signed-off-by: Masanori Yoshida --- tests/cases/tm2tm/scripts/test-create-channel-fail-unexist | 2 +- tests/cases/tm2tm/scripts/test-create-client-fail-unexist | 6 +++--- tests/cases/tm2tm/scripts/test-create-client-success-single | 2 +- .../cases/tm2tm/scripts/test-create-connection-fail-unexist | 5 +++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/cases/tm2tm/scripts/test-create-channel-fail-unexist b/tests/cases/tm2tm/scripts/test-create-channel-fail-unexist index c811eab0..0f139e3f 100755 --- a/tests/cases/tm2tm/scripts/test-create-channel-fail-unexist +++ b/tests/cases/tm2tm/scripts/test-create-channel-fail-unexist @@ -35,7 +35,7 @@ r=$? if [ $r -eq 101 ]; then echo "$(basename $0): success" - cp $CONFIG.tmp $CONFIG + mv $CONFIG.tmp $CONFIG exit 0 else echo "$(basename $0): fail: $r" diff --git a/tests/cases/tm2tm/scripts/test-create-client-fail-unexist b/tests/cases/tm2tm/scripts/test-create-client-fail-unexist index 6a8b962d..f0534328 100755 --- a/tests/cases/tm2tm/scripts/test-create-client-fail-unexist +++ b/tests/cases/tm2tm/scripts/test-create-client-fail-unexist @@ -34,11 +34,11 @@ EOF r=$? if [ $r -eq 101 ]; then - echo "success" - cp $CONFIG.tmp $CONFIG + echo "$(basename $0): success" + mv $CONFIG.tmp $CONFIG exit 0 else - echo "fail: $r" + echo "$(basename $0): fail: $r" exit 1 fi diff --git a/tests/cases/tm2tm/scripts/test-create-client-success-single b/tests/cases/tm2tm/scripts/test-create-client-success-single index 87fa368e..a97d6297 100755 --- a/tests/cases/tm2tm/scripts/test-create-client-success-single +++ b/tests/cases/tm2tm/scripts/test-create-client-success-single @@ -53,4 +53,4 @@ if [ "$NEW_DST_CLIENT_ID" != "$OLD_DST_CLIENT_ID" ]; then exit 1 fi -cp $CONFIG.tmp $CONFIG +mv $CONFIG.tmp $CONFIG diff --git a/tests/cases/tm2tm/scripts/test-create-connection-fail-unexist b/tests/cases/tm2tm/scripts/test-create-connection-fail-unexist index c1472d79..022e2d0c 100755 --- a/tests/cases/tm2tm/scripts/test-create-connection-fail-unexist +++ b/tests/cases/tm2tm/scripts/test-create-connection-fail-unexist @@ -34,10 +34,11 @@ EOF r=$? if [ $r -eq 101 ]; then - echo "success" + echo "$(basename $0): success" + mv $CONFIG.tmp $CONFIG exit 0 else - echo "fail: $r" + echo "$(basename $0): fail: $r" exit 1 fi From 1bae6e3a41a495ad9a0d692d86b2919f7864e756 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 5 Aug 2024 10:53:53 +0900 Subject: [PATCH 23/47] minor fix of test-channel-upgrade Signed-off-by: Masanori Yoshida --- tests/cases/tm2tm/scripts/test-channel-upgrade | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade index ed3a9cae..5eed5aaf 100755 --- a/tests/cases/tm2tm/scripts/test-channel-upgrade +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -26,20 +26,20 @@ $RLY paths edit $PATH_NAME dst connection-id '' # create a new connection and save the new connection identifiers retry 5 $RLY tx connection $PATH_NAME -srcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') -dstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') +srcAltConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') +dstAltConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') # resume the original config.json mv $origconfig "$RELAYER_CONF/config/config.json" # test channel upgrade (crossing-hello) -$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcAltConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstAltConnectionId --version ics20-1 $RLY tx channel-upgrade execute ibc01 pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') -[ "$pathSrcConnectionId" = "$srcConnectionId" ] || { echo 'src path config update failed'; exit 1; } -[ "$pathDstConnectionId" = "$dstConnectionId" ] || { echo 'dst path config update failed'; exit 1; } +[ "$pathSrcConnectionId" = "$srcAltConnectionId" ] || { echo 'src path config update failed'; exit 1; } +[ "$pathDstConnectionId" = "$dstAltConnectionId" ] || { echo 'dst path config update failed'; exit 1; } # test channel upgrade (non-crossing-hello) $RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 @@ -52,8 +52,8 @@ pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name " [ "$pathDstConnectionId" = "$dstOrigConnectionId" ] || { echo 'dst path config update failed'; exit 1; } # test channel upgrade cancel -$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcAltConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstAltConnectionId --version ics20-1 $RLY tx channel-upgrade cancel ibc01 ibc0 # create situation where ibc0.error_receipt.sequence >= ibc1.channel.upgrade_sequence $RLY tx channel-upgrade execute ibc01 # the channel upgrade of ibc1 should be cancelled pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') From 432d60fcb4c9cd16a94b265bed22a65a83f46292 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 14 Aug 2024 00:50:01 +0900 Subject: [PATCH 24/47] output a WARN log when executing chanUpgradeCancel without error receipt Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 3781b2cf..7613f933 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -298,6 +298,10 @@ func upgradeChannelStep(src, dst *ProvableChain, untilFlushing bool) (*RelayMsgs return nil, err } + if cpChanUpgErr == nil { + logger.Warn("error receipt not found", "seq", upgradeSequence, "chain_id", cp.ChainID()) + } + var msgs []sdk.Msg addr := mustGetAddress(chain) if len(cpHeaders) > 0 { From 974ff9fe6f11a9ea1943b37b270a6705f4457311 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Tue, 20 Aug 2024 14:21:21 +0900 Subject: [PATCH 25/47] brush up `channel-upgrade execute` subcommand Signed-off-by: Masanori Yoshida --- cmd/tx.go | 69 ++- core/channel-upgrade.go | 470 ++++++++++-------- .../cases/tm2tm/scripts/test-channel-upgrade | 46 +- 3 files changed, 348 insertions(+), 237 deletions(-) diff --git a/cmd/tx.go b/cmd/tx.go index ef5d1157..4f896a02 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "errors" "fmt" "strings" "time" @@ -13,6 +14,7 @@ import ( "github.com/hyperledger-labs/yui-relayer/config" "github.com/hyperledger-labs/yui-relayer/core" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/spf13/viper" ) @@ -241,13 +243,11 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { } // get ordering from flags - var ordering chantypes.Order - if s, err := cmd.Flags().GetString(flagOrdering); err != nil { + ordering, err := getOrderFromFlags(cmd.Flags(), flagOrdering) + if err != nil { return err - } else if n, ok := chantypes.Order_value[s]; !ok || n == int32(chantypes.NONE) { - return fmt.Errorf("invalid ordering flag: %s", s) - } else { - ordering = chantypes.Order(n) + } else if ordering == chantypes.NONE { + return errors.New("NONE is unacceptable channel ordering") } // get connection hops from flags @@ -279,12 +279,14 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { func channelUpgradeExecuteCmd(ctx *config.Context) *cobra.Command { const ( - flagInterval = "interval" - flagUntilFlushing = "until-flushing" + flagInterval = "interval" + flagTargetSrcState = "target-src-state" + flagTargetDstState = "target-dst-state" ) const ( - defaultInterval = time.Second + defaultInterval = time.Second + defaultTargetState = "UNINIT" ) cmd := cobra.Command{ @@ -313,17 +315,23 @@ func channelUpgradeExecuteCmd(ctx *config.Context) *cobra.Command { return err } - untilFlushing, err := cmd.Flags().GetBool(flagUntilFlushing) + targetSrcState, err := getUpgradeStateFromFlags(cmd.Flags(), flagTargetSrcState) if err != nil { return err } - return core.ExecuteChannelUpgrade(pathName, src, dst, interval, untilFlushing) + targetDstState, err := getUpgradeStateFromFlags(cmd.Flags(), flagTargetDstState) + if err != nil { + return err + } + + return core.ExecuteChannelUpgrade(pathName, src, dst, interval, targetSrcState, targetDstState) }, } - cmd.Flags().Duration(flagInterval, defaultInterval, "interval between attempts to proceed channel upgrade steps") - cmd.Flags().Bool(flagUntilFlushing, false, "the process exits when both chains have started flushing") + cmd.Flags().Duration(flagInterval, defaultInterval, "the interval between attempts to proceed the upgrade handshake") + cmd.Flags().String(flagTargetSrcState, defaultTargetState, "the source channel's upgrade state to be reached") + cmd.Flags().String(flagTargetDstState, defaultTargetState, "the destination channel's upgrade state to be reached") return &cmd } @@ -542,3 +550,38 @@ func getUint64Slice(key string) []uint64 { } return ret } + +func getUpgradeStateFromFlags(flags *pflag.FlagSet, flagName string) (core.UpgradeState, error) { + s, err := flags.GetString(flagName) + if err != nil { + return 0, err + } + + switch strings.ToUpper(s) { + case "UNINIT": + return core.UPGRADE_STATE_UNINIT, nil + case "INIT": + return core.UPGRADE_STATE_INIT, nil + case "FLUSHING": + return core.UPGRADE_STATE_FLUSHING, nil + case "FLUSHCOMPLETE": + return core.UPGRADE_STATE_FLUSHCOMPLETE, nil + default: + return 0, fmt.Errorf("invalid upgrade state specified: %s", s) + } +} + +func getOrderFromFlags(flags *pflag.FlagSet, flagName string) (chantypes.Order, error) { + s, err := flags.GetString(flagName) + if err != nil { + return 0, err + } + + s = "ORDER_" + strings.ToUpper(s) + value, ok := chantypes.Order_value[s] + if !ok { + return 0, fmt.Errorf("invalid channel order specified: %s", s) + } + + return chantypes.Order(value), nil +} diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 7613f933..bea6cf11 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -3,7 +3,7 @@ package core import ( "errors" "fmt" - "math" + "log/slog" "time" retry "github.com/avast/retry-go" @@ -12,6 +12,66 @@ import ( chantypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" ) +type UpgradeState int + +const ( + UPGRADE_STATE_UNINIT UpgradeState = iota + UPGRADE_STATE_INIT + UPGRADE_STATE_FLUSHING + UPGRADE_STATE_FLUSHCOMPLETE +) + +func (state UpgradeState) String() string { + switch state { + case UPGRADE_STATE_UNINIT: + return "UNINIT" + case UPGRADE_STATE_INIT: + return "INIT" + case UPGRADE_STATE_FLUSHING: + return "FLUSHING" + case UPGRADE_STATE_FLUSHCOMPLETE: + return "FLUSHCOMPLETE" + default: + panic(fmt.Errorf("unexpected UpgradeState: %d", state)) + } +} + +type UpgradeAction int + +const ( + UPGRADE_ACTION_NONE UpgradeAction = iota + UPGRADE_ACTION_TRY + UPGRADE_ACTION_ACK + UPGRADE_ACTION_CONFIRM + UPGRADE_ACTION_OPEN + UPGRADE_ACTION_CANCEL + UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE + UPGRADE_ACTION_TIMEOUT +) + +func (action UpgradeAction) String() string { + switch action { + case UPGRADE_ACTION_NONE: + return "NONE" + case UPGRADE_ACTION_TRY: + return "TRY" + case UPGRADE_ACTION_ACK: + return "ACK" + case UPGRADE_ACTION_CONFIRM: + return "CONFIRM" + case UPGRADE_ACTION_OPEN: + return "OPEN" + case UPGRADE_ACTION_CANCEL: + return "CANCEL" + case UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE: + return "CANCEL_FLUSHCOMPLETE" + case UPGRADE_ACTION_TIMEOUT: + return "TIMEOUT" + default: + panic(fmt.Errorf("unexpected UpgradeAction: %d", action)) + } +} + // InitChannelUpgrade builds `MsgChannelUpgradeInit` based on the specified UpgradeFields and sends it to the specified chain. func InitChannelUpgrade(chain *ProvableChain, upgradeFields chantypes.UpgradeFields) error { logger := GetChannelLogger(chain.Chain) @@ -46,21 +106,27 @@ func InitChannelUpgrade(chain *ProvableChain, upgradeFields chantypes.UpgradeFie // ExecuteChannelUpgrade carries out channel upgrade handshake until both chains transition to the OPEN state. // This function repeatedly checks the states of both chains and decides the next action. -func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval time.Duration, untilFlushing bool) error { +func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval time.Duration, targetSrcState, targetDstState UpgradeState) error { logger := GetChannelPairLogger(src, dst) defer logger.TimeTrack(time.Now(), "ExecuteChannelUpgrade") tick := time.Tick(interval) failures := 0 + firstCall := true for { <-tick - steps, err := upgradeChannelStep(src, dst, untilFlushing) + steps, err := upgradeChannelStep(src, dst, targetSrcState, targetDstState, firstCall) if err != nil { logger.Error("failed to create channel upgrade step", err) return err } + if steps.Last { + logger.Info("Channel upgrade completed") + return nil + } + if !steps.Ready() { logger.Debug("Waiting for next channel upgrade step ...") continue @@ -73,11 +139,7 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti return err } - if steps.Last { - logger.Info("Channel upgrade completed") - return nil - } - + firstCall = false failures = 0 } else { if failures++; failures > 2 { @@ -155,7 +217,24 @@ func CancelChannelUpgrade(chain, cp *ProvableChain) error { return nil } -func upgradeChannelStep(src, dst *ProvableChain, untilFlushing bool) (*RelayMsgs, error) { +func NewUpgradeState(chanState chantypes.State, upgradeExists bool) (UpgradeState, error) { + switch chanState { + case chantypes.OPEN: + if upgradeExists { + return UPGRADE_STATE_INIT, nil + } else { + return UPGRADE_STATE_UNINIT, nil + } + case chantypes.FLUSHING: + return UPGRADE_STATE_FLUSHING, nil + case chantypes.FLUSHCOMPLETE: + return UPGRADE_STATE_FLUSHCOMPLETE, nil + default: + return 0, fmt.Errorf("channel not opened yet: state=%s", chanState) + } +} + +func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState UpgradeState, firstCall bool) (*RelayMsgs, error) { logger := GetChannelPairLogger(src, dst) out := NewRelayMsgs() @@ -231,299 +310,234 @@ func upgradeChannelStep(src, dst *ProvableChain, untilFlushing bool) (*RelayMsgs return out, nil } - // translate channel state to channel upgrade state - type UpgradeState chantypes.State - const ( - UPGRADEUNINIT = UpgradeState(chantypes.OPEN) - UPGRADEINIT = UpgradeState(math.MaxInt32) - FLUSHING = UpgradeState(chantypes.FLUSHING) - FLUSHCOMPLETE = UpgradeState(chantypes.FLUSHCOMPLETE) - ) - srcState := UpgradeState(srcChan.Channel.State) - if srcState == UPGRADEUNINIT && srcChanUpg != nil { - srcState = UPGRADEINIT - } - dstState := UpgradeState(dstChan.Channel.State) - if dstState == UPGRADEUNINIT && dstChanUpg != nil { - dstState = UPGRADEINIT - } - - doTry := func(chain *ProvableChain, cpCtx QueryContext, cp *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse, cpChanUpg *chantypes.QueryUpgradeResponse) ([]sdk.Msg, error) { - proposedConnectionID, err := queryProposedConnectionID(cpCtx, cp, cpChanUpg) - if err != nil { - return nil, err - } - var msgs []sdk.Msg - addr := mustGetAddress(chain) - if len(cpHeaders) > 0 { - msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) - } - msgs = append(msgs, chain.Path().ChanUpgradeTry(proposedConnectionID, cpChan, cpChanUpg, addr)) - return msgs, nil - } - - doAck := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse, cpChanUpg *chantypes.QueryUpgradeResponse) []sdk.Msg { - var msgs []sdk.Msg - addr := mustGetAddress(chain) - if len(cpHeaders) > 0 { - msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) - } - msgs = append(msgs, chain.Path().ChanUpgradeAck(cpChan, cpChanUpg, addr)) - return msgs - } - - doConfirm := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse, cpChanUpg *chantypes.QueryUpgradeResponse) []sdk.Msg { - var msgs []sdk.Msg - addr := mustGetAddress(chain) - if len(cpHeaders) > 0 { - msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) - } - msgs = append(msgs, chain.Path().ChanUpgradeConfirm(cpChan, cpChanUpg, addr)) - return msgs + // determine upgrade states + srcState, err := NewUpgradeState(srcChan.Channel.State, srcChanUpg != nil) + if err != nil { + return nil, err } - - doOpen := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse) []sdk.Msg { - var msgs []sdk.Msg - addr := mustGetAddress(chain) - if len(cpHeaders) > 0 { - msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) - } - msgs = append(msgs, chain.Path().ChanUpgradeOpen(cpChan, addr)) - return msgs + dstState, err := NewUpgradeState(dstChan.Channel.State, dstChanUpg != nil) + if err != nil { + return nil, err } - doCancel := func(chain *ProvableChain, cpCtx QueryContext, cp *ProvableChain, cpHeaders []Header, upgradeSequence uint64) ([]sdk.Msg, error) { - cpChanUpgErr, err := QueryChannelUpgradeError(cpCtx, cp, upgradeSequence, true) - if err != nil { - return nil, err - } - - if cpChanUpgErr == nil { - logger.Warn("error receipt not found", "seq", upgradeSequence, "chain_id", cp.ChainID()) - } - - var msgs []sdk.Msg - addr := mustGetAddress(chain) - if len(cpHeaders) > 0 { - msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) - } - msgs = append(msgs, chain.Path().ChanUpgradeCancel(cpChanUpgErr, addr)) - return msgs, nil + // check if both chains have reached the target states or UNINIT states + if !firstCall && srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_UNINIT || + srcState != UPGRADE_STATE_UNINIT && dstState != UPGRADE_STATE_UNINIT && srcState == targetSrcState && dstState == targetDstState { + out.Last = true + return out, nil } - doTimeout := func(chain *ProvableChain, cpHeaders []Header, cpChan *chantypes.QueryChannelResponse) []sdk.Msg { - var msgs []sdk.Msg - addr := mustGetAddress(chain) - if len(cpHeaders) > 0 { - msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) - } - msgs = append(msgs, chain.Path().ChanUpgradeTimeout(cpChan, addr)) - return msgs - } + // add info to logger + logger = logger.With( + slog.Group("src", + "state", srcState, + "seq", srcChan.Channel.UpgradeSequence, + ), + slog.Group("dst", + "state", dstState, + "seq", dstChan.Channel.UpgradeSequence, + ), + ) + // determine next actions for src/dst chains + srcAction := UPGRADE_ACTION_NONE + dstAction := UPGRADE_ACTION_NONE switch { - case srcState == UPGRADEUNINIT && dstState == UPGRADEUNINIT: + case srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_UNINIT: return nil, errors.New("channel upgrade is not initialized") - case srcState == UPGRADEINIT && dstState == UPGRADEUNINIT: + case srcState == UPGRADE_STATE_INIT && dstState == UPGRADE_STATE_UNINIT: if dstChan.Channel.UpgradeSequence >= srcChan.Channel.UpgradeSequence { - logger.Warn("the initialized channel upgrade is outdated", - "src_seq", srcChan.Channel.UpgradeSequence, - "dst_seq", dstChan.Channel.UpgradeSequence, - ) - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, 0); err != nil { - return nil, err - } - out.Last = true + srcAction = UPGRADE_ACTION_CANCEL } else { - if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { - return nil, err - } + dstAction = UPGRADE_ACTION_TRY } - case srcState == UPGRADEUNINIT && dstState == UPGRADEINIT: + case srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_INIT: if srcChan.Channel.UpgradeSequence >= dstChan.Channel.UpgradeSequence { - logger.Warn("the initialized channel upgrade is outdated", - "src_seq", srcChan.Channel.UpgradeSequence, - "dst_seq", dstChan.Channel.UpgradeSequence, - ) - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, 0); err != nil { - return nil, err - } - out.Last = true + dstAction = UPGRADE_ACTION_CANCEL } else { - if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { - return nil, err - } - } - case srcState == UPGRADEUNINIT && dstState == FLUSHING: - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, 0); err != nil { - return nil, err - } - out.Last = true - case srcState == FLUSHING && dstState == UPGRADEUNINIT: - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, 0); err != nil { - return nil, err + srcAction = UPGRADE_ACTION_TRY } - out.Last = true - case srcState == UPGRADEUNINIT && dstState == FLUSHCOMPLETE: + case srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_FLUSHING: + dstAction = UPGRADE_ACTION_CANCEL + case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_UNINIT: + srcAction = UPGRADE_ACTION_CANCEL + case srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_FLUSHCOMPLETE: if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { return nil, err } else if complete { - out.Dst = doOpen(dst, srcUpdateHeaders, srcChan) + dstAction = UPGRADE_ACTION_OPEN } else if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { return nil, err } else if timedout { - out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) + dstAction = UPGRADE_ACTION_TIMEOUT } else { - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { - return nil, err - } + dstAction = UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE } - out.Last = true - case srcState == FLUSHCOMPLETE && dstState == UPGRADEUNINIT: + case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_UNINIT: if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { return nil, err } else if complete { - out.Src = doOpen(src, dstUpdateHeaders, dstChan) + srcAction = UPGRADE_ACTION_OPEN } else if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { return nil, err } else if timedout { - out.Src = doTimeout(src, dstUpdateHeaders, dstChan) + srcAction = UPGRADE_ACTION_TIMEOUT } else { - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { - return nil, err - } + srcAction = UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE } - out.Last = true - case srcState == UPGRADEINIT && dstState == UPGRADEINIT: // crossing hellos - if srcChan.Channel.UpgradeSequence > dstChan.Channel.UpgradeSequence { - if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { - return nil, err - } - } else if srcChan.Channel.UpgradeSequence < dstChan.Channel.UpgradeSequence { - if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { - return nil, err - } - } else { - // it is intentional to execute chanUpgradeTry on both sides if upgrade sequences - // are identical to each other. this is for testing purpose. - if out.Dst, err = doTry(dst, srcCtxFinalized, src, srcUpdateHeaders, srcChan, srcChanUpg); err != nil { - return nil, err - } - if out.Src, err = doTry(src, dstCtxFinalized, dst, dstUpdateHeaders, dstChan, dstChanUpg); err != nil { - return nil, err - } - out.Last = untilFlushing + case srcState == UPGRADE_STATE_INIT && dstState == UPGRADE_STATE_INIT: // crossing hellos + // it is intentional to execute chanUpgradeTry on both sides if upgrade sequences + // are identical to each other. this is for testing purpose. + if srcChan.Channel.UpgradeSequence >= dstChan.Channel.UpgradeSequence { + dstAction = UPGRADE_ACTION_TRY } - case srcState == UPGRADEINIT && dstState == FLUSHING: + if srcChan.Channel.UpgradeSequence <= dstChan.Channel.UpgradeSequence { + srcAction = UPGRADE_ACTION_TRY + } + case srcState == UPGRADE_STATE_INIT && dstState == UPGRADE_STATE_FLUSHING: if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, 0); err != nil { - return nil, err - } + dstAction = UPGRADE_ACTION_CANCEL } else { // chanUpgradeAck checks if counterparty-specified timeout has exceeded. // if it has, chanUpgradeAck aborts the upgrade handshake. // Therefore the relayer need not check timeout by itself. - out.Src = doAck(src, dstUpdateHeaders, dstChan, dstChanUpg) - out.Last = untilFlushing + srcAction = UPGRADE_ACTION_ACK } - case srcState == FLUSHING && dstState == UPGRADEINIT: + case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_INIT: if srcChan.Channel.UpgradeSequence != dstChan.Channel.UpgradeSequence { - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, 0); err != nil { - return nil, err - } + srcAction = UPGRADE_ACTION_CANCEL } else { // chanUpgradeAck checks if counterparty-specified timeout has exceeded. // if it has, chanUpgradeAck aborts the upgrade handshake. // Therefore the relayer need not check timeout by itself. - out.Dst = doAck(dst, srcUpdateHeaders, srcChan, srcChanUpg) - out.Last = true + dstAction = UPGRADE_ACTION_ACK } - case srcState == UPGRADEINIT && dstState == FLUSHCOMPLETE: + case srcState == UPGRADE_STATE_INIT && dstState == UPGRADE_STATE_FLUSHCOMPLETE: if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { return nil, err } else if complete { - out.Dst = doOpen(dst, srcUpdateHeaders, srcChan) + dstAction = UPGRADE_ACTION_OPEN } else if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { return nil, err } else if timedout { - out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) + dstAction = UPGRADE_ACTION_TIMEOUT } else { - if out.Dst, err = doCancel(dst, srcCtxFinalized, src, srcUpdateHeaders, dstChan.Channel.UpgradeSequence); err != nil { - return nil, err - } + dstAction = UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE } - case srcState == FLUSHCOMPLETE && dstState == UPGRADEINIT: + case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_INIT: if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { return nil, err } else if complete { - out.Src = doOpen(src, dstUpdateHeaders, dstChan) + srcAction = UPGRADE_ACTION_OPEN } else if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { return nil, err } else if timedout { - out.Src = doTimeout(src, dstUpdateHeaders, dstChan) + srcAction = UPGRADE_ACTION_TIMEOUT } else { - if out.Src, err = doCancel(src, dstCtxFinalized, dst, dstUpdateHeaders, srcChan.Channel.UpgradeSequence); err != nil { - return nil, err - } + srcAction = UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE } - case srcState == FLUSHING && dstState == FLUSHING: - nTimedout := 0 + case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_FLUSHING: if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { return nil, err } else if timedout { - nTimedout += 1 - out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) + dstAction = UPGRADE_ACTION_TIMEOUT } if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { return nil, err } else if timedout { - nTimedout += 1 - out.Src = doTimeout(src, dstUpdateHeaders, dstChan) + srcAction = UPGRADE_ACTION_TIMEOUT } - // if any chains have exceeded timeout, never execute chanUpgradeConfirm - if nTimedout > 0 { + // if either chain has already timed out, never execute chanUpgradeConfirm + if srcAction == UPGRADE_ACTION_TIMEOUT || dstAction == UPGRADE_ACTION_TIMEOUT { break } if completable, err := src.QueryCanTransitionToFlushComplete(srcCtxFinalized); err != nil { return nil, err } else if completable { - out.Src = doConfirm(src, dstUpdateHeaders, dstChan, dstChanUpg) + srcAction = UPGRADE_ACTION_CONFIRM } if completable, err := dst.QueryCanTransitionToFlushComplete(dstCtxFinalized); err != nil { return nil, err } else if completable { - out.Dst = doConfirm(dst, srcUpdateHeaders, srcChan, srcChanUpg) + dstAction = UPGRADE_ACTION_CONFIRM } - case srcState == FLUSHING && dstState == FLUSHCOMPLETE: + case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_FLUSHCOMPLETE: if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { return nil, err } else if timedout { - out.Dst = doTimeout(dst, srcUpdateHeaders, srcChan) + dstAction = UPGRADE_ACTION_TIMEOUT } else if completable, err := src.QueryCanTransitionToFlushComplete(srcCtxFinalized); err != nil { return nil, err } else if completable { - out.Src = doConfirm(src, dstUpdateHeaders, dstChan, dstChanUpg) + srcAction = UPGRADE_ACTION_CONFIRM } - case srcState == FLUSHCOMPLETE && dstState == FLUSHING: + case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_FLUSHING: if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { return nil, err } else if timedout { - out.Src = doTimeout(src, dstUpdateHeaders, dstChan) + srcAction = UPGRADE_ACTION_TIMEOUT } else if completable, err := dst.QueryCanTransitionToFlushComplete(dstCtxFinalized); err != nil { return nil, err } else if completable { - out.Dst = doConfirm(dst, srcUpdateHeaders, srcChan, srcChanUpg) + dstAction = UPGRADE_ACTION_CONFIRM } - case srcState == FLUSHCOMPLETE && dstState == FLUSHCOMPLETE: - out.Src = doOpen(src, dstUpdateHeaders, dstChan) - out.Dst = doOpen(dst, srcUpdateHeaders, srcChan) - out.Last = true + case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_FLUSHCOMPLETE: + srcAction = UPGRADE_ACTION_OPEN + dstAction = UPGRADE_ACTION_OPEN default: return nil, errors.New("unexpected state") } + if srcAction != UPGRADE_ACTION_NONE { + addr := mustGetAddress(src) + + if len(dstUpdateHeaders) > 0 { + out.Src = append(out.Src, src.Path().UpdateClients(dstUpdateHeaders, addr)...) + } + + msg, err := buildActionMsg( + src, + srcAction, + srcChan, + addr, + dstCtxFinalized, + dst, + dstChan, + dstChanUpg, + ) + if err != nil { + return nil, err + } + + out.Src = append(out.Src, msg) + } + + if dstAction != UPGRADE_ACTION_NONE { + addr := mustGetAddress(dst) + + if len(srcUpdateHeaders) > 0 { + out.Dst = append(out.Dst, dst.Path().UpdateClients(srcUpdateHeaders, addr)...) + } + + msg, err := buildActionMsg( + dst, + dstAction, + dstChan, + addr, + srcCtxFinalized, + src, + srcChan, + srcChanUpg, + ) + if err != nil { + return nil, err + } + + out.Dst = append(out.Dst, msg) + } + return out, nil } @@ -624,3 +638,49 @@ func upgradeAlreadyTimedOut( } return cpChanUpg.Upgrade.Timeout.Elapsed(height, uint64(timestamp.UnixNano())), nil } + +// buildActionMsg builds and returns a MsgChannelUpgradeXXX message corresponding to `action`. +// This function also returns `UpgradeState` to which the channel will transition after the message is processed. +func buildActionMsg( + chain *ProvableChain, + action UpgradeAction, + selfChan *chantypes.QueryChannelResponse, + addr sdk.AccAddress, + cpCtx QueryContext, + cp *ProvableChain, + cpChan *chantypes.QueryChannelResponse, + cpUpg *chantypes.QueryUpgradeResponse, +) (sdk.Msg, error) { + pathEnd := chain.Path() + + switch action { + case UPGRADE_ACTION_TRY: + proposedConnectionID, err := queryProposedConnectionID(cpCtx, cp, cpUpg) + if err != nil { + return nil, err + } + return pathEnd.ChanUpgradeTry(proposedConnectionID, cpChan, cpUpg, addr), nil + case UPGRADE_ACTION_ACK: + return pathEnd.ChanUpgradeAck(cpChan, cpUpg, addr), nil + case UPGRADE_ACTION_CONFIRM: + return pathEnd.ChanUpgradeConfirm(cpChan, cpUpg, addr), nil + case UPGRADE_ACTION_OPEN: + return pathEnd.ChanUpgradeOpen(cpChan, addr), nil + case UPGRADE_ACTION_CANCEL: + upgErr, err := QueryChannelUpgradeError(cpCtx, cp, 0, true) + if err != nil { + return nil, err + } + return pathEnd.ChanUpgradeCancel(upgErr, addr), nil + case UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE: + upgErr, err := QueryChannelUpgradeError(cpCtx, cp, selfChan.Channel.UpgradeSequence, true) + if err != nil { + return nil, err + } + return pathEnd.ChanUpgradeCancel(upgErr, addr), nil + case UPGRADE_ACTION_TIMEOUT: + return pathEnd.ChanUpgradeTimeout(cpChan, addr), nil + default: + panic(fmt.Errorf("unexpected action: %s", action)) + } +} diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade index 5eed5aaf..4ee9827c 100755 --- a/tests/cases/tm2tm/scripts/test-channel-upgrade +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -32,31 +32,39 @@ dstAltConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$ # resume the original config.json mv $origconfig "$RELAYER_CONF/config/config.json" +checkResult() { + expectedSrcConnectionId=$1 + expectedDstConnectionId=$2 + pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') + pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') + if [ "$pathSrcConnectionId" != "$expectedSrcConnectionId" ] + then + echo 'src path config update failed' + exit 1 + elif [ "$pathDstConnectionId" != "$expectedDstConnectionId" ] + then + echo 'dst path config update failed' + exit 1 + fi +} + # test channel upgrade (crossing-hello) -$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcAltConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstAltConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 --ordering unordered --connection-hops $srcAltConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc1 --ordering UnOrdered --connection-hops $dstAltConnectionId --version ics20-1 $RLY tx channel-upgrade execute ibc01 -pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') -pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') -[ "$pathSrcConnectionId" = "$srcAltConnectionId" ] || { echo 'src path config update failed'; exit 1; } -[ "$pathDstConnectionId" = "$dstAltConnectionId" ] || { echo 'dst path config update failed'; exit 1; } +checkResult "$srcAltConnectionId" "$dstAltConnectionId" # test channel upgrade (non-crossing-hello) -$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstOrigConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 --ordering UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 --ordering UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc1 --ordering UNORDERED --connection-hops $dstOrigConnectionId --version ics20-1 $RLY tx channel-upgrade execute ibc01 -pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') -pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') -[ "$pathSrcConnectionId" = "$srcOrigConnectionId" ] || { echo 'src path config update failed'; exit 1; } -[ "$pathDstConnectionId" = "$dstOrigConnectionId" ] || { echo 'dst path config update failed'; exit 1; } +checkResult "$srcOrigConnectionId" "$dstOrigConnectionId" # test channel upgrade cancel -$RLY tx channel-upgrade init ibc01 ibc0 --ordering ORDER_UNORDERED --connection-hops $srcAltConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc1 --ordering ORDER_UNORDERED --connection-hops $dstAltConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 --ordering UNORDERED --connection-hops $srcAltConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc1 --ordering UNORDERED --connection-hops $dstAltConnectionId --version ics20-1 $RLY tx channel-upgrade cancel ibc01 ibc0 # create situation where ibc0.error_receipt.sequence >= ibc1.channel.upgrade_sequence $RLY tx channel-upgrade execute ibc01 # the channel upgrade of ibc1 should be cancelled -pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') -pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') -[ "$pathSrcConnectionId" = "$srcOrigConnectionId" ] || { echo 'src path config update failed'; exit 1; } -[ "$pathDstConnectionId" = "$dstOrigConnectionId" ] || { echo 'dst path config update failed'; exit 1; } +checkResult "$srcOrigConnectionId" "$dstOrigConnectionId" + From f304f8206fecd7ef7ae1a7c179ddc0abbccd2829 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 21 Aug 2024 00:39:43 +0900 Subject: [PATCH 26/47] add ibc-mock-app to simd Signed-off-by: Masanori Yoshida --- tests/chains/tendermint/go.mod | 1 + tests/chains/tendermint/go.sum | 2 ++ tests/chains/tendermint/simapp/app.go | 27 +++++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/chains/tendermint/go.mod b/tests/chains/tendermint/go.mod index a753b290..91ad795f 100644 --- a/tests/chains/tendermint/go.mod +++ b/tests/chains/tendermint/go.mod @@ -21,6 +21,7 @@ require ( github.com/cosmos/gogoproto v1.4.11 github.com/cosmos/ibc-go/modules/capability v1.0.0 github.com/cosmos/ibc-go/v8 v8.2.0 + github.com/datachainlab/ibc-mock-app v0.1.0 github.com/datachainlab/ibc-mock-client v0.4.1 github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 diff --git a/tests/chains/tendermint/go.sum b/tests/chains/tendermint/go.sum index 49f456f6..e9887c66 100644 --- a/tests/chains/tendermint/go.sum +++ b/tests/chains/tendermint/go.sum @@ -383,6 +383,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/datachainlab/ibc-mock-app v0.1.0 h1:lWgNj+7JlLsV4fDnp3YGvknRt0dAr8hyoFm1JJRmcWE= +github.com/datachainlab/ibc-mock-app v0.1.0/go.mod h1:wXISY/ZuXJter33Qv7Suul0WMteDP1UlRk+HixIjAHY= github.com/datachainlab/ibc-mock-client v0.4.1 h1:FQfyFOodgnchCIicpS7Vzji3yxXDe4Jl5hmE5Vz7M1s= github.com/datachainlab/ibc-mock-client v0.4.1/go.mod h1:2wGddiF2uHFhiMBpSskzKT/wA8naXi5DLoXt1KEZA1o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/tests/chains/tendermint/simapp/app.go b/tests/chains/tendermint/simapp/app.go index 7eadff7b..80bf7226 100644 --- a/tests/chains/tendermint/simapp/app.go +++ b/tests/chains/tendermint/simapp/app.go @@ -135,6 +135,10 @@ import ( mockclient "github.com/datachainlab/ibc-mock-client/modules/light-clients/xx-mock" mockclienttypes "github.com/datachainlab/ibc-mock-client/modules/light-clients/xx-mock/types" + + mockapp "github.com/datachainlab/ibc-mock-app" + mockappkeeper "github.com/datachainlab/ibc-mock-app/keeper" + mockapptypes "github.com/datachainlab/ibc-mock-app/types" ) const appName = "SimApp" @@ -157,6 +161,7 @@ var ( stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, govtypes.ModuleName: {authtypes.Burner}, ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + mockapptypes.ModuleName: nil, ibcfeetypes.ModuleName: nil, icatypes.ModuleName: nil, ibcmock.ModuleName: nil, @@ -202,6 +207,7 @@ type SimApp struct { ICAHostKeeper icahostkeeper.Keeper EvidenceKeeper evidencekeeper.Keeper TransferKeeper ibctransferkeeper.Keeper + MockAppKeeper mockappkeeper.Keeper FeeGrantKeeper feegrantkeeper.Keeper GroupKeeper groupkeeper.Keeper ConsensusParamsKeeper consensusparamkeeper.Keeper @@ -210,6 +216,7 @@ type SimApp struct { // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper ScopedTransferKeeper capabilitykeeper.ScopedKeeper + ScopedMockAppKeeper capabilitykeeper.ScopedKeeper ScopedFeeMockKeeper capabilitykeeper.ScopedKeeper ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper ScopedICAHostKeeper capabilitykeeper.ScopedKeeper @@ -305,7 +312,7 @@ func NewSimApp( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, crisistypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, group.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, - evidencetypes.StoreKey, ibctransfertypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, capabilitytypes.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, mockapptypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, capabilitytypes.StoreKey, authzkeeper.StoreKey, ibcfeetypes.StoreKey, consensusparamtypes.StoreKey, circuittypes.StoreKey, ) @@ -349,6 +356,8 @@ func NewSimApp( scopedFeeMockKeeper := app.CapabilityKeeper.ScopeToModule(MockFeePort) scopedICAMockKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.ModuleName + icacontrollertypes.SubModuleName) + scopedMockAppKeeper := app.CapabilityKeeper.ScopeToModule(mockapptypes.ModuleName) + // seal capability keeper after scoping modules // Applications that wish to enforce statically created ScopedKeepers should call `Seal` after creating // their scoped modules in `NewApp` with `ScopeToModule` @@ -491,6 +500,12 @@ func NewSimApp( authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) + app.MockAppKeeper = mockappkeeper.NewKeeper( + appCodec, keys[mockapptypes.StoreKey], + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, + scopedMockAppKeeper, + ) + // Mock Module Stack // Mock Module setup for testing IBC and also acts as the interchain accounts authentication module @@ -510,6 +525,10 @@ func NewSimApp( mockBlockUpgradeMw := ibcmock.NewBlockUpgradeMiddleware(&mockModule, mockBlockUpgradeIBCModule.IBCApp) ibcRouter.AddRoute(ibcmock.MockBlockUpgrade, mockBlockUpgradeMw) + // mockapp Stack + mockAppStack := mockapp.NewIBCModule(app.MockAppKeeper) + ibcRouter.AddRoute(mockapptypes.ModuleName, mockAppStack) + // Create Transfer Stack // SendPacket, since it is originating from the application to core IBC: // transferKeeper.SendPacket -> fee.SendPacket -> channel.SendPacket @@ -620,6 +639,7 @@ func NewSimApp( ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), ibctm.NewAppModule(), solomachine.NewAppModule(), + mockapp.NewAppModule(app.MockAppKeeper), mockclient.NewAppModule(), mockModule, ) @@ -660,6 +680,7 @@ func NewSimApp( stakingtypes.ModuleName, ibcexported.ModuleName, ibctransfertypes.ModuleName, + mockapptypes.ModuleName, genutiltypes.ModuleName, authz.ModuleName, icatypes.ModuleName, @@ -672,6 +693,7 @@ func NewSimApp( stakingtypes.ModuleName, ibcexported.ModuleName, ibctransfertypes.ModuleName, + mockapptypes.ModuleName, capabilitytypes.ModuleName, genutiltypes.ModuleName, feegrant.ModuleName, @@ -692,7 +714,7 @@ func NewSimApp( authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName, - ibcexported.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, ibctransfertypes.ModuleName, + ibcexported.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, ibctransfertypes.ModuleName, mockapptypes.ModuleName, icatypes.ModuleName, ibcfeetypes.ModuleName, ibcmock.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, group.ModuleName, consensusparamtypes.ModuleName, circuittypes.ModuleName, } @@ -791,6 +813,7 @@ func NewSimApp( app.ScopedIBCKeeper = scopedIBCKeeper app.ScopedTransferKeeper = scopedTransferKeeper + app.ScopedMockAppKeeper = scopedMockAppKeeper app.ScopedICAControllerKeeper = scopedICAControllerKeeper app.ScopedICAHostKeeper = scopedICAHostKeeper From 3a44e81ad301e258104e828ba51e1c99b1bdd75b Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 21 Aug 2024 02:07:54 +0900 Subject: [PATCH 27/47] use mockapp instead of ics20 in tm2tm test Signed-off-by: Masanori Yoshida --- tests/cases/tm2tm/configs/path.json | 8 +-- .../cases/tm2tm/scripts/test-channel-upgrade | 66 +++++++++++++------ tests/cases/tm2tm/scripts/test-service | 17 +++-- tests/cases/tm2tm/scripts/test-tx | 15 +++-- 4 files changed, 69 insertions(+), 37 deletions(-) diff --git a/tests/cases/tm2tm/configs/path.json b/tests/cases/tm2tm/configs/path.json index 1cf5cddc..2d008d30 100644 --- a/tests/cases/tm2tm/configs/path.json +++ b/tests/cases/tm2tm/configs/path.json @@ -4,18 +4,18 @@ "client-id": "", "connection-id": "", "channel-id": "", - "port-id": "transfer", + "port-id": "mockapp", "order": "unordered", - "version": "ics20-1" + "version": "mockapp-1" }, "dst": { "chain-id": "ibc1", "client-id": "", "connection-id": "", "channel-id": "", - "port-id": "transfer", + "port-id": "mockapp", "order": "unordered", - "version": "ics20-1" + "version": "mockapp-1" }, "strategy": { "type": "naive" diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade index 4ee9827c..1623d9f6 100755 --- a/tests/cases/tm2tm/scripts/test-channel-upgrade +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -17,6 +17,10 @@ PATH_NAME=ibc01 # back up the original connection identifiers srcOrigConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') dstOrigConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') +srcOrigVersion=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."version"') +dstOrigVersion=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."version"') +srcOrigOrder=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."order"') +dstOrigOrder=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."order"') # back up the original config.json and make connection identifiers empty origconfig=`mktemp` @@ -28,43 +32,65 @@ $RLY paths edit $PATH_NAME dst connection-id '' retry 5 $RLY tx connection $PATH_NAME srcAltConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') dstAltConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') +srcAltVersion=mockapp-999 +dstAltVersion=mockapp-999 +srcAltOrder=ordered +dstAltOrder=ordered # resume the original config.json mv $origconfig "$RELAYER_CONF/config/config.json" checkResult() { - expectedSrcConnectionId=$1 - expectedDstConnectionId=$2 - pathSrcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') - pathDstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') - if [ "$pathSrcConnectionId" != "$expectedSrcConnectionId" ] + expectedSide=$1 + + srcConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."connection-id"') + dstConnectionId=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."connection-id"') + srcVersion=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."version"') + dstVersion=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."version"') + srcOrder=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].src."order"') + dstOrder=$($RLY paths list --json | jq --raw-output --arg path_name "$PATH_NAME" '.[$path_name].dst."order"') + + if [ "$expectedSide" = orig ] then - echo 'src path config update failed' - exit 1 - elif [ "$pathDstConnectionId" != "$expectedDstConnectionId" ] + if [ "$srcConnectionId" != "$srcOrigConnectionId" -o "$dstConnectionId" != "$dstOrigConnectionId" -o "$srcVersion" != "$srcOrigVersion" -o "$dstVersion" != "$dstOrigVersion" -o "$srcOrder" != "$srcOrigOrder" -o "$dstOrder" != "$dstOrigOrder" ] + then + echo "path config is not equal to the original one: $srcConnectionId, $dstConnectionId, $srcVersion, $dstVersion, $srcOrder, $dstOrder" + exit 1 + fi + elif [ "$expectedSide" = alt ] then - echo 'dst path config update failed' + if [ "$srcConnectionId" != "$srcAltConnectionId" -o "$dstConnectionId" != "$dstAltConnectionId" -o "$srcVersion" != "$srcAltVersion" -o "$dstVersion" != "$dstAltVersion" -o "$srcOrder" != "$srcAltOrder" -o "$dstOrder" != "$dstAltOrder" ] + then + echo "path config is not equal to the alternative one: $srcConnectionId, $dstConnectionId, $srcVersion, $dstVersion, $srcOrder, $dstOrder" + exit 1 + fi + else + echo "expectedSide is invalid value: $expectedSide" exit 1 fi } +origSrcOpts="--ordering $srcOrigOrder --connection-hops $srcOrigConnectionId --version $srcOrigVersion" +origDstOpts="--ordering $dstOrigOrder --connection-hops $dstOrigConnectionId --version $dstOrigVersion" +altSrcOpts="--ordering $srcAltOrder --connection-hops $srcAltConnectionId --version $srcAltVersion" +altDstOpts="--ordering $dstAltOrder --connection-hops $dstAltConnectionId --version $dstAltVersion" + # test channel upgrade (crossing-hello) -$RLY tx channel-upgrade init ibc01 ibc0 --ordering unordered --connection-hops $srcAltConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc1 --ordering UnOrdered --connection-hops $dstAltConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts +$RLY tx channel-upgrade init ibc01 ibc1 $altDstOpts $RLY tx channel-upgrade execute ibc01 -checkResult "$srcAltConnectionId" "$dstAltConnectionId" +checkResult alt # test channel upgrade (non-crossing-hello) -$RLY tx channel-upgrade init ibc01 ibc0 --ordering UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc0 --ordering UNORDERED --connection-hops $srcOrigConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc1 --ordering UNORDERED --connection-hops $dstOrigConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 $origSrcOpts +$RLY tx channel-upgrade init ibc01 ibc0 $origSrcOpts +$RLY tx channel-upgrade init ibc01 ibc1 $origDstOpts $RLY tx channel-upgrade execute ibc01 -checkResult "$srcOrigConnectionId" "$dstOrigConnectionId" +checkResult orig # test channel upgrade cancel -$RLY tx channel-upgrade init ibc01 ibc0 --ordering UNORDERED --connection-hops $srcAltConnectionId --version ics20-1 -$RLY tx channel-upgrade init ibc01 ibc1 --ordering UNORDERED --connection-hops $dstAltConnectionId --version ics20-1 +$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts +$RLY tx channel-upgrade init ibc01 ibc1 $altDstOpts $RLY tx channel-upgrade cancel ibc01 ibc0 # create situation where ibc0.error_receipt.sequence >= ibc1.channel.upgrade_sequence $RLY tx channel-upgrade execute ibc01 # the channel upgrade of ibc1 should be cancelled -checkResult "$srcOrigConnectionId" "$dstOrigConnectionId" - +checkResult orig diff --git a/tests/cases/tm2tm/scripts/test-service b/tests/cases/tm2tm/scripts/test-service index 8b8a08f8..1c097e16 100755 --- a/tests/cases/tm2tm/scripts/test-service +++ b/tests/cases/tm2tm/scripts/test-service @@ -50,8 +50,9 @@ RLY_PID=$! echo "xxxxxxx ${SECONDS} relay service [packets = 0, time = 0] -> skip xxxxxx" sleep 4 -# transfer a token -${RLY} tx transfer ibc01 ibc0 ibc1 100samoleans ${TM_ADDRESS1} +# send a message via mockapp and wait for tx to be included +docker exec tendermint-chain0 sh -c "simd --home /root/data/ibc0 tx --keyring-backend=test --from ${TM_ADDRESS0} --chain-id ibc0 mockapp send mockapp channel-0 'mock packet data' --yes" +sleep 3 expectUnrelayedCount "unrelayed-packets" "src" 1 expectUnrelayedCount "unrelayed-acknowledgements" "dst" 0 @@ -60,7 +61,8 @@ sleep 13 echo "xxxxxxx ${SECONDS} relay service [packets = 1, time = 20] -> skip xxxxxx" sleep 4 -${RLY} tx transfer ibc01 ibc0 ibc1 100samoleans ${TM_ADDRESS1} +docker exec tendermint-chain0 sh -c "simd --home /root/data/ibc0 tx --keyring-backend=test --from ${TM_ADDRESS0} --chain-id ibc0 mockapp send mockapp channel-0 'mock packet data' --yes" +sleep 3 expectUnrelayedCount "unrelayed-packets" "src" 2 expectUnrelayedCount "unrelayed-acknowledgements" "dst" 0 @@ -72,9 +74,12 @@ sleep 4 expectUnrelayedCount "unrelayed-packets" "src" 0 expectUnrelayedCount "unrelayed-acknowledgements" "dst" 2 -${RLY} tx transfer ibc01 ibc0 ibc1 100samoleans ${TM_ADDRESS1} -${RLY} tx transfer ibc01 ibc0 ibc1 100samoleans ${TM_ADDRESS1} -${RLY} tx transfer ibc01 ibc0 ibc1 100samoleans ${TM_ADDRESS1} +docker exec tendermint-chain0 sh -c "simd --home /root/data/ibc0 tx --keyring-backend=test --from ${TM_ADDRESS0} --chain-id ibc0 mockapp send mockapp channel-0 'mock packet data' --yes" +sleep 3 +docker exec tendermint-chain0 sh -c "simd --home /root/data/ibc0 tx --keyring-backend=test --from ${TM_ADDRESS0} --chain-id ibc0 mockapp send mockapp channel-0 'mock packet data' --yes" +sleep 3 +docker exec tendermint-chain0 sh -c "simd --home /root/data/ibc0 tx --keyring-backend=test --from ${TM_ADDRESS0} --chain-id ibc0 mockapp send mockapp channel-0 'mock packet data' --yes" +sleep 3 expectUnrelayedCount "unrelayed-packets" "src" 3 expectUnrelayedCount "unrelayed-acknowledgements" "dst" 2 diff --git a/tests/cases/tm2tm/scripts/test-tx b/tests/cases/tm2tm/scripts/test-tx index 49c5e65f..62005305 100755 --- a/tests/cases/tm2tm/scripts/test-tx +++ b/tests/cases/tm2tm/scripts/test-tx @@ -14,15 +14,16 @@ TM_ADDRESS1=$(${RLY} tendermint keys show ibc1 testkey) echo "!!! ibc0 -> ibc1 !!!" -echo "Before ibc0 balance: $(${RLY} query balance ibc0 ${TM_ADDRESS0})" -echo "Before ibc1 balance: $(${RLY} query balance ibc1 ${TM_ADDRESS1})" - -${RLY} tx transfer ibc01 ibc0 ibc1 100samoleans ${TM_ADDRESS1} +docker exec tendermint-chain0 sh -c "simd --home /root/data/ibc0 tx --keyring-backend=test --from ${TM_ADDRESS0} --chain-id ibc0 mockapp send mockapp channel-0 'mock packet data' --yes" sleep ${TX_INTERNAL} ${RLY} tx relay --do-refresh ibc01 --src-seqs 1 sleep ${TX_INTERNAL} ${RLY} tx acks --do-refresh ibc01 --dst-seqs 1 -sleep ${TX_INTERNAL} -echo "After ibc0 balance: $(${RLY} query balance ibc0 ${TM_ADDRESS0})" -echo "After ibc1 balance: $(${RLY} query balance ibc1 ${TM_ADDRESS1})" +echo "!!! ibc1 -> ibc0 !!!" + +docker exec tendermint-chain1 sh -c "simd --home /root/data/ibc1 tx --keyring-backend=test --from ${TM_ADDRESS1} --chain-id ibc1 mockapp send mockapp channel-0 'mock packet data' --yes" +sleep ${TX_INTERNAL} +${RLY} tx relay --do-refresh ibc01 --dst-seqs 1 +sleep ${TX_INTERNAL} +${RLY} tx acks --do-refresh ibc01 --src-seqs 1 From 5ea24b4ddab913369a9851e68ad3b391ffa2952a Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 21 Aug 2024 04:24:23 +0900 Subject: [PATCH 28/47] add more test cases to test-channel-upgrade Signed-off-by: Masanori Yoshida --- .../cases/tm2tm/scripts/test-channel-upgrade | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade index 1623d9f6..0a86569f 100755 --- a/tests/cases/tm2tm/scripts/test-channel-upgrade +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -75,22 +75,67 @@ origDstOpts="--ordering $dstOrigOrder --connection-hops $dstOrigConnectionId --v altSrcOpts="--ordering $srcAltOrder --connection-hops $srcAltConnectionId --version $srcAltVersion" altDstOpts="--ordering $dstAltOrder --connection-hops $dstAltConnectionId --version $dstAltVersion" -# test channel upgrade (crossing-hello) +echo '##### case 1 #####' $RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts $RLY tx channel-upgrade init ibc01 ibc1 $altDstOpts $RLY tx channel-upgrade execute ibc01 checkResult alt -# test channel upgrade (non-crossing-hello) +echo '##### case 2 #####' $RLY tx channel-upgrade init ibc01 ibc0 $origSrcOpts $RLY tx channel-upgrade init ibc01 ibc0 $origSrcOpts $RLY tx channel-upgrade init ibc01 ibc1 $origDstOpts $RLY tx channel-upgrade execute ibc01 checkResult orig -# test channel upgrade cancel +echo '##### case 3 #####' $RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts $RLY tx channel-upgrade init ibc01 ibc1 $altDstOpts -$RLY tx channel-upgrade cancel ibc01 ibc0 # create situation where ibc0.error_receipt.sequence >= ibc1.channel.upgrade_sequence -$RLY tx channel-upgrade execute ibc01 # the channel upgrade of ibc1 should be cancelled +$RLY tx channel-upgrade cancel ibc01 ibc0 # ibc0 returns to UNINIT. ibc0.error_receipt.sequence >= ibc1.channel.upgrade_sequence +$RLY tx channel-upgrade execute ibc01 # ibc1's upgrade should be cancelled checkResult orig + +echo '##### case 4 #####' +$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts +$RLY tx channel-upgrade execute ibc01 --target-src-state INIT --target-dst-state FLUSHING +$RLY tx channel-upgrade cancel ibc01 ibc0 # ibc0 returns to UNINIT. ibc1 is FLUSHING. +$RLY tx channel-upgrade execute ibc01 # ibc1's upgrade should be cancelled +checkResult orig + +echo '##### case 5 #####' +$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts +$RLY tx channel-upgrade execute ibc01 --target-src-state INIT --target-dst-state FLUSHING +$RLY tx channel-upgrade cancel ibc01 ibc0 # ibc0 returns to UNINIT. ibc1 is FLUSHING. +$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts # ibc0 re-initiates new upgrade. +$RLY tx channel-upgrade execute ibc01 # The upgrade initiated by ibc0 should be completed after ibc1's one is cancelled. +checkResult alt + +echo '##### case 6 #####' +$RLY tx channel-upgrade init ibc01 ibc0 $origSrcOpts +$RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHCOMPLETE --target-dst-state FLUSHING +$RLY tx channel-upgrade cancel ibc01 ibc1 # ibc1 returns to UNINIT. ibc0 is FLUSHCOMPLETE. +$RLY tx channel-upgrade execute ibc01 # ibc0's upgrade (in FLUSHCOMPLETE) should be cancelled. +checkResult alt + +echo '##### case 7 #####' +$RLY tx channel-upgrade init ibc01 ibc0 $origSrcOpts +$RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHCOMPLETE --target-dst-state FLUSHING +$RLY tx channel-upgrade cancel ibc01 ibc1 # ibc1 returns to UNINIT. ibc0 is FLUSHCOMPLETE. +$RLY tx channel-upgrade init ibc01 ibc1 $origDstOpts # ibc1 re-initiates new upgrade. +$RLY tx channel-upgrade execute ibc01 # The upgrade initiated by ibc1 should be completed after ibc0's one is cancelled. +checkResult orig + +# echo '##### case 8 #####' +# $RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts +# $RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHCOMPLETE --target-dst-state FLUSHING +# sleep ??? +# $RLY tx channel-upgrade execute +# checkResult orig + +# echo '##### case 9 #####' +# $RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts +# $RLY tx channel-upgrade init ibc01 ibc1 $altDstOpts +# $RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHING --target-dst-state FLUSHING +# sleep ??? +# $RLY tx channel-upgrade execute +# checkResult orig From 3d197e429ce044b9cc9976003272b9217fbe6efa Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 21 Aug 2024 05:37:33 +0900 Subject: [PATCH 29/47] add more test cases related to timeout Signed-off-by: Masanori Yoshida --- .../cases/tm2tm/scripts/test-channel-upgrade | 28 +++++++++---------- tests/chains/tendermint/scripts/entrypoint.sh | 1 + tests/chains/tendermint/simapp/app.go | 10 ++++++- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade index 0a86569f..6a16d4eb 100755 --- a/tests/cases/tm2tm/scripts/test-channel-upgrade +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -125,17 +125,17 @@ $RLY tx channel-upgrade init ibc01 ibc1 $origDstOpts # ibc1 re-initiates new upg $RLY tx channel-upgrade execute ibc01 # The upgrade initiated by ibc1 should be completed after ibc0's one is cancelled. checkResult orig -# echo '##### case 8 #####' -# $RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts -# $RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHCOMPLETE --target-dst-state FLUSHING -# sleep ??? -# $RLY tx channel-upgrade execute -# checkResult orig - -# echo '##### case 9 #####' -# $RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts -# $RLY tx channel-upgrade init ibc01 ibc1 $altDstOpts -# $RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHING --target-dst-state FLUSHING -# sleep ??? -# $RLY tx channel-upgrade execute -# checkResult orig +echo '##### case 8 #####' +$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts +$RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHCOMPLETE --target-dst-state FLUSHING +sleep 20 # ibc1 exceeds upgrade.timeout.timestamp +$RLY tx channel-upgrade execute ibc01 # ibc0 <= chanUpgradeTimeout, ibc1 <= chanUpgradeCancel +checkResult orig + +echo '##### case 9 #####' +$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts +$RLY tx channel-upgrade init ibc01 ibc1 $altDstOpts +$RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHING --target-dst-state FLUSHING +sleep 20 # Both chains exceed upgrade.timeout.timestamp +$RLY tx channel-upgrade execute ibc01 # ibc0,ibc1 <= chanUpgradeTimeout +checkResult orig diff --git a/tests/chains/tendermint/scripts/entrypoint.sh b/tests/chains/tendermint/scripts/entrypoint.sh index b34f52e0..c92e58f8 100755 --- a/tests/chains/tendermint/scripts/entrypoint.sh +++ b/tests/chains/tendermint/scripts/entrypoint.sh @@ -1,5 +1,6 @@ #!/bin/sh export IBC_AUTHORITY=`jq -r .address /root/${CHAINDIR}/${CHAINID}/key_seed.json` +export IBC_CHANNEL_UPGRADE_TIMEOUT=20000000000 # 20sec = 20_000_000_000nsec simd --home /root/${CHAINDIR}/${CHAINID} start --pruning=nothing --grpc.address="0.0.0.0:${GRPCPORT}" diff --git a/tests/chains/tendermint/simapp/app.go b/tests/chains/tendermint/simapp/app.go index 80bf7226..ee73ca1f 100644 --- a/tests/chains/tendermint/simapp/app.go +++ b/tests/chains/tendermint/simapp/app.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "strconv" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/gogoproto/proto" @@ -896,8 +897,15 @@ func (app *SimApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*ab ibcGenesisState.ClientGenesis.Params.AllowedClients = append( ibcGenesisState.ClientGenesis.Params.AllowedClients, mockclienttypes.Mock) - genesisState[ibcexported.ModuleName] = app.appCodec.MustMarshalJSON(&ibcGenesisState) } + if upgTimeout := os.Getenv("IBC_CHANNEL_UPGRADE_TIMEOUT"); len(upgTimeout) > 0 { + upgTimeoutTimestampNsec, err := strconv.ParseInt(upgTimeout, 10, 64) + if err != nil { + panic(err) + } + ibcGenesisState.ChannelGenesis.Params.UpgradeTimeout.Timestamp = uint64(upgTimeoutTimestampNsec) + } + genesisState[ibcexported.ModuleName] = app.appCodec.MustMarshalJSON(&ibcGenesisState) } if err := app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()); err != nil { From 57638133af26e82965d701d17023f3f545784031 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 21 Aug 2024 07:08:17 +0900 Subject: [PATCH 30/47] remove `upgradeSequence` parameter of `Chain::QueryChannelUpgradeError` Signed-off-by: Masanori Yoshida --- chains/tendermint/query.go | 24 ++++----------------- core/chain.go | 6 +++--- core/channel-upgrade.go | 44 ++++++++++++++++++++++++++------------ core/query.go | 7 +++--- 4 files changed, 40 insertions(+), 41 deletions(-) diff --git a/chains/tendermint/query.go b/chains/tendermint/query.go index 4ce8bbd5..3a8e900d 100644 --- a/chains/tendermint/query.go +++ b/chains/tendermint/query.go @@ -399,29 +399,13 @@ func (c *Chain) queryChannelUpgrade(height int64, prove bool) (chanRes *chantype } } -func (c *Chain) QueryChannelUpgradeError(ctx core.QueryContext, upgradeSequence uint64) (*chantypes.QueryUpgradeErrorResponse, error) { - return c.queryChannelUpgradeError(int64(ctx.Height().GetRevisionHeight()), upgradeSequence, false) +func (c *Chain) QueryChannelUpgradeError(ctx core.QueryContext) (*chantypes.QueryUpgradeErrorResponse, error) { + return c.queryChannelUpgradeError(int64(ctx.Height().GetRevisionHeight()), false) } -func (c *Chain) queryChannelUpgradeError(height int64, upgradeSequence uint64, prove bool) (chanRes *chantypes.QueryUpgradeErrorResponse, err error) { - var errReceiptHeight int64 - if upgradeSequence == 0 { - errReceiptHeight = height - } else { - txs, err := c.QueryTxs(height, 1, 2, channelUpgradeErrorQuery(c.Path().ChannelID, upgradeSequence)) - switch { - case err != nil: - return nil, err - case len(txs) == 0: - return nil, fmt.Errorf("no transactions returned with query") - case len(txs) > 1: - return nil, fmt.Errorf("more than one transaction returned with query") - } - errReceiptHeight = txs[0].Height - } - +func (c *Chain) queryChannelUpgradeError(height int64, prove bool) (chanRes *chantypes.QueryUpgradeErrorResponse, err error) { if res, err := chanutils.QueryUpgradeError( - c.CLIContext(errReceiptHeight), + c.CLIContext(height), c.PathEnd.PortID, c.PathEnd.ChannelID, prove, diff --git a/core/chain.go b/core/chain.go index 568873d3..684b4fcf 100644 --- a/core/chain.go +++ b/core/chain.go @@ -159,9 +159,9 @@ type ICS04Querier interface { // QueryChannelUpgrade returns the channel upgrade associated with a channelID QueryChannelUpgrade(ctx QueryContext) (*chantypes.QueryUpgradeResponse, error) - // QueryChannelUpgradeError iterates through chain events in reverse chronological order from the height of `ctx` and returns the error receipt that matches `upgradeSequence`. - // If zero is specified as `upgradeSequence`, this function simply returns the error receipt stored at the height of `ctx`. - QueryChannelUpgradeError(ctx QueryContext, upgradeSequence uint64) (*chantypes.QueryUpgradeErrorResponse, error) + // QueryChannelUpgradeError returns the channel upgrade error receipt associated with a channelID at the height of `ctx`. + // WARN: This error receipt may not be used to cancel upgrade in FLUSHCOMPLETE state because of upgrade sequence mismatch. + QueryChannelUpgradeError(ctx QueryContext) (*chantypes.QueryUpgradeErrorResponse, error) // QueryCanTransitionToFlushComplete returns the channel can transition to FLUSHCOMPLETE state. // Basically it requires that there remains no inflight packets. diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index bea6cf11..705b5565 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -180,26 +180,31 @@ func CancelChannelUpgrade(chain, cp *ProvableChain) error { return err } - var errReceiptSequence uint64 = 0 - if chann.Channel.State == chantypes.FLUSHCOMPLETE { - errReceiptSequence = chann.Channel.UpgradeSequence - } - - var msg sdk.Msg - if upgErr, err := cp.QueryChannelUpgradeError(cpCtx, errReceiptSequence); err != nil { + upgErr, err := QueryChannelUpgradeError(cpCtx, cp, true) + if err != nil { logger.Error("failed to query the channel upgrade error receipt", err) return err + } else if chann.Channel.State == chantypes.FLUSHCOMPLETE && + (upgErr == nil || upgErr.ErrorReceipt.Sequence != chann.Channel.UpgradeSequence) { + var err error + if upgErr == nil { + err = fmt.Errorf("upgrade error receipt not found") + } else { + err = fmt.Errorf("upgrade sequences don't match: channel.upgrade_sequence=%d, error_receipt.sequence=%d", + chann.Channel.UpgradeSequence, upgErr.ErrorReceipt.Sequence) + } + logger.Error("cannot cancel the upgrade in FLUSHCOMPLETE state", err) + return err } else if upgErr == nil { // NOTE: Even if an error receipt is not found, anyway try to execute ChanUpgradeCancel. // If the sender is authority and the channel state is anything other than FLUSHCOMPLETE, // the cancellation will be successful. - msg = chain.Path().ChanUpgradeCancel(&chantypes.QueryUpgradeErrorResponse{}, addr) - } else { - msg = chain.Path().ChanUpgradeCancel(upgErr, addr) + upgErr = &chantypes.QueryUpgradeErrorResponse{} } - msgIDs, err := chain.SendMsgs([]sdk.Msg{msg}) - if err != nil { + msg := chain.Path().ChanUpgradeCancel(upgErr, addr) + + if msgIDs, err := chain.SendMsgs([]sdk.Msg{msg}); err != nil { logger.Error("failed to send MsgChannelUpgradeCancel", err) return err } else if len(msgIDs) != 1 { @@ -667,15 +672,26 @@ func buildActionMsg( case UPGRADE_ACTION_OPEN: return pathEnd.ChanUpgradeOpen(cpChan, addr), nil case UPGRADE_ACTION_CANCEL: - upgErr, err := QueryChannelUpgradeError(cpCtx, cp, 0, true) + upgErr, err := QueryChannelUpgradeError(cpCtx, cp, true) if err != nil { return nil, err + } else if upgErr == nil { + // NOTE: Even if an error receipt is not found, anyway try to execute ChanUpgradeCancel. + // If the sender is authority and the channel state is anything other than FLUSHCOMPLETE, + // the cancellation will be successful. + upgErr = &chantypes.QueryUpgradeErrorResponse{} } return pathEnd.ChanUpgradeCancel(upgErr, addr), nil case UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE: - upgErr, err := QueryChannelUpgradeError(cpCtx, cp, selfChan.Channel.UpgradeSequence, true) + upgErr, err := QueryChannelUpgradeError(cpCtx, cp, true) if err != nil { return nil, err + } else if upgErr == nil { + return nil, fmt.Errorf("upgrade error receipt not found") + } else if upgErr.ErrorReceipt.Sequence != selfChan.Channel.UpgradeSequence { + return nil, fmt.Errorf( + "upgrade sequences don't match: channel.upgrade_sequence=%d, error_receipt.sequence=%d", + selfChan.Channel.UpgradeSequence, upgErr.ErrorReceipt.Sequence) } return pathEnd.ChanUpgradeCancel(upgErr, addr), nil case UPGRADE_ACTION_TIMEOUT: diff --git a/core/query.go b/core/query.go index 41ff4784..7e3c9fcd 100644 --- a/core/query.go +++ b/core/query.go @@ -297,8 +297,8 @@ func QueryChannelUpgradePair(srcCtx, dstCtx QueryContext, src, dst interface { func QueryChannelUpgradeError(ctx QueryContext, chain interface { Chain StateProver -}, upgradeSequence uint64, prove bool) (*chantypes.QueryUpgradeErrorResponse, error) { - if chanUpgErr, err := chain.QueryChannelUpgradeError(ctx, upgradeSequence); err != nil { +}, prove bool) (*chantypes.QueryUpgradeErrorResponse, error) { + if chanUpgErr, err := chain.QueryChannelUpgradeError(ctx); err != nil { return nil, err } else if chanUpgErr == nil { return nil, nil @@ -307,9 +307,8 @@ func QueryChannelUpgradeError(ctx QueryContext, chain interface { } else if value, err := chain.Codec().Marshal(&chanUpgErr.ErrorReceipt); err != nil { return nil, err } else { - proveCtx := NewQueryContext(ctx.Context(), chanUpgErr.ProofHeight) path := host.ChannelUpgradeErrorPath(chain.Path().PortID, chain.Path().ChannelID) - chanUpgErr.Proof, chanUpgErr.ProofHeight, err = chain.ProveState(proveCtx, path, value) + chanUpgErr.Proof, chanUpgErr.ProofHeight, err = chain.ProveState(ctx, path, value) return chanUpgErr, err } } From a60f6e206fbe0e55b788eaf92943579e0927558a Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 21 Aug 2024 12:41:21 +0900 Subject: [PATCH 31/47] add `yrly query channel-upgrade` subcommand Signed-off-by: Masanori Yoshida --- cmd/query.go | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/cmd/query.go b/cmd/query.go index 2a79b262..57c00ac9 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -7,13 +7,13 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/gogoproto/jsonpb" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" "github.com/hyperledger-labs/yui-relayer/config" "github.com/hyperledger-labs/yui-relayer/core" "github.com/hyperledger-labs/yui-relayer/helpers" "github.com/spf13/cobra" - "github.com/cosmos/gogoproto/jsonpb" ) // queryCmd represents the chain command @@ -33,6 +33,7 @@ func queryCmd(ctx *config.Context) *cobra.Command { queryClientCmd(ctx), queryConnection(ctx), queryChannel(ctx), + queryChannelUpgrade(ctx), ) return cmd @@ -121,7 +122,7 @@ func queryConnection(ctx *config.Context) *cobra.Command { func queryChannel(ctx *config.Context) *cobra.Command { cmd := &cobra.Command{ Use: "channel [path-name] [chain-id]", - Short: "Query the connection state for the given connection id", + Short: "Query the channel state for the given connection id", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { chains, _, _, err := ctx.Config.ChainsFromPath(args[0]) @@ -143,6 +144,7 @@ func queryChannel(ctx *config.Context) *cobra.Command { if err != nil { return err } + marshaler := jsonpb.Marshaler{} if json, err := marshaler.MarshalToString(res.Channel); err != nil { fmt.Println(res.Channel.String()) @@ -156,6 +158,47 @@ func queryChannel(ctx *config.Context) *cobra.Command { return heightFlag(cmd) } +func queryChannelUpgrade(ctx *config.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "channel-upgrade [path-name] [chain-id]", + Short: "Query the channel upgrade state for the given channel id", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + chains, _, _, err := ctx.Config.ChainsFromPath(args[0]) + if err != nil { + return err + } + c := chains[args[1]] + + height, err := cmd.Flags().GetUint64(flags.FlagHeight) + if err != nil { + return err + } + latestHeight, err := c.LatestHeight() + if err != nil { + return err + } + queryHeight := clienttypes.NewHeight(latestHeight.GetRevisionNumber(), uint64(height)) + res, err := c.QueryChannelUpgrade(core.NewQueryContext(context.TODO(), queryHeight)) + if err != nil { + return err + } else if res == nil { + return fmt.Errorf("failed to query for channel upgrade") + } + + marshaler := jsonpb.Marshaler{} + if json, err := marshaler.MarshalToString(&res.Upgrade); err != nil { + fmt.Println(res.Upgrade.String()) + } else { + fmt.Println(json) + } + return nil + }, + } + + return heightFlag(cmd) +} + func queryBalanceCmd(ctx *config.Context) *cobra.Command { cmd := &cobra.Command{ Use: "balance [chain-id] [address]", From f776f949b823e73c44812f2bd270ee014a622334 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 23 Aug 2024 00:14:05 +0900 Subject: [PATCH 32/47] delete use of logger.With in upgradeChannelStep Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 705b5565..0125ee12 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -3,7 +3,6 @@ package core import ( "errors" "fmt" - "log/slog" "time" retry "github.com/avast/retry-go" @@ -332,18 +331,6 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState return out, nil } - // add info to logger - logger = logger.With( - slog.Group("src", - "state", srcState, - "seq", srcChan.Channel.UpgradeSequence, - ), - slog.Group("dst", - "state", dstState, - "seq", dstChan.Channel.UpgradeSequence, - ), - ) - // determine next actions for src/dst chains srcAction := UPGRADE_ACTION_NONE dstAction := UPGRADE_ACTION_NONE From b25de5e24115f15bd2673280ec4a58f85325dc15 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 23 Aug 2024 16:08:38 +0900 Subject: [PATCH 33/47] add log outputs in channel upgrade functions Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 50 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 0125ee12..14cac9ce 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -3,12 +3,14 @@ package core import ( "errors" "fmt" + "log/slog" "time" retry "github.com/avast/retry-go" sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" chantypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + "github.com/hyperledger-labs/yui-relayer/log" ) type UpgradeState int @@ -96,6 +98,7 @@ func InitChannelUpgrade(chain *ProvableChain, upgradeFields chantypes.UpgradeFie } else if ok, desc := result.Status(); !ok { err := fmt.Errorf("failed to initialize channel upgrade: %s", desc) logger.Error(err.Error(), err) + return err } else { logger.Info("successfully initialized channel upgrade") } @@ -135,6 +138,7 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti if steps.Success() { if err := SyncChainConfigsFromEvents(pathName, steps.SrcMsgIDs, steps.DstMsgIDs, src, dst); err != nil { + logger.Error("failed to synchronize the updated path config to the config file", err) return err } @@ -142,7 +146,7 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti failures = 0 } else { if failures++; failures > 2 { - err := errors.New("Channel upgrade failed") + err := errors.New("channel upgrade failed") logger.Error(err.Error(), err) return err } @@ -214,6 +218,7 @@ func CancelChannelUpgrade(chain, cp *ProvableChain) error { } else if ok, desc := result.Status(); !ok { err := fmt.Errorf("failed to cancel the channel upgrade: %s", desc) logger.Error(err.Error(), err) + return err } else { logger.Info("successfully cancelled the channel upgrade") } @@ -271,15 +276,19 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState // prepare query contexts var srcCtxFinalized, dstCtxFinalized, srcCtxLatest, dstCtxLatest QueryContext if srcCtxFinalized, err = getQueryContext(src, sh, true); err != nil { + logger.Error("failed to get query the context based on the src chain's latest finalized height", err) return nil, err } if dstCtxFinalized, err = getQueryContext(dst, sh, true); err != nil { + logger.Error("failed to get query the context based on the dst chain's latest finalized height", err) return nil, err } if srcCtxLatest, err = getQueryContext(src, sh, false); err != nil { + logger.Error("failed to get query the context based on the src chain's latest height", err) return nil, err } if dstCtxLatest, err = getQueryContext(dst, sh, false); err != nil { + logger.Error("failed to get query the context based on the dst chain's latest height", err) return nil, err } @@ -292,8 +301,10 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState true, ) if err != nil { + logger.Error("failed to query the channel pair with proofs", err) return nil, err } else if finalized, err := checkChannelFinality(src, dst, srcChan.Channel, dstChan.Channel); err != nil { + logger.Error("failed to check if the queried channels have been finalized", err) return nil, err } else if !finalized { return out, nil @@ -309,6 +320,7 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState dst, ) if err != nil { + logger.Error("failed to query the finalized channel upgrade pair with proofs", err) return nil, err } else if !finalized { return out, nil @@ -317,16 +329,26 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState // determine upgrade states srcState, err := NewUpgradeState(srcChan.Channel.State, srcChanUpg != nil) if err != nil { + logger.Error("failed to create UpgradeState of the src chain", err) return nil, err } dstState, err := NewUpgradeState(dstChan.Channel.State, dstChanUpg != nil) if err != nil { + logger.Error("failed to create UpgradeState of the dst chain", err) return nil, err } + logger = &log.RelayLogger{Logger: logger.With( + slog.Group("current_channel_upgrade_states", + "src", srcState.String(), + "dst", dstState.String(), + ), + )} + // check if both chains have reached the target states or UNINIT states if !firstCall && srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_UNINIT || srcState != UPGRADE_STATE_UNINIT && dstState != UPGRADE_STATE_UNINIT && srcState == targetSrcState && dstState == targetDstState { + logger.Info("both chains have reached the target states") out.Last = true return out, nil } @@ -355,10 +377,12 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState srcAction = UPGRADE_ACTION_CANCEL case srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_FLUSHCOMPLETE: if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the src side has already completed", err) return nil, err } else if complete { dstAction = UPGRADE_ACTION_OPEN } else if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the src side has already timed out", err) return nil, err } else if timedout { dstAction = UPGRADE_ACTION_TIMEOUT @@ -367,10 +391,12 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState } case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_UNINIT: if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the dst side has already completed", err) return nil, err } else if complete { srcAction = UPGRADE_ACTION_OPEN } else if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the dst side has already timed out", err) return nil, err } else if timedout { srcAction = UPGRADE_ACTION_TIMEOUT @@ -406,10 +432,12 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState } case srcState == UPGRADE_STATE_INIT && dstState == UPGRADE_STATE_FLUSHCOMPLETE: if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the src side has already completed", err) return nil, err } else if complete { dstAction = UPGRADE_ACTION_OPEN } else if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the src side has already timed out", err) return nil, err } else if timedout { dstAction = UPGRADE_ACTION_TIMEOUT @@ -418,10 +446,12 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState } case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_INIT: if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the dst side has already completed", err) return nil, err } else if complete { srcAction = UPGRADE_ACTION_OPEN } else if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the dst side has already timed out", err) return nil, err } else if timedout { srcAction = UPGRADE_ACTION_TIMEOUT @@ -430,11 +460,13 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState } case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_FLUSHING: if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the src side has already timed out", err) return nil, err } else if timedout { dstAction = UPGRADE_ACTION_TIMEOUT } if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the dst side has already timed out", err) return nil, err } else if timedout { srcAction = UPGRADE_ACTION_TIMEOUT @@ -446,31 +478,37 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState } if completable, err := src.QueryCanTransitionToFlushComplete(srcCtxFinalized); err != nil { + logger.Error("failed to check if the src channel can transition to FLUSHCOMPLETE", err) return nil, err } else if completable { srcAction = UPGRADE_ACTION_CONFIRM } if completable, err := dst.QueryCanTransitionToFlushComplete(dstCtxFinalized); err != nil { + logger.Error("failed to check if the dst channel can transition to FLUSHCOMPLETE", err) return nil, err } else if completable { dstAction = UPGRADE_ACTION_CONFIRM } case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_FLUSHCOMPLETE: if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the src side has already timed out", err) return nil, err } else if timedout { dstAction = UPGRADE_ACTION_TIMEOUT } else if completable, err := src.QueryCanTransitionToFlushComplete(srcCtxFinalized); err != nil { + logger.Error("failed to check if the src channel can transition to FLUSHCOMPLETE", err) return nil, err } else if completable { srcAction = UPGRADE_ACTION_CONFIRM } case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_FLUSHING: if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + logger.Error("failed to check if the upgrade on the dst side has already timed out", err) return nil, err } else if timedout { srcAction = UPGRADE_ACTION_TIMEOUT } else if completable, err := dst.QueryCanTransitionToFlushComplete(dstCtxFinalized); err != nil { + logger.Error("failed to check if the dst channel can transition to FLUSHCOMPLETE", err) return nil, err } else if completable { dstAction = UPGRADE_ACTION_CONFIRM @@ -482,6 +520,13 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState return nil, errors.New("unexpected state") } + logger = &log.RelayLogger{Logger: logger.With( + slog.Group("next_channel_upgrade_actions", + "src", srcAction.String(), + "dst", dstAction.String(), + ), + )} + if srcAction != UPGRADE_ACTION_NONE { addr := mustGetAddress(src) @@ -500,6 +545,7 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState dstChanUpg, ) if err != nil { + logger.Error("failed to build Msg for the src chain", err) return nil, err } @@ -524,12 +570,14 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState srcChanUpg, ) if err != nil { + logger.Error("failed to build Msg for the dst chain", err) return nil, err } out.Dst = append(out.Dst, msg) } + logger.Info("successfully generates the next step of the channel upgrade") return out, nil } From 315bb5a7c213d3e5b6990b0840756d9191b11d3e Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Sun, 25 Aug 2024 15:02:26 +0900 Subject: [PATCH 34/47] modify {Init,Cancel}ChannelUpgrade to just send a msg and not to wait for the finality of it Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 14cac9ce..6a754df7 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -86,19 +86,9 @@ func InitChannelUpgrade(chain *ProvableChain, upgradeFields chantypes.UpgradeFie msg := chain.Path().ChanUpgradeInit(upgradeFields, addr) - msgIDs, err := chain.SendMsgs([]sdk.Msg{msg}) - if err != nil { + if _, err := chain.SendMsgs([]sdk.Msg{msg}); err != nil { logger.Error("failed to send MsgChannelUpgradeInit", err) return err - } else if len(msgIDs) != 1 { - panic(fmt.Sprintf("len(msgIDs) == %d", len(msgIDs))) - } else if result, err := GetFinalizedMsgResult(*chain, msgIDs[0]); err != nil { - logger.Error("failed to get the finalized result of MsgChannelUpgradeInit", err) - return err - } else if ok, desc := result.Status(); !ok { - err := fmt.Errorf("failed to initialize channel upgrade: %s", desc) - logger.Error(err.Error(), err) - return err } else { logger.Info("successfully initialized channel upgrade") } @@ -207,18 +197,9 @@ func CancelChannelUpgrade(chain, cp *ProvableChain) error { msg := chain.Path().ChanUpgradeCancel(upgErr, addr) - if msgIDs, err := chain.SendMsgs([]sdk.Msg{msg}); err != nil { + if _, err := chain.SendMsgs([]sdk.Msg{msg}); err != nil { logger.Error("failed to send MsgChannelUpgradeCancel", err) return err - } else if len(msgIDs) != 1 { - panic(fmt.Sprintf("len(msgIDs) == %d", len(msgIDs))) - } else if result, err := GetFinalizedMsgResult(*chain, msgIDs[0]); err != nil { - logger.Error("failed to get the finalized result of MsgChannelUpgradeCancel", err) - return err - } else if ok, desc := result.Status(); !ok { - err := fmt.Errorf("failed to cancel the channel upgrade: %s", desc) - logger.Error(err.Error(), err) - return err } else { logger.Info("successfully cancelled the channel upgrade") } From eaec52a0c5556c0b97a04969ccbe099300db9b44 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 26 Aug 2024 14:54:44 +0900 Subject: [PATCH 35/47] add the `--unsafe` flag to the `tx channel-upgrade init` subcommand Signed-off-by: Masanori Yoshida --- cmd/tx.go | 30 +++++++++++++++++-- .../cases/tm2tm/scripts/test-channel-upgrade | 12 ++++---- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/cmd/tx.go b/cmd/tx.go index 4f896a02..63ad6d46 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -224,6 +224,7 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { flagOrdering = "ordering" flagConnectionHops = "connection-hops" flagVersion = "version" + flagUnsafe = "unsafe" ) cmd := cobra.Command{ @@ -234,12 +235,34 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { pathName := args[0] chainID := args[1] - if _, _, _, err := ctx.Config.ChainsFromPath(pathName); err != nil { + + chains, srcID, dstID, err := ctx.Config.ChainsFromPath(pathName) + if err != nil { return nil } - chain, err := ctx.Config.GetChain(chainID) - if err != nil { + + var chain, cp *core.ProvableChain + if chainID == srcID { + chain = chains[srcID] + cp = chains[dstID] + } else if chainID == dstID { + chain = chains[dstID] + cp = chains[srcID] + } else { + return fmt.Errorf("unknown chain ID: %s", chainID) + } + + // check cp state + if unsafe, err := cmd.Flags().GetBool(flagUnsafe); err != nil { return err + } else if !unsafe { + if height, err := cp.LatestHeight(); err != nil { + return err + } else if chann, err := cp.QueryChannel(core.NewQueryContext(cmd.Context(), height)); err != nil { + return err + } else if state := chann.Channel.State; state >= chantypes.FLUSHING && state <= chantypes.FLUSHCOMPLETE { + return fmt.Errorf("stop channel upgrade initialization because the counterparty is in %v state", state) + } } // get ordering from flags @@ -273,6 +296,7 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { cmd.Flags().String(flagOrdering, "", "channel ordering applied for the new channel") cmd.Flags().StringSlice(flagConnectionHops, nil, "connection hops applied for the new channel") cmd.Flags().String(flagVersion, "", "channel version applied for the new channel") + cmd.Flags().Bool(flagUnsafe, false, "set true if you want to allow for initializing a new channel upgrade even though the counterparty chain is still flushing packets.") return &cmd } diff --git a/tests/cases/tm2tm/scripts/test-channel-upgrade b/tests/cases/tm2tm/scripts/test-channel-upgrade index 6a16d4eb..c03db195 100755 --- a/tests/cases/tm2tm/scripts/test-channel-upgrade +++ b/tests/cases/tm2tm/scripts/test-channel-upgrade @@ -105,9 +105,9 @@ checkResult orig echo '##### case 5 #####' $RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts $RLY tx channel-upgrade execute ibc01 --target-src-state INIT --target-dst-state FLUSHING -$RLY tx channel-upgrade cancel ibc01 ibc0 # ibc0 returns to UNINIT. ibc1 is FLUSHING. -$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts # ibc0 re-initiates new upgrade. -$RLY tx channel-upgrade execute ibc01 # The upgrade initiated by ibc0 should be completed after ibc1's one is cancelled. +$RLY tx channel-upgrade cancel ibc01 ibc0 # ibc0 returns to UNINIT. ibc1 is FLUSHING. +$RLY tx channel-upgrade init ibc01 ibc0 --unsafe $altSrcOpts # ibc0 re-initiates new upgrade. +$RLY tx channel-upgrade execute ibc01 # The upgrade initiated by ibc0 should be completed after ibc1's one is cancelled. checkResult alt echo '##### case 6 #####' @@ -120,9 +120,9 @@ checkResult alt echo '##### case 7 #####' $RLY tx channel-upgrade init ibc01 ibc0 $origSrcOpts $RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHCOMPLETE --target-dst-state FLUSHING -$RLY tx channel-upgrade cancel ibc01 ibc1 # ibc1 returns to UNINIT. ibc0 is FLUSHCOMPLETE. -$RLY tx channel-upgrade init ibc01 ibc1 $origDstOpts # ibc1 re-initiates new upgrade. -$RLY tx channel-upgrade execute ibc01 # The upgrade initiated by ibc1 should be completed after ibc0's one is cancelled. +$RLY tx channel-upgrade cancel ibc01 ibc1 # ibc1 returns to UNINIT. ibc0 is FLUSHCOMPLETE. +$RLY tx channel-upgrade init ibc01 ibc1 --unsafe $origDstOpts # ibc1 re-initiates new upgrade. +$RLY tx channel-upgrade execute ibc01 # The upgrade initiated by ibc1 should be completed after ibc0's one is cancelled. checkResult orig echo '##### case 8 #####' From 0e7d2b3b2eeaee1ff0c69d88a3c08af7323d3e49 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 26 Aug 2024 14:56:40 +0900 Subject: [PATCH 36/47] finish the command successfully if channel upgrade states are transitioning to UNINIT+UNINIT before sending the first tx Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 6a754df7..d671136e 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -121,6 +121,7 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti if !steps.Ready() { logger.Debug("Waiting for next channel upgrade step ...") + firstCall = false continue } From b65f836afc3c78cb8ec89ea0e7eb957722a55352 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 26 Aug 2024 23:55:18 +0900 Subject: [PATCH 37/47] fix a bug in `tx channel-upgrade init` Signed-off-by: Masanori Yoshida --- cmd/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tx.go b/cmd/tx.go index 63ad6d46..b6a08ab3 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -238,7 +238,7 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { chains, srcID, dstID, err := ctx.Config.ChainsFromPath(pathName) if err != nil { - return nil + return err } var chain, cp *core.ProvableChain From 8ec8a0a7cf710bc9145e9f3dbbb62a9ae4685443 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Mon, 26 Aug 2024 23:57:29 +0900 Subject: [PATCH 38/47] fix `tx query client|connection|channel|channel-upgrade` to return an error if it fails to marshal the state Signed-off-by: Masanori Yoshida --- cmd/query.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/query.go b/cmd/query.go index 57c00ac9..a28e3a9a 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -70,7 +70,7 @@ func queryClientCmd(ctx *config.Context) *cobra.Command { } marshaler := jsonpb.Marshaler{} if json, err := marshaler.MarshalToString(cs); err != nil { - fmt.Println(cs.String()) + return err } else { fmt.Println(json) } @@ -108,7 +108,7 @@ func queryConnection(ctx *config.Context) *cobra.Command { } marshaler := jsonpb.Marshaler{} if json, err := marshaler.MarshalToString(res.Connection); err != nil { - fmt.Println(res.Connection.String()) + return err } else { fmt.Println(json) } @@ -147,7 +147,7 @@ func queryChannel(ctx *config.Context) *cobra.Command { marshaler := jsonpb.Marshaler{} if json, err := marshaler.MarshalToString(res.Channel); err != nil { - fmt.Println(res.Channel.String()) + return err } else { fmt.Println(json) } @@ -188,7 +188,7 @@ func queryChannelUpgrade(ctx *config.Context) *cobra.Command { marshaler := jsonpb.Marshaler{} if json, err := marshaler.MarshalToString(&res.Upgrade); err != nil { - fmt.Println(res.Upgrade.String()) + return err } else { fmt.Println(json) } From 8479e6c64bf81b82845cb2919c619d0200df707d Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Tue, 27 Aug 2024 00:56:00 +0900 Subject: [PATCH 39/47] brush up and fix logics of checkChannelFinality and checkConnectionFinality Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 160 +++++++++++++++++++--------------------- core/channel.go | 77 +++++++++++++------ core/connection.go | 77 +++++++++++++------ 3 files changed, 189 insertions(+), 125 deletions(-) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index d671136e..fc0a89fc 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -1,6 +1,7 @@ package core import ( + "context" "errors" "fmt" "log/slog" @@ -255,56 +256,30 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState return nil, err } - // prepare query contexts - var srcCtxFinalized, dstCtxFinalized, srcCtxLatest, dstCtxLatest QueryContext - if srcCtxFinalized, err = getQueryContext(src, sh, true); err != nil { - logger.Error("failed to get query the context based on the src chain's latest finalized height", err) - return nil, err - } - if dstCtxFinalized, err = getQueryContext(dst, sh, true); err != nil { - logger.Error("failed to get query the context based on the dst chain's latest finalized height", err) - return nil, err - } - if srcCtxLatest, err = getQueryContext(src, sh, false); err != nil { - logger.Error("failed to get query the context based on the src chain's latest height", err) - return nil, err - } - if dstCtxLatest, err = getQueryContext(dst, sh, false); err != nil { - logger.Error("failed to get query the context based on the dst chain's latest height", err) - return nil, err - } + srcCtx := sh.GetQueryContext(src.ChainID()) + dstCtx := sh.GetQueryContext(dst.ChainID()) // query finalized channels with proofs - srcChan, dstChan, err := QueryChannelPair( - srcCtxFinalized, - dstCtxFinalized, - src, - dst, - true, - ) + srcChan, dstChan, settled, err := querySettledChannelPair(srcCtx, dstCtx, src, dst, true) if err != nil { logger.Error("failed to query the channel pair with proofs", err) return nil, err - } else if finalized, err := checkChannelFinality(src, dst, srcChan.Channel, dstChan.Channel); err != nil { - logger.Error("failed to check if the queried channels have been finalized", err) - return nil, err - } else if !finalized { + } else if !settled { return out, nil } // query finalized channel upgrades with proofs - srcChanUpg, dstChanUpg, finalized, err := queryFinalizedChannelUpgradePair( - srcCtxFinalized, - dstCtxFinalized, - srcCtxLatest, - dstCtxLatest, + srcChanUpg, dstChanUpg, settled, err := querySettledChannelUpgradePair( + srcCtx, + dstCtx, src, dst, + true, ) if err != nil { - logger.Error("failed to query the finalized channel upgrade pair with proofs", err) + logger.Error("failed to query the channel upgrade pair with proofs", err) return nil, err - } else if !finalized { + } else if !settled { return out, nil } @@ -358,12 +333,12 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_UNINIT: srcAction = UPGRADE_ACTION_CANCEL case srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_FLUSHCOMPLETE: - if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { + if complete, err := upgradeAlreadyComplete(srcChan, dstCtx, dst, dstChanUpg); err != nil { logger.Error("failed to check if the upgrade on the src side has already completed", err) return nil, err } else if complete { dstAction = UPGRADE_ACTION_OPEN - } else if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + } else if timedout, err := upgradeAlreadyTimedOut(srcCtx, src, dstChanUpg); err != nil { logger.Error("failed to check if the upgrade on the src side has already timed out", err) return nil, err } else if timedout { @@ -372,12 +347,12 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState dstAction = UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE } case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_UNINIT: - if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { + if complete, err := upgradeAlreadyComplete(dstChan, srcCtx, src, srcChanUpg); err != nil { logger.Error("failed to check if the upgrade on the dst side has already completed", err) return nil, err } else if complete { srcAction = UPGRADE_ACTION_OPEN - } else if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + } else if timedout, err := upgradeAlreadyTimedOut(dstCtx, dst, srcChanUpg); err != nil { logger.Error("failed to check if the upgrade on the dst side has already timed out", err) return nil, err } else if timedout { @@ -413,12 +388,12 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState dstAction = UPGRADE_ACTION_ACK } case srcState == UPGRADE_STATE_INIT && dstState == UPGRADE_STATE_FLUSHCOMPLETE: - if complete, err := upgradeAlreadyComplete(srcChan, dstCtxFinalized, dst, dstChanUpg); err != nil { + if complete, err := upgradeAlreadyComplete(srcChan, dstCtx, dst, dstChanUpg); err != nil { logger.Error("failed to check if the upgrade on the src side has already completed", err) return nil, err } else if complete { dstAction = UPGRADE_ACTION_OPEN - } else if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + } else if timedout, err := upgradeAlreadyTimedOut(srcCtx, src, dstChanUpg); err != nil { logger.Error("failed to check if the upgrade on the src side has already timed out", err) return nil, err } else if timedout { @@ -427,12 +402,12 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState dstAction = UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE } case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_INIT: - if complete, err := upgradeAlreadyComplete(dstChan, srcCtxFinalized, src, srcChanUpg); err != nil { + if complete, err := upgradeAlreadyComplete(dstChan, srcCtx, src, srcChanUpg); err != nil { logger.Error("failed to check if the upgrade on the dst side has already completed", err) return nil, err } else if complete { srcAction = UPGRADE_ACTION_OPEN - } else if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + } else if timedout, err := upgradeAlreadyTimedOut(dstCtx, dst, srcChanUpg); err != nil { logger.Error("failed to check if the upgrade on the dst side has already timed out", err) return nil, err } else if timedout { @@ -441,13 +416,13 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState srcAction = UPGRADE_ACTION_CANCEL_FLUSHCOMPLETE } case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_FLUSHING: - if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + if timedout, err := upgradeAlreadyTimedOut(srcCtx, src, dstChanUpg); err != nil { logger.Error("failed to check if the upgrade on the src side has already timed out", err) return nil, err } else if timedout { dstAction = UPGRADE_ACTION_TIMEOUT } - if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + if timedout, err := upgradeAlreadyTimedOut(dstCtx, dst, srcChanUpg); err != nil { logger.Error("failed to check if the upgrade on the dst side has already timed out", err) return nil, err } else if timedout { @@ -459,37 +434,37 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState break } - if completable, err := src.QueryCanTransitionToFlushComplete(srcCtxFinalized); err != nil { + if completable, err := queryCanTransitionToFlushComplete(src); err != nil { logger.Error("failed to check if the src channel can transition to FLUSHCOMPLETE", err) return nil, err } else if completable { srcAction = UPGRADE_ACTION_CONFIRM } - if completable, err := dst.QueryCanTransitionToFlushComplete(dstCtxFinalized); err != nil { + if completable, err := queryCanTransitionToFlushComplete(dst); err != nil { logger.Error("failed to check if the dst channel can transition to FLUSHCOMPLETE", err) return nil, err } else if completable { dstAction = UPGRADE_ACTION_CONFIRM } case srcState == UPGRADE_STATE_FLUSHING && dstState == UPGRADE_STATE_FLUSHCOMPLETE: - if timedout, err := upgradeAlreadyTimedOut(srcCtxFinalized, src, dstChanUpg); err != nil { + if timedout, err := upgradeAlreadyTimedOut(srcCtx, src, dstChanUpg); err != nil { logger.Error("failed to check if the upgrade on the src side has already timed out", err) return nil, err } else if timedout { dstAction = UPGRADE_ACTION_TIMEOUT - } else if completable, err := src.QueryCanTransitionToFlushComplete(srcCtxFinalized); err != nil { + } else if completable, err := queryCanTransitionToFlushComplete(src); err != nil { logger.Error("failed to check if the src channel can transition to FLUSHCOMPLETE", err) return nil, err } else if completable { srcAction = UPGRADE_ACTION_CONFIRM } case srcState == UPGRADE_STATE_FLUSHCOMPLETE && dstState == UPGRADE_STATE_FLUSHING: - if timedout, err := upgradeAlreadyTimedOut(dstCtxFinalized, dst, srcChanUpg); err != nil { + if timedout, err := upgradeAlreadyTimedOut(dstCtx, dst, srcChanUpg); err != nil { logger.Error("failed to check if the upgrade on the dst side has already timed out", err) return nil, err } else if timedout { srcAction = UPGRADE_ACTION_TIMEOUT - } else if completable, err := dst.QueryCanTransitionToFlushComplete(dstCtxFinalized); err != nil { + } else if completable, err := queryCanTransitionToFlushComplete(dst); err != nil { logger.Error("failed to check if the dst channel can transition to FLUSHCOMPLETE", err) return nil, err } else if completable { @@ -521,7 +496,7 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState srcAction, srcChan, addr, - dstCtxFinalized, + dstCtx, dst, dstChan, dstChanUpg, @@ -546,7 +521,7 @@ func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState dstAction, dstChan, addr, - srcCtxFinalized, + srcCtx, src, srcChan, srcChanUpg, @@ -574,52 +549,71 @@ func queryProposedConnectionID(cpCtx QueryContext, cp *ProvableChain, cpChanUpg } } -func queryFinalizedChannelUpgradePair( - srcCtxFinalized, - dstCtxFinalized, - srcCtxLatest, - dstCtxLatest QueryContext, - src, - dst *ProvableChain, -) (srcChanUpg, dstChanUpg *chantypes.QueryUpgradeResponse, finalized bool, err error) { +func queryCanTransitionToFlushComplete(chain interface { + ChainInfo + ICS04Querier +}) (bool, error) { + if h, err := chain.LatestHeight(); err != nil { + return false, err + } else { + return chain.QueryCanTransitionToFlushComplete(NewQueryContext(context.TODO(), h)) + } +} + +func querySettledChannelUpgradePair( + srcCtx, dstCtx QueryContext, + src, dst interface { + Chain + StateProver + }, + prove bool, +) (*chantypes.QueryUpgradeResponse, *chantypes.QueryUpgradeResponse, bool, error) { logger := GetChannelPairLogger(src, dst) + logger = &log.RelayLogger{Logger: logger.With( + "src_height", srcCtx.Height().String(), + "dst_height", dstCtx.Height().String(), + "prove", prove, + )} // query channel upgrade pair at latest finalized heights - srcChanUpgF, dstChanUpgF, err := QueryChannelUpgradePair( - srcCtxFinalized, - dstCtxFinalized, - src, - dst, - true, - ) + srcChanUpg, dstChanUpg, err := QueryChannelUpgradePair(srcCtx, dstCtx, src, dst, prove) if err != nil { logger.Error("failed to query a channel upgrade pair at the latest finalized heights", err) return nil, nil, false, err } + // prepare QueryContext's based on the latest heights + var srcLatestCtx, dstLatestCtx QueryContext + if h, err := src.LatestHeight(); err != nil { + logger.Error("failed to get the latest height of the src chain", err) + return nil, nil, false, err + } else { + srcLatestCtx = NewQueryContext(context.TODO(), h) + } + if h, err := dst.LatestHeight(); err != nil { + logger.Error("failed to get the latest height of the dst chain", err) + return nil, nil, false, err + } else { + dstLatestCtx = NewQueryContext(context.TODO(), h) + } + // query channel upgrade pair at latest heights - srcChanUpgL, dstChanUpgL, err := QueryChannelUpgradePair( - srcCtxLatest, - dstCtxLatest, - src, - dst, - false, - ) + srcLatestChanUpg, dstLatestChanUpg, err := QueryChannelUpgradePair(srcLatestCtx, dstLatestCtx, src, dst, false) if err != nil { logger.Error("failed to query a channel upgrade pair at the latest heights", err) return nil, nil, false, err } - if !compareUpgrades(srcChanUpgF, srcChanUpgL) { - logger.Debug("channel upgrade is not finalized on src chain") - return nil, nil, false, nil + if !compareUpgrades(srcChanUpg, srcLatestChanUpg) { + logger.Debug("src channel upgrade in transition") + return srcChanUpg, dstChanUpg, false, nil } - if !compareUpgrades(dstChanUpgF, dstChanUpgL) { - logger.Debug("channel upgrade is not finalized on dst chain") - return nil, nil, false, nil + if !compareUpgrades(dstChanUpg, dstLatestChanUpg) { + logger.Debug("dst channel upgrade in transition") + return srcChanUpg, dstChanUpg, false, nil } - return srcChanUpgF, dstChanUpgF, true, nil + return srcChanUpg, dstChanUpg, true, nil } func compareUpgrades(a, b *chantypes.QueryUpgradeResponse) bool { diff --git a/core/channel.go b/core/channel.go index a4721546..b7d61256 100644 --- a/core/channel.go +++ b/core/channel.go @@ -151,14 +151,16 @@ func createChannelStep(src, dst *ProvableChain) (*RelayMsgs, error) { return nil, err } - srcChan, dstChan, err := QueryChannelPair(sh.GetQueryContext(src.ChainID()), sh.GetQueryContext(dst.ChainID()), src, dst, true) + srcChan, dstChan, settled, err := querySettledChannelPair( + sh.GetQueryContext(src.ChainID()), + sh.GetQueryContext(dst.ChainID()), + src, + dst, + true, + ) if err != nil { return nil, err - } - - if finalized, err := checkChannelFinality(src, dst, srcChan.Channel, dstChan.Channel); err != nil { - return nil, err - } else if !finalized { + } else if !settled { return out, nil } @@ -244,29 +246,62 @@ func logChannelStates(src, dst *ProvableChain, srcChan, dstChan *chantypes.Query )) } -func checkChannelFinality(src, dst *ProvableChain, srcChannel, dstChannel *chantypes.Channel) (bool, error) { +func querySettledChannelPair( + srcCtx, dstCtx QueryContext, + src, dst interface { + Chain + StateProver + }, + prove bool, +) (*chantypes.QueryChannelResponse, *chantypes.QueryChannelResponse, bool, error) { logger := GetChannelPairLogger(src, dst) - sh, err := src.LatestHeight() + logger = &log.RelayLogger{Logger: logger.With( + "src_height", srcCtx.Height().String(), + "dst_height", dstCtx.Height().String(), + "prove", prove, + )} + + srcChan, dstChan, err := QueryChannelPair(srcCtx, dstCtx, src, dst, prove) if err != nil { - return false, err + logger.Error("failed to query channel pair at the latest finalized height", err) + return nil, nil, false, err } - dh, err := dst.LatestHeight() - if err != nil { - return false, err + + var srcLatestCtx, dstLatestCtx QueryContext + if h, err := src.LatestHeight(); err != nil { + logger.Error("failed to get the latest height of the src chain", err) + return nil, nil, false, err + } else { + srcLatestCtx = NewQueryContext(context.TODO(), h) + } + if h, err := dst.LatestHeight(); err != nil { + logger.Error("failed to get the latest height of the dst chain", err) + return nil, nil, false, err + } else { + dstLatestCtx = NewQueryContext(context.TODO(), h) } - srcChanLatest, dstChanLatest, err := QueryChannelPair(NewQueryContext(context.TODO(), sh), NewQueryContext(context.TODO(), dh), src, dst, false) + + srcLatestChan, dstLatestChan, err := QueryChannelPair(srcLatestCtx, dstLatestCtx, src, dst, false) if err != nil { - return false, err + logger.Error("failed to query channel pair at the latest height", err) + return nil, nil, false, err } - if srcChannel.State != srcChanLatest.Channel.State { - logger.Debug("src channel state in transition", "from_state", srcChannel.State, "to_state", srcChanLatest.Channel.State) - return false, nil + + if srcChan.Channel.String() != srcLatestChan.Channel.String() { + logger.Debug("src channel end in transition", + "from", srcChan.Channel.String(), + "to", srcLatestChan.Channel.String(), + ) + return srcChan, dstChan, false, nil } - if dstChannel.State != dstChanLatest.Channel.State { - logger.Debug("dst channel state in transition", "from_state", dstChannel.State, "to_state", dstChanLatest.Channel.State) - return false, nil + if dstChan.Channel.String() != dstLatestChan.Channel.String() { + logger.Debug("dst channel end in transition", + "from", dstChan.Channel.String(), + "to", dstLatestChan.Channel.String(), + ) + return srcChan, dstChan, false, nil } - return true, nil + return srcChan, dstChan, true, nil } func GetChannelLogger(c Chain) *log.RelayLogger { diff --git a/core/connection.go b/core/connection.go index da9208c3..3b9ee5ca 100644 --- a/core/connection.go +++ b/core/connection.go @@ -164,14 +164,16 @@ func createConnectionStep(src, dst *ProvableChain) (*RelayMsgs, error) { return nil, err } - srcConn, dstConn, err := QueryConnectionPair(sh.GetQueryContext(src.ChainID()), sh.GetQueryContext(dst.ChainID()), src, dst, true) + srcConn, dstConn, settled, err := querySettledConnectionPair( + sh.GetQueryContext(src.ChainID()), + sh.GetQueryContext(dst.ChainID()), + src, + dst, + true, + ) if err != nil { return nil, err - } - - if finalized, err := checkConnectionFinality(src, dst, srcConn.Connection, dstConn.Connection); err != nil { - return nil, err - } else if !finalized { + } else if !settled { return out, nil } @@ -327,29 +329,62 @@ func mustGetAddress(chain interface { return addr } -func checkConnectionFinality(src, dst *ProvableChain, srcConnection, dstConnection *conntypes.ConnectionEnd) (bool, error) { +func querySettledConnectionPair( + srcCtx, dstCtx QueryContext, + src, dst interface { + Chain + StateProver + }, + prove bool, +) (*conntypes.QueryConnectionResponse, *conntypes.QueryConnectionResponse, bool, error) { logger := GetConnectionPairLogger(src, dst) - sh, err := src.LatestHeight() + logger = &log.RelayLogger{Logger: logger.With( + "src_height", srcCtx.Height().String(), + "dst_height", dstCtx.Height().String(), + "prove", prove, + )} + + srcConn, dstConn, err := QueryConnectionPair(srcCtx, dstCtx, src, dst, prove) if err != nil { - return false, err + logger.Error("failed to query connection pair at the latest finalized height", err) + return nil, nil, false, err } - dh, err := dst.LatestHeight() - if err != nil { - return false, err + + var srcLatestCtx, dstLatestCtx QueryContext + if h, err := src.LatestHeight(); err != nil { + logger.Error("failed to get the latest height of the src chain", err) + return nil, nil, false, err + } else { + srcLatestCtx = NewQueryContext(context.TODO(), h) } - srcConnLatest, dstConnLatest, err := QueryConnectionPair(NewQueryContext(context.TODO(), sh), NewQueryContext(context.TODO(), dh), src, dst, false) + if h, err := dst.LatestHeight(); err != nil { + logger.Error("failed to get the latest height of the dst chain", err) + return nil, nil, false, err + } else { + dstLatestCtx = NewQueryContext(context.TODO(), h) + } + + srcLatestConn, dstLatestConn, err := QueryConnectionPair(srcLatestCtx, dstLatestCtx, src, dst, false) if err != nil { - return false, err + logger.Error("failed to query connection pair at the latest height", err) + return nil, nil, false, err } - if srcConnection.State != srcConnLatest.Connection.State { - logger.Debug("src connection state in transition", "from_state", srcConnection.State, "to_state", srcConnLatest.Connection.State) - return false, nil + + if srcConn.Connection.String() != srcLatestConn.Connection.String() { + logger.Debug("src connection end in transition", + "from", srcConn.Connection.String(), + "to", srcLatestConn.Connection.String(), + ) + return srcConn, dstConn, false, nil } - if dstConnection.State != dstConnLatest.Connection.State { - logger.Debug("dst connection state in transition", "from_state", dstConnection.State, "to_state", dstConnLatest.Connection.State) - return false, nil + if dstConn.Connection.String() != dstLatestConn.Connection.String() { + logger.Debug("dst connection end in transition", + "from", dstConn.Connection.String(), + "to", dstLatestConn.Connection.String(), + ) + return srcConn, dstConn, false, nil } - return true, nil + return srcConn, dstConn, true, nil } func GetConnectionPairLogger(src, dst Chain) *log.RelayLogger { From 5f042f22ff8fe3eb4b55d42efd6972067b57f2bb Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 28 Aug 2024 07:43:19 +0900 Subject: [PATCH 40/47] fix `query channel-upgrade` subcommand to print the zero-value Upgrade if upgrade not found Signed-off-by: Masanori Yoshida --- cmd/query.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/query.go b/cmd/query.go index a28e3a9a..364f882d 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -9,6 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/gogoproto/jsonpb" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + chantypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" "github.com/hyperledger-labs/yui-relayer/config" "github.com/hyperledger-labs/yui-relayer/core" @@ -183,7 +184,7 @@ func queryChannelUpgrade(ctx *config.Context) *cobra.Command { if err != nil { return err } else if res == nil { - return fmt.Errorf("failed to query for channel upgrade") + res = &chantypes.QueryUpgradeResponse{} } marshaler := jsonpb.Marshaler{} From 382b372b21e9263fd324b6f2c858ea46c52c3cec Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 28 Aug 2024 15:02:42 +0900 Subject: [PATCH 41/47] fix CancelChannelUpgrade to wait for state settlement and to send MsgUpdateClient if necessary Signed-off-by: Masanori Yoshida --- cmd/tx.go | 17 +++++- core/channel-upgrade.go | 126 ++++++++++++++++++++++++---------------- 2 files changed, 92 insertions(+), 51 deletions(-) diff --git a/cmd/tx.go b/cmd/tx.go index b6a08ab3..866e781c 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -361,7 +361,11 @@ func channelUpgradeExecuteCmd(ctx *config.Context) *cobra.Command { } func channelUpgradeCancelCmd(ctx *config.Context) *cobra.Command { - return &cobra.Command{ + const ( + flagSettlementInterval = "settlement-interval" + ) + + cmd := cobra.Command{ Use: "cancel [path-name] [chain-id]", Short: "execute chanUpgradeCancel", Long: "This command is meant to be used to cancel an IBC channel upgrade on a configured chain", @@ -370,6 +374,11 @@ func channelUpgradeCancelCmd(ctx *config.Context) *cobra.Command { pathName := args[0] chainID := args[1] + settlementInterval, err := cmd.Flags().GetDuration(flagSettlementInterval) + if err != nil { + return err + } + _, srcChainID, dstChainID, err := ctx.Config.ChainsFromPath(pathName) if err != nil { return err @@ -394,9 +403,13 @@ func channelUpgradeCancelCmd(ctx *config.Context) *cobra.Command { return err } - return core.CancelChannelUpgrade(chain, cp) + return core.CancelChannelUpgrade(chain, cp, settlementInterval) }, } + + cmd.Flags().Duration(flagSettlementInterval, 10*time.Second, "time interval between attemts to query for settled channel/upgrade states") + + return &cmd } func relayMsgsCmd(ctx *config.Context) *cobra.Command { diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index fc0a89fc..4ee5d589 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -103,11 +103,13 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti logger := GetChannelPairLogger(src, dst) defer logger.TimeTrack(time.Now(), "ExecuteChannelUpgrade") - tick := time.Tick(interval) + ticker := time.NewTicker(interval) + defer ticker.Stop() + failures := 0 firstCall := true for { - <-tick + <-ticker.C steps, err := upgradeChannelStep(src, dst, targetSrcState, targetDstState, firstCall) if err != nil { @@ -150,63 +152,89 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti } // CancelChannelUpgrade executes chanUpgradeCancel on `chain`. -func CancelChannelUpgrade(chain, cp *ProvableChain) error { +func CancelChannelUpgrade(chain, cp *ProvableChain, settlementInterval time.Duration) error { logger := GetChannelPairLogger(chain, cp) defer logger.TimeTrack(time.Now(), "CancelChannelUpgrade") - addr, err := chain.GetAddress() - if err != nil { - logger.Error("failed to get address", err) - return err - } + // wait for settlement + ticker := time.NewTicker(settlementInterval) + defer ticker.Stop() - var ctx, cpCtx QueryContext - if sh, err := NewSyncHeaders(chain, cp); err != nil { - logger.Error("failed to create a SyncHeaders", err) - return err - } else { - ctx = sh.GetQueryContext(chain.ChainID()) - cpCtx = sh.GetQueryContext(cp.ChainID()) - } + for { + <-ticker.C - chann, err := chain.QueryChannel(ctx) - if err != nil { - logger.Error("failed to get the channel state", err) - return err - } + sh, err := NewSyncHeaders(chain, cp) + if err != nil { + logger.Error("failed to create a SyncHeaders", err) + return err + } + ctx := sh.GetQueryContext(chain.ChainID()) + cpCtx := sh.GetQueryContext(cp.ChainID()) - upgErr, err := QueryChannelUpgradeError(cpCtx, cp, true) - if err != nil { - logger.Error("failed to query the channel upgrade error receipt", err) - return err - } else if chann.Channel.State == chantypes.FLUSHCOMPLETE && - (upgErr == nil || upgErr.ErrorReceipt.Sequence != chann.Channel.UpgradeSequence) { - var err error - if upgErr == nil { - err = fmt.Errorf("upgrade error receipt not found") - } else { - err = fmt.Errorf("upgrade sequences don't match: channel.upgrade_sequence=%d, error_receipt.sequence=%d", - chann.Channel.UpgradeSequence, upgErr.ErrorReceipt.Sequence) + chann, _, settled, err := querySettledChannelPair(ctx, cpCtx, chain, cp, false) + if err != nil { + logger.Error("failed to query for settled channel pair", err) + return err + } else if !settled { + logger.Info("waiting for settlement of channel pair ...") + continue } - logger.Error("cannot cancel the upgrade in FLUSHCOMPLETE state", err) - return err - } else if upgErr == nil { - // NOTE: Even if an error receipt is not found, anyway try to execute ChanUpgradeCancel. - // If the sender is authority and the channel state is anything other than FLUSHCOMPLETE, - // the cancellation will be successful. - upgErr = &chantypes.QueryUpgradeErrorResponse{} - } - msg := chain.Path().ChanUpgradeCancel(upgErr, addr) + if _, _, settled, err := querySettledChannelUpgradePair(ctx, cpCtx, chain, cp, false); err != nil { + logger.Error("failed to query for settled channel upgrade pair", err) + return err + } else if !settled { + logger.Info("waiting for settlement of channel upgrade pair") + continue + } - if _, err := chain.SendMsgs([]sdk.Msg{msg}); err != nil { - logger.Error("failed to send MsgChannelUpgradeCancel", err) - return err - } else { - logger.Info("successfully cancelled the channel upgrade") - } + cpHeaders, err := cp.SetupHeadersForUpdate(chain, sh.GetLatestFinalizedHeader(cp.ChainID())) + if err != nil { + logger.Error("failed to set up headers for LC update", err) + return err + } - return nil + upgErr, err := QueryChannelUpgradeError(cpCtx, cp, true) + if err != nil { + logger.Error("failed to query the channel upgrade error receipt", err) + return err + } else if chann.Channel.State == chantypes.FLUSHCOMPLETE && + (upgErr == nil || upgErr.ErrorReceipt.Sequence != chann.Channel.UpgradeSequence) { + var err error + if upgErr == nil { + err = fmt.Errorf("upgrade error receipt not found") + } else { + err = fmt.Errorf("upgrade sequences don't match: channel.upgrade_sequence=%d, error_receipt.sequence=%d", + chann.Channel.UpgradeSequence, upgErr.ErrorReceipt.Sequence) + } + logger.Error("cannot cancel the upgrade in FLUSHCOMPLETE state", err) + return err + } else if upgErr == nil { + // NOTE: Even if an error receipt is not found, anyway try to execute ChanUpgradeCancel. + // If the sender is authority and the channel state is anything other than FLUSHCOMPLETE, + // the cancellation will be successful. + upgErr = &chantypes.QueryUpgradeErrorResponse{} + } + + addr, err := chain.GetAddress() + if err != nil { + logger.Error("failed to get address", err) + return err + } + + var msgs []sdk.Msg + msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) + msgs = append(msgs, chain.Path().ChanUpgradeCancel(upgErr, addr)) + + if _, err := chain.SendMsgs(msgs); err != nil { + logger.Error("failed to send msgs to cancel the channel upgrade", err) + return err + } else { + logger.Info("successfully cancelled the channel upgrade") + } + + return nil + } } func NewUpgradeState(chanState chantypes.State, upgradeExists bool) (UpgradeState, error) { From b1b5e669da538dc8e812e989fa3dd021dfe3d125 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Wed, 28 Aug 2024 15:51:30 +0900 Subject: [PATCH 42/47] fix CancelChannelUpgrade to avoid using multicall Signed-off-by: Masanori Yoshida --- cmd/tx.go | 13 +++++++------ core/channel-upgrade.go | 13 ++++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cmd/tx.go b/cmd/tx.go index 866e781c..59400686 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -242,13 +242,14 @@ func channelUpgradeInitCmd(ctx *config.Context) *cobra.Command { } var chain, cp *core.ProvableChain - if chainID == srcID { + switch chainID { + case srcID: chain = chains[srcID] cp = chains[dstID] - } else if chainID == dstID { + case dstID: chain = chains[dstID] cp = chains[srcID] - } else { + default: return fmt.Errorf("unknown chain ID: %s", chainID) } @@ -385,10 +386,10 @@ func channelUpgradeCancelCmd(ctx *config.Context) *cobra.Command { } var cpChainID string - switch { - case chainID == srcChainID: + switch chainID { + case srcChainID: cpChainID = dstChainID - case chainID == dstChainID: + case dstChainID: cpChainID = srcChainID default: return fmt.Errorf("invalid chain ID: %s or %s was expected, but %s was given", srcChainID, dstChainID, chainID) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 4ee5d589..66f88d3e 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -226,12 +226,15 @@ func CancelChannelUpgrade(chain, cp *ProvableChain, settlementInterval time.Dura msgs = append(msgs, chain.Path().UpdateClients(cpHeaders, addr)...) msgs = append(msgs, chain.Path().ChanUpgradeCancel(upgErr, addr)) - if _, err := chain.SendMsgs(msgs); err != nil { - logger.Error("failed to send msgs to cancel the channel upgrade", err) - return err - } else { - logger.Info("successfully cancelled the channel upgrade") + // NOTE: A call of SendMsgs for each msg is executed separately to avoid using multicall for eth. + // This is just a workaround and should be fixed in the future. + for _, msg := range msgs { + if _, err := chain.SendMsgs([]sdk.Msg{msg}); err != nil { + logger.Error("failed to send a msg to cancel the channel upgrade", err) + return err + } } + logger.Info("successfully cancelled the channel upgrade") return nil } From 94e2251407eebf8751aa903b56173d6f6c772556 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 29 Aug 2024 09:10:42 +0900 Subject: [PATCH 43/47] fix logging in RelayMsgs Signed-off-by: Masanori Yoshida --- core/relayMsgs.go | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/core/relayMsgs.go b/core/relayMsgs.go index de98b59a..31bbc509 100644 --- a/core/relayMsgs.go +++ b/core/relayMsgs.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/gogoproto/proto" + "github.com/hyperledger-labs/yui-relayer/log" ) // RelayMsgs contains the msgs that need to be sent to both a src and dst chain @@ -71,14 +72,17 @@ func (r *RelayMsgs) Send(src, dst Chain) { txSize += uint64(len(bz)) if r.IsMaxTx(msgLen, txSize) { - logger := logger.With("msgs", msgsToLoggable(msgs)) + logger := &log.RelayLogger{Logger: logger.With( + "msgs", msgsToLoggable(msgs), + "side", "src", + )} // Submit the transactions to src chain and update its status msgIDs, err := src.SendMsgs(msgs) if err != nil { logger.Error("failed to send msgs", err) } else { - logger.Info("successfully sent msgs", "side", "src") + logger.Info("successfully sent msgs") } r.Succeeded = r.Succeeded && (err == nil) if err == nil { @@ -96,13 +100,16 @@ func (r *RelayMsgs) Send(src, dst Chain) { // submit leftover msgs if len(msgs) > 0 { - logger := logger.With("msgs", msgsToLoggable(msgs)) + logger := &log.RelayLogger{Logger: logger.With( + "msgs", msgsToLoggable(msgs), + "side", "src", + )} msgIDs, err := src.SendMsgs(msgs) if err != nil { logger.Error("failed to send msgs", err) } else { - logger.Info("successfully sent msgs", "side", "src") + logger.Info("successfully sent msgs") } r.Succeeded = r.Succeeded && (err == nil) if err == nil { @@ -128,14 +135,17 @@ func (r *RelayMsgs) Send(src, dst Chain) { txSize += uint64(len(bz)) if r.IsMaxTx(msgLen, txSize) { - logger := logger.With("msgs", msgsToLoggable(msgs)) + logger := &log.RelayLogger{Logger: logger.With( + "msgs", msgsToLoggable(msgs), + "side", "dst", + )} // Submit the transaction to dst chain and update its status msgIDs, err := dst.SendMsgs(msgs) if err != nil { logger.Error("failed to send msgs", err) } else { - logger.Info("successfully sent msgs", "side", "dst") + logger.Info("successfully sent msgs") } r.Succeeded = r.Succeeded && (err == nil) if err == nil { @@ -153,13 +163,16 @@ func (r *RelayMsgs) Send(src, dst Chain) { // submit leftover msgs if len(msgs) > 0 { - logger := logger.With("msgs", msgsToLoggable(msgs)) + logger := &log.RelayLogger{Logger: logger.With( + "msgs", msgsToLoggable(msgs), + "side", "dst", + )} msgIDs, err := dst.SendMsgs(msgs) if err != nil { logger.Error("failed to send msgs", err) } else { - logger.Info("successfully sent msgs", "side", "dst") + logger.Info("successfully sent msgs") } r.Succeeded = r.Succeeded && (err == nil) if err == nil { @@ -175,7 +188,7 @@ func (r *RelayMsgs) Send(src, dst Chain) { func msgsToLoggable(msgs []sdk.Msg) []string { var ret []string for _, msg := range msgs { - ret = append(ret, fmt.Sprintf("%T", msg)) + ret = append(ret, fmt.Sprintf("%T{%v}", msg, msg)) } return ret } From 311828ea2b15242bbacf9ed6ada5600ee033a9f9 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 29 Aug 2024 09:15:29 +0900 Subject: [PATCH 44/47] fix CancelChannelUpgrade to stop waiting before the first attempt Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 66f88d3e..b376a894 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -161,8 +161,6 @@ func CancelChannelUpgrade(chain, cp *ProvableChain, settlementInterval time.Dura defer ticker.Stop() for { - <-ticker.C - sh, err := NewSyncHeaders(chain, cp) if err != nil { logger.Error("failed to create a SyncHeaders", err) @@ -177,6 +175,7 @@ func CancelChannelUpgrade(chain, cp *ProvableChain, settlementInterval time.Dura return err } else if !settled { logger.Info("waiting for settlement of channel pair ...") + <-ticker.C continue } @@ -185,6 +184,7 @@ func CancelChannelUpgrade(chain, cp *ProvableChain, settlementInterval time.Dura return err } else if !settled { logger.Info("waiting for settlement of channel upgrade pair") + <-ticker.C continue } From 67a5da8ffb6a290f457574a6c9b32b75f3d0df07 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Thu, 29 Aug 2024 09:48:13 +0900 Subject: [PATCH 45/47] fix updateChannelStep to add the `first_call` attribute in log outputs Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index b376a894..8e4aa2d2 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -259,13 +259,15 @@ func NewUpgradeState(chanState chantypes.State, upgradeExists bool) (UpgradeStat func upgradeChannelStep(src, dst *ProvableChain, targetSrcState, targetDstState UpgradeState, firstCall bool) (*RelayMsgs, error) { logger := GetChannelPairLogger(src, dst) + logger = &log.RelayLogger{Logger: logger.With("first_call", firstCall)} - out := NewRelayMsgs() if err := validatePaths(src, dst); err != nil { logger.Error("failed to validate paths", err) return nil, err } + out := NewRelayMsgs() + // First, update the light clients to the latest header and return the header sh, err := NewSyncHeaders(src, dst) if err != nil { From d02879e3c99c6a040784be6b8a6d7a657a26a8ce Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 30 Aug 2024 11:49:09 +0900 Subject: [PATCH 46/47] fix msgsToLoggable Signed-off-by: Masanori Yoshida --- core/relayMsgs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/relayMsgs.go b/core/relayMsgs.go index 31bbc509..6c52defd 100644 --- a/core/relayMsgs.go +++ b/core/relayMsgs.go @@ -188,7 +188,7 @@ func (r *RelayMsgs) Send(src, dst Chain) { func msgsToLoggable(msgs []sdk.Msg) []string { var ret []string for _, msg := range msgs { - ret = append(ret, fmt.Sprintf("%T{%v}", msg, msg)) + ret = append(ret, fmt.Sprintf("%#v", msg)) } return ret } From dc23bc2883abe0b6e249709396002403aefbeef6 Mon Sep 17 00:00:00 2001 From: Masanori Yoshida Date: Fri, 30 Aug 2024 11:52:55 +0900 Subject: [PATCH 47/47] improve the loop in ExecuteChannelUpgrade Signed-off-by: Masanori Yoshida --- core/channel-upgrade.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/channel-upgrade.go b/core/channel-upgrade.go index 8e4aa2d2..7930a393 100644 --- a/core/channel-upgrade.go +++ b/core/channel-upgrade.go @@ -109,7 +109,9 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti failures := 0 firstCall := true for { - <-ticker.C + if !firstCall { + <-ticker.C + } steps, err := upgradeChannelStep(src, dst, targetSrcState, targetDstState, firstCall) if err != nil { @@ -117,6 +119,8 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti return err } + firstCall = false + if steps.Last { logger.Info("Channel upgrade completed") return nil @@ -124,7 +128,6 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti if !steps.Ready() { logger.Debug("Waiting for next channel upgrade step ...") - firstCall = false continue } @@ -136,7 +139,6 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti return err } - firstCall = false failures = 0 } else { if failures++; failures > 2 { @@ -146,7 +148,6 @@ func ExecuteChannelUpgrade(pathName string, src, dst *ProvableChain, interval ti } logger.Warn("Retrying transaction...") - time.Sleep(5 * time.Second) } } }