Repository: cosmos/relayer
Pull request #874 (2022-07-29 08:47:46)

Consolidate provider
Consolidates the provider methods used for the flush packet/acks mechanisms, the event processor, and the legacy processor.

The bulk of this change is consolidating `QueryHeaderAtHeight`, `GetIBCUpdateHeader`, and `IBCHeaderAtHeight` into the method `QueryIBCHeader` (which is a rename of `IBCHeaderAtHeight`), and the propagation of that out into the Client methods such as creating, updating, and upgrading clients.


Patch:
diff --git a/cmd/query.go b/cmd/query.go
index 8a475cc..8e811b1 100644
--- a/cmd/query.go
+++ b/cmd/query.go
@@ -6,7 +6,6 @@ import (
 	"strconv"
 	"strings"
 
-	ibcexported "github.com/cosmos/ibc-go/v4/modules/core/exported"
 	"github.com/cosmos/relayer/v2/relayer"
 	"github.com/spf13/cobra"
 )
@@ -261,23 +260,29 @@ $ %s query header ibc-0 1400`,
 				return errChainNotFound(args[0])
 			}
 
-			var header ibcexported.Header
-			var err error
+			var height int64
 			switch len(args) {
 			case 1:
-				header, err = chain.ChainProvider.GetLightSignedHeaderAtHeight(cmd.Context(), 0)
+				var err error
+				height, err = chain.ChainProvider.QueryLatestHeight(cmd.Context())
 				if err != nil {
 					return err
 				}
 
 			case 2:
-				header, err = relayer.QueryHeader(cmd.Context(), chain, args[1])
+				var err error
+				height, err = strconv.ParseInt(args[1], 10, 64)
 				if err != nil {
 					return err
 				}
 			}
 
-			s, err := chain.ChainProvider.Sprint(header)
+			header, err := chain.ChainProvider.QueryIBCHeader(cmd.Context(), height)
+			if err != nil {
+				return err
+			}
+
+			s, err := json.Marshal(header)
 			if err != nil {
 				fmt.Fprintf(cmd.ErrOrStderr(), "Failed to marshal header: %v\n", err)
 				return err
diff --git a/cmd/tx.go b/cmd/tx.go
index 3aed4cb..28aadf7 100644
--- a/cmd/tx.go
+++ b/cmd/tx.go
@@ -8,8 +8,8 @@ import (
 	"github.com/avast/retry-go/v4"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	chantypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"
-	"github.com/cosmos/ibc-go/v4/modules/core/exported"
 	"github.com/cosmos/relayer/v2/relayer"
+	"github.com/cosmos/relayer/v2/relayer/provider"
 	"github.com/spf13/cobra"
 	"go.uber.org/zap"
 )
@@ -178,9 +178,9 @@ func createClientCmd(a *appState) *cobra.Command {
 			}
 
 			// Query the light signed headers for src & dst at the heights srch & dsth, retry if the query fails
-			var srcUpdateHeader, dstUpdateHeader exported.Header
+			var srcUpdateHeader, dstUpdateHeader provider.IBCHeader
 			if err = retry.Do(func() error {
-				srcUpdateHeader, dstUpdateHeader, err = relayer.GetLightSignedHeadersAtHeights(cmd.Context(), src, dst, srch, dsth)
+				srcUpdateHeader, dstUpdateHeader, err = relayer.QueryIBCHeaders(cmd.Context(), src, dst, srch, dsth)
 				if err != nil {
 					return err
 				}
@@ -244,7 +244,7 @@ corresponding update-client messages.`,
 				return fmt.Errorf("key %s not found on dst chain %s", c[dst].ChainProvider.Key(), c[dst].ChainID())
 			}
 
-			return c[src].UpdateClients(cmd.Context(), c[dst], a.Config.memo(cmd))
+			return relayer.UpdateClients(cmd.Context(), c[src], c[dst], a.Config.memo(cmd))
 		},
 	}
 
@@ -281,10 +281,10 @@ func upgradeClientsCmd(a *appState) *cobra.Command {
 
 			// send the upgrade message on the targetChainID
 			if src == targetChainID {
-				return c[src].UpgradeClients(cmd.Context(), c[dst], height, memo)
+				return relayer.UpgradeClient(cmd.Context(), c[dst], c[src], height, memo)
 			}
 
-			return c[dst].UpgradeClients(cmd.Context(), c[src], height, memo)
+			return relayer.UpgradeClient(cmd.Context(), c[src], c[dst], height, memo)
 		},
 	}
 
diff --git a/relayer/chains/cosmos/cosmos_chain_processor.go b/relayer/chains/cosmos/cosmos_chain_processor.go
index 62ce58b..99ee41c 100644
--- a/relayer/chains/cosmos/cosmos_chain_processor.go
+++ b/relayer/chains/cosmos/cosmos_chain_processor.go
@@ -307,7 +307,7 @@ func (ccp *CosmosChainProcessor) queryCycle(ctx context.Context, persistence *qu
 		eg.Go(func() (err error) {
 			queryCtx, cancelQueryCtx := context.WithTimeout(ctx, queryTimeout)
 			defer cancelQueryCtx()
-			ibcHeader, err = ccp.chainProvider.IBCHeaderAtHeight(queryCtx, i)
+			ibcHeader, err = ccp.chainProvider.QueryIBCHeader(queryCtx, i)
 			return err
 		})
 
diff --git a/relayer/chains/cosmos/provider.go b/relayer/chains/cosmos/provider.go
index cb811ba..79ab64e 100644
--- a/relayer/chains/cosmos/provider.go
+++ b/relayer/chains/cosmos/provider.go
@@ -8,6 +8,9 @@ import (
 
 	"github.com/cosmos/cosmos-sdk/crypto/keyring"
 	"github.com/cosmos/cosmos-sdk/types/module"
+	commitmenttypes "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types"
+	ibcexported "github.com/cosmos/ibc-go/v4/modules/core/exported"
+	tmclient "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types"
 	"github.com/cosmos/relayer/v2/relayer/provider"
 	"github.com/gogo/protobuf/proto"
 	lens "github.com/strangelove-ventures/lens/client"
@@ -98,13 +101,18 @@ type CosmosIBCHeader struct {
 	ValidatorSet *tmtypes.ValidatorSet
 }
 
-// noop to implement processor.IBCHeader
-func (h CosmosIBCHeader) IBCHeaderIndicator() {}
-
 func (h CosmosIBCHeader) Height() uint64 {
 	return uint64(h.SignedHeader.Height)
 }
 
+func (h CosmosIBCHeader) ConsensusState() ibcexported.ConsensusState {
+	return &tmclient.ConsensusState{
+		Timestamp:          h.SignedHeader.Time,
+		Root:               commitmenttypes.NewMerkleRoot(h.SignedHeader.AppHash),
+		NextValidatorsHash: h.ValidatorSet.Hash(),
+	}
+}
+
 func (cc *CosmosProvider) ProviderConfig() provider.ProviderConfig {
 	return cc.PCfg
 }
@@ -219,10 +227,10 @@ func (cc *CosmosProvider) WaitForNBlocks(ctx context.Context, n int64) error {
 	}
 }
 
-func (cc *CosmosProvider) BlockTime(ctx context.Context, height int64) (int64, error) {
+func (cc *CosmosProvider) BlockTime(ctx context.Context, height int64) (time.Time, error) {
 	resultBlock, err := cc.RPCClient.Block(ctx, &height)
 	if err != nil {
-		return 0, err
+		return time.Time{}, err
 	}
-	return resultBlock.Block.Time.UnixNano(), nil
+	return resultBlock.Block.Time, nil
 }
diff --git a/relayer/chains/cosmos/query.go b/relayer/chains/cosmos/query.go
index d6a657c..9093896 100644
--- a/relayer/chains/cosmos/query.go
+++ b/relayer/chains/cosmos/query.go
@@ -27,11 +27,43 @@ import (
 	"github.com/cosmos/relayer/v2/relayer/provider"
 	abci "github.com/tendermint/tendermint/abci/types"
 	tmtypes "github.com/tendermint/tendermint/types"
+	"go.uber.org/zap"
 	"golang.org/x/sync/errgroup"
 )
 
 var _ provider.QueryProvider = &CosmosProvider{}
 
+// queryIBCMessages returns an array of IBC messages given a tag
+func (cc *CosmosProvider) queryIBCMessages(ctx context.Context, log *zap.Logger, page, limit int, query string) ([]ibcMessage, error) {
+	if query == "" {
+		return nil, errors.New("query string must be provided")
+	}
+
+	if page <= 0 {
+		return nil, errors.New("page must greater than 0")
+	}
+
+	if limit <= 0 {
+		return nil, errors.New("limit must greater than 0")
+	}
+
+	res, err := cc.RPCClient.TxSearch(ctx, query, true, &page, &limit, "")
+	if err != nil {
+		return nil, err
+	}
+	var ibcMsgs []ibcMessage
+	for _, tx := range res.Txs {
+		parsedLogs, err := sdk.ParseABCILogs(tx.TxResult.Log)
+		if err != nil {
+			continue
+		}
+
+		ibcMsgs = append(ibcMsgs, parseABCILogs(log, parsedLogs, 0)...)
+	}
+
+	return ibcMsgs, nil
+}
+
 // QueryTx takes a transaction hash and returns the transaction
 func (cc *CosmosProvider) QueryTx(ctx context.Context, hashHex string) (*provider.RelayerTxResponse, error) {
 	hash, err := hex.DecodeString(hashHex)
@@ -658,6 +690,66 @@ func (cc *CosmosProvider) QueryUnreceivedPackets(ctx context.Context, height uin
 	return res.Sequences, nil
 }
 
+func sendPacketQuery(channelID string, portID string, seq uint64) string {
+	x := []string{
+		fmt.Sprintf("%s.packet_src_channel='%s'", spTag, channelID),
+		fmt.Sprintf("%s.packet_src_port='%s'", spTag, portID),
+		fmt.Sprintf("%s.packet_sequence='%d'", spTag, seq),
+	}
+	return strings.Join(x, " AND ")
+}
+
+func writeAcknowledgementQuery(channelID string, portID string, seq uint64) string {
+	x := []string{
+		fmt.Sprintf("%s.packet_dst_channel='%s'", waTag, channelID),
+		fmt.Sprintf("%s.packet_dst_port='%s'", waTag, portID),
+		fmt.Sprintf("%s.packet_sequence='%d'", waTag, seq),
+	}
+	return strings.Join(x, " AND ")
+}
+
+func (cc *CosmosProvider) QuerySendPacket(
+	ctx context.Context,
+	srcChanID,
+	srcPortID string,
+	sequence uint64,
+) (provider.PacketInfo, error) {
+	q := sendPacketQuery(srcChanID, srcPortID, sequence)
+	ibcMsgs, err := cc.queryIBCMessages(ctx, cc.log, 1, 1000, q)
+	if err != nil {
+		return provider.PacketInfo{}, err
+	}
+	for _, msg := range ibcMsgs {
+		if pi, ok := msg.info.(*packetInfo); ok {
+			if pi.Sequence == sequence {
+				return provider.PacketInfo(*pi), nil
+			}
+		}
+	}
+	return provider.PacketInfo{}, fmt.Errorf("no ibc messages found for send_packet query: %s", q)
+}
+
+func (cc *CosmosProvider) QueryRecvPacket(
+	ctx context.Context,
+	dstChanID,
+	dstPortID string,
+	sequence uint64,
+) (provider.PacketInfo, error) {
+	q := writeAcknowledgementQuery(dstChanID, dstPortID, sequence)
+	ibcMsgs, err := cc.queryIBCMessages(ctx, cc.log, 1, 1000, q)
+	if err != nil {
+		return provider.PacketInfo{}, err
+	}
+	for _, msg := range ibcMsgs {
+		if pi, ok := msg.info.(*packetInfo); ok {
+			if pi.Sequence == sequence {
+				return provider.PacketInfo(*pi), nil
+			}
+		}
+	}
+	return provider.PacketInfo{}, fmt.Errorf("no ibc messages found for write_acknowledgement query: %s", q)
+}
+
 // QueryUnreceivedAcknowledgements returns a list of unrelayed packet acks
 func (cc *CosmosProvider) QueryUnreceivedAcknowledgements(ctx context.Context, height uint64, channelid, portid string, seqs []uint64) ([]uint64, error) {
 	qc := chantypes.NewQueryClient(cc)
diff --git a/relayer/chains/cosmos/tx.go b/relayer/chains/cosmos/tx.go
index ce04451..59075fe 100644
--- a/relayer/chains/cosmos/tx.go
+++ b/relayer/chains/cosmos/tx.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"strconv"
 	"strings"
 	"time"
 
@@ -279,11 +278,14 @@ func (cc *CosmosProvider) buildMessages(ctx context.Context, msgs []provider.Rel
 	return txBytes, nil
 }
 
-// CreateClient creates an sdk.Msg to update the client on src with consensus state from dst
-func (cc *CosmosProvider) CreateClient(clientState ibcexported.ClientState, dstHeader ibcexported.Header, signer string) (provider.RelayerMessage, error) {
-	tmHeader, ok := dstHeader.(*tmclient.Header)
-	if !ok {
-		return nil, fmt.Errorf("got data of type %T but wanted tmclient.Header", dstHeader)
+// MsgCreateClient creates an sdk.Msg to update the client on src with consensus state from dst
+func (cc *CosmosProvider) MsgCreateClient(
+	clientState ibcexported.ClientState,
+	consensusState ibcexported.ConsensusState,
+) (provider.RelayerMessage, error) {
+	signer, err := cc.Address()
+	if err != nil {
+		return nil, err
 	}
 
 	anyClientState, err := clienttypes.PackClientState(clientState)
@@ -291,7 +293,7 @@ func (cc *CosmosProvider) CreateClient(clientState ibcexported.ClientState, dstH
 		return nil, err
 	}
 
-	anyConsensusState, err := clienttypes.PackConsensusState(tmHeader.ConsensusState())
+	anyConsensusState, err := clienttypes.PackConsensusState(consensusState)
 	if err != nil {
 		return nil, err
 	}
@@ -305,10 +307,6 @@ func (cc *CosmosProvider) CreateClient(clientState ibcexported.ClientState, dstH
 	return NewCosmosMessage(msg), nil
 }
 
-func (cc *CosmosProvider) SubmitMisbehavior( /*TBD*/ ) (provider.RelayerMessage, error) {
-	return nil, nil
-}
-
 func (cc *CosmosProvider) MsgUpdateClient(srcClientId string, dstHeader ibcexported.Header) (provider.RelayerMessage, error) {
 	acc, err := cc.Address()
 	if err != nil {
@@ -351,210 +349,6 @@ func mustGetHeight(h ibcexported.Height) clienttypes.Height {
 	return height
 }
 
-// MsgRelayAcknowledgement constructs the MsgAcknowledgement which is to be sent to the sending chain.
-// The counterparty represents the receiving chain where the acknowledgement would be stored.
-func (cc *CosmosProvider) MsgRelayAcknowledgement(ctx context.Context, dst provider.ChainProvider, dstChanId, dstPortId, srcChanId, srcPortId string, dsth int64, packet provider.RelayPacket) (provider.RelayerMessage, error) {
-	var (
-		acc string
-		err error
-	)
-	msgPacketAck, ok := packet.(*relayMsgPacketAck)
-	if !ok {
-		return nil, fmt.Errorf("got data of type %T but wanted relayMsgPacketAck", packet)
-	}
-
-	if acc, err = cc.Address(); err != nil {
-		return nil, err
-	}
-
-	ackRes, err := dst.QueryPacketAcknowledgement(ctx, dsth, dstChanId, dstPortId, packet.Seq())
-	switch {
-	case err != nil:
-		return nil, err
-	case ackRes.Proof == nil || ackRes.Acknowledgement == nil:
-		return nil, fmt.Errorf("ack packet acknowledgement query seq(%d) is nil", packet.Seq())
-	case ackRes == nil:
-		return nil, fmt.Errorf("ack packet [%s]seq{%d} has no associated proofs", dst.ChainId(), packet.Seq())
-	default:
-		msg := &chantypes.MsgAcknowledgement{
-			Packet: chantypes.Packet{
-				Sequence:           packet.Seq(),
-				SourcePort:         srcPortId,
-				SourceChannel:      srcChanId,
-				DestinationPort:    dstPortId,
-				DestinationChannel: dstChanId,
-				Data:               packet.Data(),
-				TimeoutHeight:      packet.Timeout(),
-				TimeoutTimestamp:   packet.TimeoutStamp(),
-			},
-			Acknowledgement: msgPacketAck.ack,
-			ProofAcked:      ackRes.Proof,
-			ProofHeight:     ackRes.ProofHeight,
-			Signer:          acc,
-		}
-
-		return NewCosmosMessage(msg), nil
-	}
-}
-
-// MsgRelayTimeout constructs the MsgTimeout which is to be sent to the sending chain.
-// The counterparty represents the receiving chain where the receipts would have been
-// stored.
-func (cc *CosmosProvider) MsgRelayTimeout(
-	ctx context.Context,
-	dst provider.ChainProvider,
-	dsth int64,
-	packet provider.RelayPacket,
-	dstChanId, dstPortId, srcChanId, srcPortId string,
-	order chantypes.Order,
-) (provider.RelayerMessage, error) {
-	var (
-		acc string
-		err error
-		msg provider.RelayerMessage
-	)
-	if acc, err = cc.Address(); err != nil {
-		return nil, err
-	}
-
-	switch order {
-	case chantypes.UNORDERED:
-		msg, err = cc.unorderedChannelTimeoutMsg(ctx, dst, dsth, packet, acc, dstChanId, dstPortId, srcChanId, srcPortId)
-		if err != nil {
-			return nil, err
-		}
-	case chantypes.ORDERED:
-		msg, err = cc.orderedChannelTimeoutMsg(ctx, dst, dsth, packet, acc, dstChanId, dstPortId, srcChanId, srcPortId)
-		if err != nil {
-			return nil, err
-		}
-	default:
-		return nil, fmt.Errorf("invalid order type %s, order should be %s or %s",
-			order, chantypes.ORDERED, chantypes.UNORDERED)
-	}
-
-	return msg, nil
-}
-
-func (cc *CosmosProvider) orderedChannelTimeoutMsg(
-	ctx context.Context,
-	dst provider.ChainProvider,
-	dsth int64,
-	packet provider.RelayPacket,
-	acc, dstChanId, dstPortId, srcChanId, srcPortId string,
-) (provider.RelayerMessage, error) {
-	seqRes, err := dst.QueryNextSeqRecv(ctx, dsth, dstChanId, dstPortId)
-	if err != nil {
-		return nil, err
-	}
-
-	if seqRes == nil {
-		return nil, fmt.Errorf("timeout packet [%s]seq{%d} has no associated proofs", cc.PCfg.ChainID, packet.Seq())
-	}
-
-	if seqRes.Proof == nil {
-		return nil, fmt.Errorf("timeout packet next sequence received proof seq(%d) is nil", packet.Seq())
-	}
-
-	msg := &chantypes.MsgTimeout{
-		Packet: chantypes.Packet{
-			Sequence:           packet.Seq(),
-			SourcePort:         srcPortId,
-			SourceChannel:      srcChanId,
-			DestinationPort:    dstPortId,
-			DestinationChannel: dstChanId,
-			Data:               packet.Data(),
-			TimeoutHeight:      packet.Timeout(),
-			TimeoutTimestamp:   packet.TimeoutStamp(),
-		},
-		ProofUnreceived:  seqRes.Proof,
-		ProofHeight:      seqRes.ProofHeight,
-		NextSequenceRecv: packet.Seq(),
-		Signer:           acc,
-	}
-
-	return NewCosmosMessage(msg), nil
-}
-
-func (cc *CosmosProvider) unorderedChannelTimeoutMsg(
-	ctx context.Context,
-	dst provider.ChainProvider,
-	dsth int64,
-	packet provider.RelayPacket,
-	acc, dstChanId, dstPortId, srcChanId, srcPortId string,
-) (provider.RelayerMessage, error) {
-	recvRes, err := dst.QueryPacketReceipt(ctx, dsth, dstChanId, dstPortId, packet.Seq())
-	if err != nil {
-		return nil, err
-	}
-
-	if recvRes == nil {
-		return nil, fmt.Errorf("timeout packet [%s]seq{%d} has no associated proofs", cc.PCfg.ChainID, packet.Seq())
-	}
-
-	if recvRes.Proof == nil {
-		return nil, fmt.Errorf("timeout packet receipt proof seq(%d) is nil", packet.Seq())
-	}
-
-	msg := &chantypes.MsgTimeout{
-		Packet: chantypes.Packet{
-			Sequence:           packet.Seq(),
-			SourcePort:         srcPortId,
-			SourceChannel:      srcChanId,
-			DestinationPort:    dstPortId,
-			DestinationChannel: dstChanId,
-			Data:               packet.Data(),
-			TimeoutHeight:      packet.Timeout(),
-			TimeoutTimestamp:   packet.TimeoutStamp(),
-		},
-		ProofUnreceived:  recvRes.Proof,
-		ProofHeight:      recvRes.ProofHeight,
-		NextSequenceRecv: packet.Seq(),
-		Signer:           acc,
-	}
-	return NewCosmosMessage(msg), nil
-}
-
-// MsgRelayRecvPacket constructs the MsgRecvPacket which is to be sent to the receiving chain.
-// The counterparty represents the sending chain where the packet commitment would be stored.
-func (cc *CosmosProvider) MsgRelayRecvPacket(ctx context.Context, dst provider.ChainProvider, dsth int64, packet provider.RelayPacket, dstChanId, dstPortId, srcChanId, srcPortId string) (provider.RelayerMessage, error) {
-	var (
-		acc string
-		err error
-	)
-	if acc, err = cc.Address(); err != nil {
-		return nil, err
-	}
-
-	comRes, err := dst.QueryPacketCommitment(ctx, dsth, dstChanId, dstPortId, packet.Seq())
-	switch {
-	case err != nil:
-		return nil, err
-	case comRes.Proof == nil || comRes.Commitment == nil:
-		return nil, fmt.Errorf("recv packet commitment query seq(%d) is nil", packet.Seq())
-	case comRes == nil:
-		return nil, fmt.Errorf("receive packet [%s]seq{%d} has no associated proofs", cc.PCfg.ChainID, packet.Seq())
-	default:
-		msg := &chantypes.MsgRecvPacket{
-			Packet: chantypes.Packet{
-				Sequence:           packet.Seq(),
-				SourcePort:         dstPortId,
-				SourceChannel:      dstChanId,
-				DestinationPort:    srcPortId,
-				DestinationChannel: srcChanId,
-				Data:               packet.Data(),
-				TimeoutHeight:      packet.Timeout(),
-				TimeoutTimestamp:   packet.TimeoutStamp(),
-			},
-			ProofCommitment: comRes.Proof,
-			ProofHeight:     comRes.ProofHeight,
-			Signer:          acc,
-		}
-
-		return NewCosmosMessage(msg), nil
-	}
-}
-
 // MsgTransfer creates a new transfer message
 func (cc *CosmosProvider) MsgTransfer(
 	dstAddr string,
@@ -1066,339 +860,91 @@ func (cc *CosmosProvider) MsgUpdateClientHeader(latestHeader provider.IBCHeader,
 // RelayPacketFromSequence relays a packet with a given seq on src and returns recvPacket msgs, timeoutPacketmsgs and error
 func (cc *CosmosProvider) RelayPacketFromSequence(
 	ctx context.Context,
-	src, dst provider.ChainProvider,
+	src provider.ChainProvider,
 	srch, dsth, seq uint64,
-	dstChanId, dstPortId, dstClientId, srcChanId, srcPortId, srcClientId string,
+	srcChanID, srcPortID string,
 	order chantypes.Order,
 ) (provider.RelayerMessage, provider.RelayerMessage, error) {
-	txs, err := cc.QueryTxs(ctx, 1, 1000, rcvPacketQuery(srcChanId, int(seq)))
-	switch {
-	case err != nil:
+	msgTransfer, err := src.QuerySendPacket(ctx, srcChanID, srcPortID, seq)
+	if err != nil {
 		return nil, nil, err
-	case len(txs) == 0:
-		return nil, nil, fmt.Errorf("no transactions returned with query")
-	case len(txs) > 1:
-		return nil, nil, fmt.Errorf("more than one transaction returned with query")
 	}
 
-	rcvPackets, timeoutPackets, err := cc.relayPacketsFromResultTx(ctx, src, dst, int64(dsth), txs[0], dstChanId, dstPortId, dstClientId, srcChanId, srcPortId, srcClientId)
-	switch {
-	case err != nil:
+	dstTime, err := cc.BlockTime(ctx, int64(dsth))
+	if err != nil {
 		return nil, nil, err
-	case len(rcvPackets) == 0 && len(timeoutPackets) == 0:
-		return nil, nil, fmt.Errorf("no relay msgs created from query response")
-	case len(rcvPackets)+len(timeoutPackets) > 1:
-		return nil, nil, fmt.Errorf("more than one relay msg found in tx query")
-	}
-
-	if len(rcvPackets) == 1 {
-		pkt := rcvPackets[0]
-		if seq != pkt.Seq() {
-			return nil, nil, fmt.Errorf("wrong sequence: expected(%d) got(%d)", seq, pkt.Seq())
-		}
-
-		packet, err := dst.MsgRelayRecvPacket(ctx, src, int64(srch), pkt, srcChanId, srcPortId, dstChanId, dstPortId)
-		if err != nil {
-			return nil, nil, err
-		}
-
-		return packet, nil, nil
-	}
-
-	if len(timeoutPackets) == 1 {
-		pkt := timeoutPackets[0]
-		if seq != pkt.Seq() {
-			return nil, nil, fmt.Errorf("wrong sequence: expected(%d) got(%d)", seq, pkt.Seq())
-		}
-
-		timeout, err := src.MsgRelayTimeout(ctx, dst, int64(dsth), pkt, dstChanId, dstPortId, srcChanId, srcPortId, order)
-		if err != nil {
-			return nil, nil, err
-		}
-		return nil, timeout, nil
-	}
-
-	return nil, nil, fmt.Errorf("should have errored before here")
-}
-
-// AcknowledgementFromSequence relays an acknowledgement with a given seq on src, source is the sending chain, destination is the receiving chain
-func (cc *CosmosProvider) AcknowledgementFromSequence(ctx context.Context, dst provider.ChainProvider, dsth, seq uint64, dstChanId, dstPortId, srcChanId, srcPortId string) (provider.RelayerMessage, error) {
-	txs, err := dst.QueryTxs(ctx, 1, 1000, ackPacketQuery(dstChanId, int(seq)))
-	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")
 	}
 
-	acks, err := cc.acknowledgementsFromResultTx(dstChanId, dstPortId, srcChanId, srcPortId, txs[0])
-	switch {
-	case err != nil:
-		return nil, err
-	case len(acks) == 0:
-		return nil, fmt.Errorf("no ack msgs created from query response")
-	}
-
-	var out provider.RelayerMessage
-	for _, ack := range acks {
-		if seq != ack.Seq() {
-			continue
-		}
-		msg, err := cc.MsgRelayAcknowledgement(ctx, dst, dstChanId, dstPortId, srcChanId, srcPortId, int64(dsth), ack)
-		if err != nil {
-			return nil, err
-		}
-		out = msg
-	}
-	return out, nil
-}
-
-func rcvPacketQuery(channelID string, seq int) []string {
-	return []string{fmt.Sprintf("%s.packet_src_channel='%s'", spTag, channelID),
-		fmt.Sprintf("%s.packet_sequence='%d'", spTag, seq)}
-}
-
-func ackPacketQuery(channelID string, seq int) []string {
-	return []string{fmt.Sprintf("%s.packet_dst_channel='%s'", waTag, channelID),
-		fmt.Sprintf("%s.packet_sequence='%d'", waTag, seq)}
-}
-
-// relayPacketsFromResultTx looks through the events in a *ctypes.ResultTx
-// and returns relayPackets with the appropriate data
-func (cc *CosmosProvider) relayPacketsFromResultTx(ctx context.Context, src, dst provider.ChainProvider, dsth int64, resp *provider.RelayerTxResponse, dstChanId, dstPortId, dstClientId, srcChanId, srcPortId, srcClientId string) ([]provider.RelayPacket, []provider.RelayPacket, error) {
-	var (
-		rcvPackets     []provider.RelayPacket
-		timeoutPackets []provider.RelayPacket
-	)
-
-EventLoop:
-	for _, event := range resp.Events {
-		rp := &relayMsgRecvPacket{}
-
-		if event.EventType != spTag {
-			continue
-		}
-
-		for attributeKey, attributeValue := range event.Attributes {
-			switch attributeKey {
-			case srcChanTag:
-				if attributeValue != srcChanId {
-					continue EventLoop
-				}
-			case dstChanTag:
-				if attributeValue != dstChanId {
-					continue EventLoop
-				}
-			case srcPortTag:
-				if attributeValue != srcPortId {
-					continue EventLoop
-				}
-			case dstPortTag:
-				if attributeValue != dstPortId {
-					continue EventLoop
-				}
-			case dataTag:
-				rp.packetData = []byte(attributeValue)
-			case toHeightTag:
-				timeout, err := clienttypes.ParseHeight(attributeValue)
-				if err != nil {
-					cc.log.Warn("error parsing height timeout",
-						zap.String("chain_id", cc.ChainId()),
-						zap.Uint64("sequence", rp.seq),
-						zap.Error(err),
-					)
-					continue EventLoop
-				}
-				rp.timeout = timeout
-			case toTSTag:
-				timeout, err := strconv.ParseUint(attributeValue, 10, 64)
+	if err := cc.ValidatePacket(msgTransfer, provider.LatestBlock{
+		Height: dsth,
+		Time:   dstTime,
+	}); err != nil {
+		switch err.(type) {
+		case *provider.TimeoutHeightError, *provider.TimeoutTimestampError, *provider.TimeoutOnCloseError:
+			var pp provider.PacketProof
+			switch order {
+			case chantypes.UNORDERED:
+				pp, err = cc.PacketReceipt(ctx, msgTransfer, dsth)
 				if err != nil {
-					cc.log.Warn("error parsing timestamp timeout",
-						zap.String("chain_id", cc.ChainId()),
-						zap.Uint64("sequence", rp.seq),
-						zap.Error(err),
-					)
-					continue EventLoop
+					return nil, nil, err
 				}
-				rp.timeoutStamp = timeout
-			case seqTag:
-				seq, err := strconv.ParseUint(attributeValue, 10, 64)
+			case chantypes.ORDERED:
+				pp, err = cc.NextSeqRecv(ctx, msgTransfer, dsth)
 				if err != nil {
-					cc.log.Warn("error parsing packet sequence",
-						zap.String("chain_id", cc.ChainId()),
-						zap.Error(err),
-					)
-					continue EventLoop
+					return nil, nil, err
 				}
-				rp.seq = seq
-			}
-		}
-
-		// If packet data is nil or sequence number is 0 keep parsing events,
-		// also check that at least the block height or timestamp is set.
-		if rp.packetData == nil || rp.seq == 0 || (rp.timeout.IsZero() && rp.timeoutStamp == 0) {
-			continue
-		}
-
-		// fetch the header which represents a block produced on destination
-		block, err := dst.GetIBCUpdateHeader(ctx, dsth, src, srcClientId)
-		if err != nil {
-			return nil, nil, err
-		}
-
-		// if the timestamp is set on the packet, we need to retrieve the current block time from dst
-		var dstBlockTime int64
-		if rp.timeoutStamp > 0 {
-			dstBlockTime, err = dst.BlockTime(ctx, dsth)
-			if err != nil {
-				return nil, nil, err
 			}
-		}
-
-		switch {
-		// If the packet has a timeout time, and it has been reached, return a timeout packet
-		case rp.timeoutStamp > 0 && uint64(dstBlockTime) > rp.timeoutStamp:
-			timeoutPackets = append(timeoutPackets, rp.timeoutPacket())
-		// If the packet has a timeout height, and it has been reached, return a timeout packet
-		case !rp.timeout.IsZero() && block.GetHeight().GTE(rp.timeout):
-			timeoutPackets = append(timeoutPackets, rp.timeoutPacket())
-		// If the packet matches the relay constraints relay it as a MsgReceivePacket
-		default:
-			rcvPackets = append(rcvPackets, rp)
-		}
-	}
-
-	// If there is a relayPacket, return it
-	if len(rcvPackets) > 0 || len(timeoutPackets) > 0 {
-		return rcvPackets, timeoutPackets, nil
-	}
-
-	return nil, nil, fmt.Errorf("no packet data found")
-}
-
-// acknowledgementsFromResultTx looks through the events in a *ctypes.ResultTx and returns
-// relayPackets with the appropriate data
-func (cc *CosmosProvider) acknowledgementsFromResultTx(dstChanId, dstPortId, srcChanId, srcPortId string, resp *provider.RelayerTxResponse) ([]provider.RelayPacket, error) {
-	var ackPackets []provider.RelayPacket
-
-EventLoop:
-	for _, event := range resp.Events {
-		rp := &relayMsgPacketAck{}
-
-		if event.EventType != waTag {
-			continue
-		}
-
-		for attributeKey, attributeValue := range event.Attributes {
-
-			switch attributeKey {
-			case srcChanTag:
-				if attributeValue != srcChanId {
-					continue EventLoop
-				}
-			case dstChanTag:
-				if attributeValue != dstChanId {
-					continue EventLoop
-				}
-			case srcPortTag:
-				if attributeValue != srcPortId {
-					continue EventLoop
-				}
-			case dstPortTag:
-				if attributeValue != dstPortId {
-					continue EventLoop
-				}
-			case ackTag:
-				rp.ack = []byte(attributeValue)
-			case dataTag:
-				rp.packetData = []byte(attributeValue)
-			case toHeightTag:
-				timeout, err := clienttypes.ParseHeight(attributeValue)
-				if err != nil {
-					cc.log.Warn("error parsing height timeout",
-						zap.String("chain_id", cc.ChainId()),
-						zap.Uint64("sequence", rp.seq),
-						zap.Error(err),
-					)
-					continue EventLoop
-				}
-				rp.timeout = timeout
-			case toTSTag:
-				timeout, err := strconv.ParseUint(attributeValue, 10, 64)
+			if _, ok := err.(*provider.TimeoutOnCloseError); ok {
+				timeout, err := src.MsgTimeoutOnClose(msgTransfer, pp)
 				if err != nil {
-					cc.log.Warn("error parsing timestamp timeout",
-						zap.String("chain_id", cc.ChainId()),
-						zap.Uint64("sequence", rp.seq),
-						zap.Error(err),
-					)
-					continue EventLoop
+					return nil, nil, err
 				}
-				rp.timeoutStamp = timeout
-			case seqTag:
-				seq, err := strconv.ParseUint(attributeValue, 10, 64)
+				return nil, timeout, nil
+			} else {
+				timeout, err := src.MsgTimeout(msgTransfer, pp)
 				if err != nil {
-					cc.log.Warn("error parsing packet sequence",
-						zap.String("chain_id", cc.ChainId()),
-						zap.Error(err),
-					)
-					continue EventLoop
+					return nil, nil, err
 				}
-				rp.seq = seq
+				return nil, timeout, nil
 			}
+		default:
+			return nil, nil, err
 		}
+	}
 
-		// If packet data is nil or sequence number is 0 keep parsing events,
-		// also check that at least the block height or timestamp is set.
-		if rp.ack == nil || rp.packetData == nil || rp.seq == 0 || (rp.timeout.IsZero() && rp.timeoutStamp == 0) {
-			continue
-		}
-
-		ackPackets = append(ackPackets, rp)
-
+	pp, err := src.PacketCommitment(ctx, msgTransfer, srch)
+	if err != nil {
+		return nil, nil, err
 	}
 
-	// If there is a relayPacket, return it
-	if len(ackPackets) > 0 {
-		return ackPackets, nil
+	packet, err := cc.MsgRecvPacket(msgTransfer, pp)
+	if err != nil {
+		return nil, nil, err
 	}
 
-	return nil, fmt.Errorf("no packet data found")
+	return packet, nil, nil
 }
 
-// GetIBCUpdateHeader updates the off chain tendermint light client and
-// returns an IBC Update Header which can be used to update an on chain
-// light client on the destination chain. The source is used to construct
-// the header data.
-func (cc *CosmosProvider) GetIBCUpdateHeader(ctx context.Context, srch int64, dst provider.ChainProvider, dstClientId string) (ibcexported.Header, error) {
-	// Construct header data from light client representing source.
-	h, err := cc.GetLightSignedHeaderAtHeight(ctx, srch)
+// AcknowledgementFromSequence relays an acknowledgement with a given seq on src, source is the sending chain, destination is the receiving chain
+func (cc *CosmosProvider) AcknowledgementFromSequence(ctx context.Context, dst provider.ChainProvider, dsth, seq uint64, dstChanId, dstPortId, srcChanId, srcPortId string) (provider.RelayerMessage, error) {
+	msgRecvPacket, err := dst.QueryRecvPacket(ctx, dstChanId, dstPortId, seq)
 	if err != nil {
 		return nil, err
 	}
 
-	// Inject trusted fields based on previous header data from source
-	return cc.InjectTrustedFields(ctx, h, dst, dstClientId)
-}
-
-func (cc *CosmosProvider) IBCHeaderAtHeight(ctx context.Context, h int64) (provider.IBCHeader, error) {
-	if h == 0 {
-		return nil, fmt.Errorf("height cannot be 0")
+	pp, err := dst.PacketAcknowledgement(ctx, msgRecvPacket, dsth)
+	if err != nil {
+		return nil, err
 	}
-
-	lightBlock, err := cc.LightProvider.LightBlock(ctx, h)
+	msg, err := cc.MsgAcknowledgement(msgRecvPacket, pp)
 	if err != nil {
 		return nil, err
 	}
-
-	return CosmosIBCHeader{
-		SignedHeader: lightBlock.SignedHeader,
-		ValidatorSet: lightBlock.ValidatorSet,
-	}, nil
+	return msg, nil
 }
 
-func (cc *CosmosProvider) GetLightSignedHeaderAtHeight(ctx context.Context, h int64) (ibcexported.Header, error) {
+// QueryIBCHeader returns the IBC compatible block header (CosmosIBCHeader) at a specific height.
+func (cc *CosmosProvider) QueryIBCHeader(ctx context.Context, h int64) (provider.IBCHeader, error) {
 	if h == 0 {
 		return nil, fmt.Errorf("height cannot be 0")
 	}
@@ -1408,14 +954,9 @@ func (cc *CosmosProvider) GetLightSignedHeaderAtHeight(ctx context.Context, h in
 		return nil, err
 	}
 
-	protoVal, err := tmtypes.NewValidatorSet(lightBlock.ValidatorSet.Validators).ToProto()
-	if err != nil {
-		return nil, err
-	}
-
-	return &tmclient.Header{
-		SignedHeader: lightBlock.SignedHeader.ToProto(),
-		ValidatorSet: protoVal,
+	return CosmosIBCHeader{
+		SignedHeader: lightBlock.SignedHeader,
+		ValidatorSet: lightBlock.ValidatorSet,
 	}, nil
 }
 
@@ -1448,19 +989,14 @@ func (cc *CosmosProvider) InjectTrustedFields(ctx context.Context, header ibcexp
 
 	// TODO: this is likely a source of off by 1 errors but may be impossible to change? Maybe this is the
 	// place where we need to fix the upstream query proof issue?
-	var trustedHeader *tmclient.Header
+	var trustedValidators *tmtypes.ValidatorSet
 	if err := retry.Do(func() error {
-		tmpHeader, err := cc.GetLightSignedHeaderAtHeight(ctx, int64(h.TrustedHeight.RevisionHeight+1))
+		ibcHeader, err := cc.QueryIBCHeader(ctx, int64(h.TrustedHeight.RevisionHeight+1))
 		if err != nil {
 			return err
 		}
 
-		th, ok := tmpHeader.(*tmclient.Header)
-		if !ok {
-			err = fmt.Errorf("non-tm client header")
-		}
-
-		trustedHeader = th
+		trustedValidators = ibcHeader.(CosmosIBCHeader).ValidatorSet
 		return err
 	}, retry.Context(ctx), rtyAtt, rtyDel, rtyErr); err != nil {
 		return nil, fmt.Errorf(
@@ -1469,8 +1005,13 @@ func (cc *CosmosProvider) InjectTrustedFields(ctx context.Context, header ibcexp
 		)
 	}
 
+	tvProto, err := trustedValidators.ToProto()
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert trusted validators to proto: %w", err)
+	}
+
 	// inject TrustedValidators into header
-	h.TrustedValidators = trustedHeader.ValidatorSet
+	h.TrustedValidators = tvProto
 	return h, nil
 }
 
@@ -1505,21 +1046,29 @@ func castClientStateToTMType(cs *codectypes.Any) (*tmclient.ClientState, error)
 //DefaultUpgradePath is the default IBC upgrade path set for an on-chain light client
 var defaultUpgradePath = []string{"upgrade", "upgradedIBCState"}
 
-func (cc *CosmosProvider) NewClientState(dstUpdateHeader ibcexported.Header, dstTrustingPeriod, dstUbdPeriod time.Duration, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool) (ibcexported.ClientState, error) {
-	dstTmHeader, ok := dstUpdateHeader.(*tmclient.Header)
-	if !ok {
-		return nil, fmt.Errorf("got data of type %T but wanted tmclient.Header", dstUpdateHeader)
-	}
+// NewClientState creates a new tendermint client state tracking the dst chain.
+func (cc *CosmosProvider) NewClientState(
+	dstChainID string,
+	dstUpdateHeader provider.IBCHeader,
+	dstTrustingPeriod,
+	dstUbdPeriod time.Duration,
+	allowUpdateAfterExpiry,
+	allowUpdateAfterMisbehaviour bool,
+) (ibcexported.ClientState, error) {
+	revisionNumber := clienttypes.ParseChainID(dstChainID)
 
 	// Create the ClientState we want on 'c' tracking 'dst'
 	return &tmclient.ClientState{
-		ChainId:                      dstTmHeader.GetHeader().GetChainID(),
-		TrustLevel:                   tmclient.NewFractionFromTm(light.DefaultTrustLevel),
-		TrustingPeriod:               dstTrustingPeriod,
-		UnbondingPeriod:              dstUbdPeriod,
-		MaxClockDrift:                time.Minute * 10,
-		FrozenHeight:                 clienttypes.ZeroHeight(),
-		LatestHeight:                 dstUpdateHeader.GetHeight().(clienttypes.Height),
+		ChainId:         dstChainID,
+		TrustLevel:      tmclient.NewFractionFromTm(light.DefaultTrustLevel),
+		TrustingPeriod:  dstTrustingPeriod,
+		UnbondingPeriod: dstUbdPeriod,
+		MaxClockDrift:   time.Minute * 10,
+		FrozenHeight:    clienttypes.ZeroHeight(),
+		LatestHeight: clienttypes.Height{
+			RevisionNumber: revisionNumber,
+			RevisionHeight: dstUpdateHeader.Height(),
+		},
 		ProofSpecs:                   commitmenttypes.GetSDKSpecs(),
 		UpgradePath:                  defaultUpgradePath,
 		AllowUpdateAfterExpiry:       allowUpdateAfterExpiry,
diff --git a/relayer/client.go b/relayer/client.go
index 5e1fc41..c54352e 100644
--- a/relayer/client.go
+++ b/relayer/client.go
@@ -29,10 +29,10 @@ func (c *Chain) CreateClients(ctx context.Context, dst *Chain, allowUpdateAfterE
 	}
 
 	// Query the light signed headers for src & dst at the heights srch & dsth, retry if the query fails
-	var srcUpdateHeader, dstUpdateHeader ibcexported.Header
+	var srcUpdateHeader, dstUpdateHeader provider.IBCHeader
 	if err := retry.Do(func() error {
 		var err error
-		srcUpdateHeader, dstUpdateHeader, err = GetLightSignedHeadersAtHeights(ctx, c, dst, srch, dsth)
+		srcUpdateHeader, dstUpdateHeader, err = QueryIBCHeaders(ctx, c, dst, srch, dsth)
 		if err != nil {
 			return err
 		}
@@ -91,11 +91,20 @@ func (c *Chain) CreateClients(ctx context.Context, dst *Chain, allowUpdateAfterE
 	return modifiedSrc || modifiedDst, nil
 }
 
-func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpdateHeader ibcexported.Header, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override bool, customClientTrustingPeriod time.Duration, memo string) (bool, error) {
+// CreateClient creates client tracking dst on src.
+func CreateClient(
+	ctx context.Context,
+	src, dst *Chain,
+	srcUpdateHeader, dstUpdateHeader provider.IBCHeader,
+	allowUpdateAfterExpiry bool,
+	allowUpdateAfterMisbehaviour bool,
+	override bool,
+	customClientTrustingPeriod time.Duration,
+	memo string) (bool, error) {
 	// If a client ID was specified in the path, ensure it exists.
 	if src.PathEnd.ClientID != "" {
 		// TODO: check client is not expired
-		_, err := src.ChainProvider.QueryClientStateResponse(ctx, int64(srcUpdateHeader.GetHeight().GetRevisionHeight()), src.ClientID())
+		_, err := src.ChainProvider.QueryClientStateResponse(ctx, int64(srcUpdateHeader.Height()), src.ClientID())
 		if err != nil {
 			return false, fmt.Errorf("please ensure provided on-chain client (%s) exists on the chain (%s): %v",
 				src.PathEnd.ClientID, src.ChainID(), err)
@@ -129,7 +138,7 @@ func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpda
 		"Creating client",
 		zap.String("src_chain_id", src.ChainID()),
 		zap.String("dst_chain_id", dst.ChainID()),
-		zap.Uint64("dst_header_height", dstUpdateHeader.GetHeight().GetRevisionHeight()),
+		zap.Uint64("dst_header_height", dstUpdateHeader.Height()),
 		zap.Duration("trust_period", tp),
 	)
 
@@ -148,7 +157,7 @@ func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpda
 
 	// We want to create a light client on the src chain which tracks the state of the dst chain.
 	// So we build a new client state from dst and attempt to use this for creating the light client on src.
-	clientState, err := dst.ChainProvider.NewClientState(dstUpdateHeader, tp, ubdPeriod, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour)
+	clientState, err := dst.ChainProvider.NewClientState(dst.ChainID(), dstUpdateHeader, tp, ubdPeriod, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour)
 	if err != nil {
 		return false, fmt.Errorf("failed to create new client state for chain{%s}: %w", dst.ChainID(), err)
 	}
@@ -186,12 +195,8 @@ func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpda
 	// the dst chains implementation of CreateClient, to ensure the proper client/header
 	// logic is executed, but the message gets submitted on the src chain which means
 	// we need to sign with the address from src.
-	acc, err := src.ChainProvider.Address()
-	if err != nil {
-		return false, err
-	}
 
-	createMsg, err := dst.ChainProvider.CreateClient(clientState, dstUpdateHeader, acc)
+	createMsg, err := src.ChainProvider.MsgCreateClient(clientState, dstUpdateHeader.ConsensusState())
 	if err != nil {
 		return false, fmt.Errorf("failed to compose CreateClient msg for chain{%s} tracking the state of chain{%s}: %w",
 			src.ChainID(), dst.ChainID(), err)
@@ -238,82 +243,130 @@ func CreateClient(ctx context.Context, src, dst *Chain, srcUpdateHeader, dstUpda
 	return true, nil
 }
 
-// UpdateClients updates clients for src on dst and dst on src given the configured paths
-func (c *Chain) UpdateClients(ctx context.Context, dst *Chain, memo string) (err error) {
-	var (
-		srcUpdateHeader, dstUpdateHeader ibcexported.Header
-		srch, dsth                       int64
-	)
-
-	if err = retry.Do(func() error {
-		srch, dsth, err = QueryLatestHeights(ctx, c, dst)
-		if err != nil {
-			return err
-		}
-		return nil
+// MsgUpdateClient queries for the current client state on dst,
+// then queries for the latest and trusted headers on src
+// in order to build a MsgUpdateClient message for dst.
+func MsgUpdateClient(
+	ctx context.Context,
+	src, dst *Chain,
+	srch, dsth int64,
+) (provider.RelayerMessage, error) {
+	var dstClientState ibcexported.ClientState
+	if err := retry.Do(func() error {
+		var err error
+		dstClientState, err = dst.ChainProvider.QueryClientState(ctx, dsth, dst.ClientID())
+		return err
 	}, retry.Context(ctx), RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
-		c.log.Info(
-			"Failed to get query latest heights when updating clients",
-			zap.String("src_chain_id", c.ChainID()),
-			zap.String("dst_chain_id", dst.ChainID()),
+		dst.log.Info(
+			"Failed to query client state when updating clients",
+			zap.String("client_id", dst.ClientID()),
 			zap.Uint("attempt", n+1),
 			zap.Uint("max_attempts", RtyAttNum),
 			zap.Error(err),
 		)
 	})); err != nil {
-		return err
+		return nil, err
 	}
 
-	if err = retry.Do(func() error {
-		srcUpdateHeader, dstUpdateHeader, err = GetIBCUpdateHeaders(ctx, srch, dsth, c.ChainProvider, dst.ChainProvider, c.ClientID(), dst.ClientID())
-		if err != nil {
+	var srcHeader, dstTrustedHeader provider.IBCHeader
+
+	eg, egCtx := errgroup.WithContext(ctx)
+	eg.Go(func() error {
+		return retry.Do(func() error {
+			var err error
+			srcHeader, err = src.ChainProvider.QueryIBCHeader(egCtx, srch)
 			return err
-		}
-		return nil
+		}, retry.Context(egCtx), RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
+			src.log.Info(
+				"Failed to query IBC header when building update client message",
+				zap.String("client_id", dst.ClientID()),
+				zap.Uint("attempt", n+1),
+				zap.Uint("max_attempts", RtyAttNum),
+				zap.Error(err),
+			)
+		}))
+	})
+	eg.Go(func() error {
+		return retry.Do(func() error {
+			var err error
+			dstTrustedHeader, err = src.ChainProvider.QueryIBCHeader(egCtx, int64(dstClientState.GetLatestHeight().GetRevisionHeight())+1)
+			return err
+		}, retry.Context(egCtx), RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
+			src.log.Info(
+				"Failed to query IBC header when building update client message",
+				zap.String("client_id", dst.ClientID()),
+				zap.Uint("attempt", n+1),
+				zap.Uint("max_attempts", RtyAttNum),
+				zap.Error(err),
+			)
+		}))
+	})
+
+	if err := eg.Wait(); err != nil {
+		return nil, err
+	}
+
+	var updateHeader ibcexported.Header
+	if err := retry.Do(func() error {
+		var err error
+		updateHeader, err = src.ChainProvider.MsgUpdateClientHeader(srcHeader, dstClientState.GetLatestHeight().(clienttypes.Height), dstTrustedHeader)
+		return err
 	}, retry.Context(ctx), RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
-		c.log.Info(
-			"Failed to get IBC update headers",
+		src.log.Info(
+			"Failed to build update client header",
+			zap.String("client_id", dst.ClientID()),
 			zap.Uint("attempt", n+1),
 			zap.Uint("max_attempts", RtyAttNum),
 			zap.Error(err),
 		)
-		srch, dsth, _ = QueryLatestHeights(ctx, c, dst)
 	})); err != nil {
-		return err
+		return nil, err
 	}
 
-	srcUpdateMsg, err := c.ChainProvider.MsgUpdateClient(c.ClientID(), dstUpdateHeader)
+	// updates off-chain light client
+	return dst.ChainProvider.MsgUpdateClient(dst.ClientID(), updateHeader)
+}
+
+// UpdateClients updates clients for src on dst and dst on src given the configured paths.
+func UpdateClients(
+	ctx context.Context,
+	src, dst *Chain,
+	memo string,
+) error {
+	srch, dsth, err := QueryLatestHeights(ctx, src, dst)
 	if err != nil {
-		c.log.Debug(
-			"Failed to update source client",
-			zap.String("src_chain", c.ChainID()),
-			zap.Error(err),
-		)
 		return err
 	}
 
-	dstUpdateMsg, err := dst.ChainProvider.MsgUpdateClient(dst.ClientID(), srcUpdateHeader)
-	if err != nil {
-		dst.log.Debug(
-			"Failed to update destination client",
-			zap.String("dst_chain", dst.ChainID()),
-			zap.Error(err),
-		)
+	var srcMsgUpdateClient, dstMsgUpdateClient provider.RelayerMessage
+	eg, egCtx := errgroup.WithContext(ctx)
+	eg.Go(func() error {
+		var err error
+		srcMsgUpdateClient, err = MsgUpdateClient(egCtx, dst, src, dsth, srch)
+		return err
+	})
+	eg.Go(func() error {
+		var err error
+		dstMsgUpdateClient, err = MsgUpdateClient(egCtx, src, dst, srch, dsth)
+		return err
+	})
+
+	if err = eg.Wait(); err != nil {
 		return err
 	}
 
 	clients := &RelayMsgs{
-		Src: []provider.RelayerMessage{srcUpdateMsg},
-		Dst: []provider.RelayerMessage{dstUpdateMsg},
+		Src: []provider.RelayerMessage{srcMsgUpdateClient},
+		Dst: []provider.RelayerMessage{dstMsgUpdateClient},
 	}
 
 	// Send msgs to both chains
-	result := clients.Send(ctx, c.log, AsRelayMsgSender(c), AsRelayMsgSender(dst), memo)
+	result := clients.Send(ctx, src.log, AsRelayMsgSender(src), AsRelayMsgSender(dst), memo)
 	if err := result.Error(); err != nil {
 		if result.PartiallySent() {
-			c.log.Info(
+			src.log.Info(
 				"Partial success when updating clients",
-				zap.String("src_chain_id", c.ChainID()),
+				zap.String("src_chain_id", src.ChainID()),
 				zap.String("dst_chain_id", dst.ChainID()),
 				zap.Object("send_result", result),
 			)
@@ -321,54 +374,64 @@ func (c *Chain) UpdateClients(ctx context.Context, dst *Chain, memo string) (err
 		return err
 	}
 
-	c.log.Info(
+	src.log.Info(
 		"Clients updated",
-		zap.String("src_chain_id", c.ChainID()),
-		zap.String("src_client", c.PathEnd.ClientID),
-		zap.Stringer("src_height", MustGetHeight(srcUpdateHeader.GetHeight())),
-		zap.Uint64("src_revision_height", srcUpdateHeader.GetHeight().GetRevisionHeight()),
+		zap.String("src_chain_id", src.ChainID()),
+		zap.String("src_client", src.PathEnd.ClientID),
 
 		zap.String("dst_chain_id", dst.ChainID()),
 		zap.String("dst_client", dst.PathEnd.ClientID),
-		zap.Stringer("dst_height", MustGetHeight(dstUpdateHeader.GetHeight())),
-		zap.Uint64("dst_revision_height", dstUpdateHeader.GetHeight().GetRevisionHeight()),
 	)
 
 	return nil
 }
 
-// UpgradeClients upgrades the client on src after dst chain has undergone an upgrade.
-func (c *Chain) UpgradeClients(ctx context.Context, dst *Chain, height int64, memo string) error {
-	dstHeader, err := dst.ChainProvider.GetLightSignedHeaderAtHeight(ctx, height)
+// UpgradeClient upgrades the client on dst after src chain has undergone an upgrade.
+// If height is zero, will use the latest height of the source chain.
+// If height is non-zero, it will be used for queries on the source chain.
+func UpgradeClient(
+	ctx context.Context,
+	src, dst *Chain,
+	height int64,
+	memo string,
+) error {
+	srch, dsth, err := QueryLatestHeights(ctx, src, dst)
 	if err != nil {
 		return err
 	}
 
-	// updates off-chain light client
-	updateMsg, err := c.ChainProvider.MsgUpdateClient(c.ClientID(), dstHeader)
-	if err != nil {
-		return err
+	if height != 0 {
+		srch = height
 	}
 
-	if height == 0 {
-		height, err = dst.ChainProvider.QueryLatestHeight(ctx)
-		if err != nil {
-			return err
-		}
-	}
+	var eg errgroup.Group
 
-	// query proofs on counterparty
-	clientRes, err := dst.ChainProvider.QueryUpgradedClient(ctx, height)
-	if err != nil {
+	var clientRes *clienttypes.QueryClientStateResponse
+	eg.Go(func() error {
+		var err error
+		clientRes, err = src.ChainProvider.QueryUpgradedClient(ctx, srch)
 		return err
-	}
+	})
 
-	consRes, err := dst.ChainProvider.QueryUpgradedConsState(ctx, height)
-	if err != nil {
+	var consRes *clienttypes.QueryConsensusStateResponse
+	eg.Go(func() error {
+		var err error
+		consRes, err = src.ChainProvider.QueryUpgradedConsState(ctx, srch)
+		return err
+	})
+
+	var updateMsg provider.RelayerMessage
+	eg.Go(func() error {
+		var err error
+		updateMsg, err = MsgUpdateClient(ctx, src, dst, srch, dsth)
+		return err
+	})
+
+	if err := eg.Wait(); err != nil {
 		return err
 	}
 
-	upgradeMsg, err := c.ChainProvider.MsgUpgradeClient(c.ClientID(), consRes, clientRes)
+	upgradeMsg, err := dst.ChainProvider.MsgUpgradeClient(dst.ClientID(), consRes, clientRes)
 	if err != nil {
 		return err
 	}
@@ -378,9 +441,9 @@ func (c *Chain) UpgradeClients(ctx context.Context, dst *Chain, height int64, me
 		upgradeMsg,
 	}
 
-	res, _, err := c.ChainProvider.SendMessages(ctx, msgs, memo)
+	res, _, err := dst.ChainProvider.SendMessages(ctx, msgs, memo)
 	if err != nil {
-		c.LogFailedTx(res, err, msgs)
+		dst.LogFailedTx(res, err, msgs)
 		return err
 	}
 
diff --git a/relayer/naive-strategy.go b/relayer/naive-strategy.go
index 993b710..0b5e3d7 100644
--- a/relayer/naive-strategy.go
+++ b/relayer/naive-strategy.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/avast/retry-go/v4"
 	chantypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"
-	ibcexported "github.com/cosmos/ibc-go/v4/modules/core/exported"
 	"github.com/cosmos/relayer/v2/relayer/provider"
 	"go.uber.org/zap"
 	"golang.org/x/sync/errgroup"
@@ -384,6 +383,11 @@ func (rs *RelaySequences) Empty() bool {
 
 // RelayAcknowledgements creates transactions to relay acknowledgements from src to dst and from dst to src
 func RelayAcknowledgements(ctx context.Context, log *zap.Logger, src, dst *Chain, sp RelaySequences, maxTxSize, maxMsgLength uint64, memo string, srcChannel *chantypes.IdentifiedChannel) error {
+	srch, dsth, err := QueryLatestHeights(ctx, src, dst)
+	if err != nil {
+		return err
+	}
+
 	// set the maximum relay transaction constraints
 	msgs := &RelayMsgs{
 		Src:          []provider.RelayerMessage{},
@@ -392,116 +396,74 @@ func RelayAcknowledgements(ctx context.Context, log *zap.Logger, src, dst *Chain
 		MaxMsgLength: maxMsgLength,
 	}
 
-	select {
-	case <-ctx.Done():
-		return ctx.Err()
-	default:
-		srch, dsth, err := QueryLatestHeights(ctx, src, dst)
+	// add messages for received packets on dst
+	for _, seq := range sp.Dst {
+		// dst wrote the ack. acknowledgementFromSequence will query the acknowledgement
+		// from the counterparty chain (second chain provided in the arguments). The message
+		// should be sent to src.
+		relayAckMsgs, err := src.ChainProvider.AcknowledgementFromSequence(ctx, dst.ChainProvider, uint64(dsth), seq, srcChannel.Counterparty.ChannelId, srcChannel.Counterparty.PortId, srcChannel.ChannelId, srcChannel.PortId)
 		if err != nil {
 			return err
 		}
 
-		var (
-			srcHeader, dstHeader ibcexported.Header
-		)
-		eg, egCtx := errgroup.WithContext(ctx)
-		eg.Go(func() error {
-			var err error
-			srcHeader, err = src.ChainProvider.GetIBCUpdateHeader(egCtx, srch, dst.ChainProvider, dst.PathEnd.ClientID)
-			return err
-		})
-		eg.Go(func() error {
-			var err error
-			dstHeader, err = dst.ChainProvider.GetIBCUpdateHeader(egCtx, dsth, src.ChainProvider, src.PathEnd.ClientID)
-			return err
-		})
-		if err := eg.Wait(); err != nil {
-			return err
+		// Do not allow nil messages to the queued, or else we will panic in send()
+		if relayAckMsgs != nil {
+			msgs.Src = append(msgs.Src, relayAckMsgs)
 		}
+	}
 
-		srcUpdateMsg, err := src.ChainProvider.MsgUpdateClient(src.PathEnd.ClientID, dstHeader)
-		if err != nil {
-			return err
-		}
-		dstUpdateMsg, err := dst.ChainProvider.MsgUpdateClient(dst.PathEnd.ClientID, srcHeader)
+	// add messages for received packets on src
+	for _, seq := range sp.Src {
+		// src wrote the ack. acknowledgementFromSequence will query the acknowledgement
+		// from the counterparty chain (second chain provided in the arguments). The message
+		// should be sent to dst.
+		relayAckMsgs, err := dst.ChainProvider.AcknowledgementFromSequence(ctx, src.ChainProvider, uint64(srch), seq, srcChannel.ChannelId, srcChannel.PortId, srcChannel.Counterparty.ChannelId, srcChannel.Counterparty.PortId)
 		if err != nil {
 			return err
 		}
 
-		// add messages for received packets on dst
-		for _, seq := range sp.Dst {
-			// dst wrote the ack. acknowledgementFromSequence will query the acknowledgement
-			// from the counterparty chain (second chain provided in the arguments). The message
-			// should be sent to src.
-			relayAckMsgs, err := src.ChainProvider.AcknowledgementFromSequence(ctx, dst.ChainProvider, uint64(dsth), seq, srcChannel.Counterparty.ChannelId, srcChannel.Counterparty.PortId, srcChannel.ChannelId, srcChannel.PortId)
-			if err != nil {
-				return err
-			}
-
-			// Do not allow nil messages to the queued, or else we will panic in send()
-			if relayAckMsgs != nil {
-				msgs.Src = append(msgs.Src, relayAckMsgs)
-			}
+		// Do not allow nil messages to the queued, or else we will panic in send()
+		if relayAckMsgs != nil {
+			msgs.Dst = append(msgs.Dst, relayAckMsgs)
 		}
+	}
 
-		// add messages for received packets on src
-		for _, seq := range sp.Src {
-			// src wrote the ack. acknowledgementFromSequence will query the acknowledgement
-			// from the counterparty chain (second chain provided in the arguments). The message
-			// should be sent to dst.
-			relayAckMsgs, err := dst.ChainProvider.AcknowledgementFromSequence(ctx, src.ChainProvider, uint64(srch), seq, srcChannel.ChannelId, srcChannel.PortId, srcChannel.Counterparty.ChannelId, srcChannel.Counterparty.PortId)
-			if err != nil {
-				return err
-			}
+	if !msgs.Ready() {
+		log.Info(
+			"No acknowledgements to relay",
+			zap.String("src_chain_id", src.ChainID()),
+			zap.String("src_port_id", srcChannel.PortId),
+			zap.String("dst_chain_id", dst.ChainID()),
+			zap.String("dst_port_id", srcChannel.Counterparty.PortId),
+		)
+		return nil
+	}
 
-			// Do not allow nil messages to the queued, or else we will panic in send()
-			if relayAckMsgs != nil {
-				msgs.Dst = append(msgs.Dst, relayAckMsgs)
-			}
-		}
+	if err := msgs.PrependMsgUpdateClient(ctx, src, dst, srch, dsth); err != nil {
+		return err
+	}
 
-		if !msgs.Ready() {
+	// send messages to their respective chains
+	result := msgs.Send(ctx, log, AsRelayMsgSender(src), AsRelayMsgSender(dst), memo)
+	if err := result.Error(); err != nil {
+		if result.PartiallySent() {
 			log.Info(
-				"No acknowledgements to relay",
+				"Partial success when relaying acknowledgements",
 				zap.String("src_chain_id", src.ChainID()),
 				zap.String("src_port_id", srcChannel.PortId),
 				zap.String("dst_chain_id", dst.ChainID()),
 				zap.String("dst_port_id", srcChannel.Counterparty.PortId),
+				zap.Error(err),
 			)
-			return nil
-		}
-
-		// Prepend non-empty msg lists with UpdateClient
-		if len(msgs.Dst) != 0 {
-			msgs.Dst = append([]provider.RelayerMessage{dstUpdateMsg}, msgs.Dst...)
-		}
-
-		if len(msgs.Src) != 0 {
-			msgs.Src = append([]provider.RelayerMessage{srcUpdateMsg}, msgs.Src...)
-		}
-
-		// send messages to their respective chains
-		result := msgs.Send(ctx, log, AsRelayMsgSender(src), AsRelayMsgSender(dst), memo)
-		if err := result.Error(); err != nil {
-			if result.PartiallySent() {
-				log.Info(
-					"Partial success when relaying acknowledgements",
-					zap.String("src_chain_id", src.ChainID()),
-					zap.String("src_port_id", srcChannel.PortId),
-					zap.String("dst_chain_id", dst.ChainID()),
-					zap.String("dst_port_id", srcChannel.Counterparty.PortId),
-					zap.Error(err),
-				)
-			}
-			return err
 		}
+		return err
+	}
 
-		if result.SuccessfulSrcBatches > 0 {
-			src.logPacketsRelayed(dst, result.SuccessfulSrcBatches, srcChannel)
-		}
-		if result.SuccessfulDstBatches > 0 {
-			dst.logPacketsRelayed(src, result.SuccessfulDstBatches, srcChannel)
-		}
+	if result.SuccessfulSrcBatches > 0 {
+		src.logPacketsRelayed(dst, result.SuccessfulSrcBatches, srcChannel)
+	}
+	if result.SuccessfulDstBatches > 0 {
+		dst.logPacketsRelayed(src, result.SuccessfulDstBatches, srcChannel)
 	}
 
 	return nil
@@ -509,6 +471,11 @@ func RelayAcknowledgements(ctx context.Context, log *zap.Logger, src, dst *Chain
 
 // RelayPackets creates transactions to relay packets from src to dst and from dst to src
 func RelayPackets(ctx context.Context, log *zap.Logger, src, dst *Chain, sp RelaySequences, maxTxSize, maxMsgLength uint64, memo string, srcChannel *chantypes.IdentifiedChannel) error {
+	srch, dsth, err := QueryLatestHeights(ctx, src, dst)
+	if err != nil {
+		return err
+	}
+
 	// set the maximum relay transaction constraints
 	msgs := &RelayMsgs{
 		Src:          []provider.RelayerMessage{},
@@ -517,83 +484,63 @@ func RelayPackets(ctx context.Context, log *zap.Logger, src, dst *Chain, sp Rela
 		MaxMsgLength: maxMsgLength,
 	}
 
-	select {
-	case <-ctx.Done():
-		return ctx.Err()
-	default:
-		srch, dsth, err := QueryLatestHeights(ctx, src, dst)
-		if err != nil {
-			return err
-		}
+	eg, egCtx := errgroup.WithContext(ctx)
+	// add messages for sequences on src
+	eg.Go(func() error {
+		return AddMessagesForSequences(egCtx, sp.Src, src, dst, srch, dsth, &msgs.Src, &msgs.Dst,
+			srcChannel.ChannelId, srcChannel.PortId, srcChannel.Counterparty.ChannelId, srcChannel.Counterparty.PortId, srcChannel.Ordering)
+	})
 
-		eg, egCtx := errgroup.WithContext(ctx)
-		// add messages for sequences on src
-		eg.Go(func() error {
-			return AddMessagesForSequences(egCtx, sp.Src, src, dst, srch, dsth, &msgs.Src, &msgs.Dst,
-				srcChannel.ChannelId, srcChannel.PortId, srcChannel.Counterparty.ChannelId, srcChannel.Counterparty.PortId, srcChannel.Ordering)
-		})
+	// add messages for sequences on dst
+	eg.Go(func() error {
+		return AddMessagesForSequences(egCtx, sp.Dst, dst, src, dsth, srch, &msgs.Dst, &msgs.Src,
+			srcChannel.Counterparty.ChannelId, srcChannel.Counterparty.PortId, srcChannel.ChannelId, srcChannel.PortId, srcChannel.Ordering)
+	})
 
-		// add messages for sequences on dst
-		eg.Go(func() error {
-			return AddMessagesForSequences(egCtx, sp.Dst, dst, src, dsth, srch, &msgs.Dst, &msgs.Src,
-				srcChannel.Counterparty.ChannelId, srcChannel.Counterparty.PortId, srcChannel.ChannelId, srcChannel.PortId, srcChannel.Ordering)
-		})
+	if err = eg.Wait(); err != nil {
+		return err
+	}
 
-		if err = eg.Wait(); err != nil {
-			return err
-		}
+	if !msgs.Ready() {
+		log.Info(
+			"No packets to relay",
+			zap.String("src_chain_id", src.ChainID()),
+			zap.String("src_port_id", srcChannel.PortId),
+			zap.String("dst_chain_id", dst.ChainID()),
+			zap.String("dst_port_id", srcChannel.Counterparty.PortId),
+		)
+		return nil
+	}
+
+	if err := msgs.PrependMsgUpdateClient(ctx, src, dst, srch, dsth); err != nil {
+		return err
+	}
 
-		if !msgs.Ready() {
+	// send messages to their respective chains
+	result := msgs.Send(ctx, log, AsRelayMsgSender(src), AsRelayMsgSender(dst), memo)
+	if err := result.Error(); err != nil {
+		if result.PartiallySent() {
 			log.Info(
-				"No packets to relay",
+				"Partial success when relaying packets",
 				zap.String("src_chain_id", src.ChainID()),
 				zap.String("src_port_id", srcChannel.PortId),
 				zap.String("dst_chain_id", dst.ChainID()),
 				zap.String("dst_port_id", srcChannel.Counterparty.PortId),
+				zap.Error(err),
 			)
-			return nil
-		}
-
-		// Prepend non-empty msg lists with UpdateClient
-
-		eg, egCtx = errgroup.WithContext(ctx) // New errgroup because previous egCtx is canceled at this point.
-		eg.Go(func() error {
-			return PrependUpdateClientMsg(egCtx, &msgs.Dst, src, dst, srch)
-		})
-
-		eg.Go(func() error {
-			return PrependUpdateClientMsg(egCtx, &msgs.Src, dst, src, dsth)
-		})
-
-		if err = eg.Wait(); err != nil {
-			return err
 		}
+		return err
+	}
 
-		// send messages to their respective chains
-		result := msgs.Send(ctx, log, AsRelayMsgSender(src), AsRelayMsgSender(dst), memo)
-		if err := result.Error(); err != nil {
-			if result.PartiallySent() {
-				log.Info(
-					"Partial success when relaying packets",
-					zap.String("src_chain_id", src.ChainID()),
-					zap.String("src_port_id", srcChannel.PortId),
-					zap.String("dst_chain_id", dst.ChainID()),
-					zap.String("dst_port_id", srcChannel.Counterparty.PortId),
-					zap.Error(err),
-				)
-			}
-			return err
-		}
+	if result.SuccessfulSrcBatches > 0 {
+		src.logPacketsRelayed(dst, result.SuccessfulSrcBatches, srcChannel)
+	}
+	if result.SuccessfulDstBatches > 0 {
+		dst.logPacketsRelayed(src, result.SuccessfulDstBatches, srcChannel)
+	}
 
-		if result.SuccessfulSrcBatches > 0 {
-			src.logPacketsRelayed(dst, result.SuccessfulSrcBatches, srcChannel)
-		}
-		if result.SuccessfulDstBatches > 0 {
-			dst.logPacketsRelayed(src, result.SuccessfulDstBatches, srcChannel)
-		}
+	return nil
 
-		return nil
-	}
 }
 
 // AddMessagesForSequences constructs RecvMsgs and TimeoutMsgs from sequence numbers on a src chain
@@ -608,13 +555,12 @@ func AddMessagesForSequences(
 	order chantypes.Order,
 ) error {
 	for _, seq := range sequences {
-		recvMsg, timeoutMsg, err := src.ChainProvider.RelayPacketFromSequence(
+		recvMsg, timeoutMsg, err := dst.ChainProvider.RelayPacketFromSequence(
 			ctx,
-			src.ChainProvider, dst.ChainProvider,
+			src.ChainProvider,
 			uint64(srch), uint64(dsth),
 			seq,
-			dstChanID, dstPortID, dst.ClientID(),
-			srcChanID, srcPortID, src.ClientID(),
+			srcChanID, srcPortID,
 			order,
 		)
 		if err != nil {
@@ -644,53 +590,3 @@ func AddMessagesForSequences(
 
 	return nil
 }
-
-// PrependUpdateClientMsg adds an UpdateClient msg to the front of non-empty msg lists
-func PrependUpdateClientMsg(ctx context.Context, msgs *[]provider.RelayerMessage, src, dst *Chain, srch int64) error {
-	if len(*msgs) == 0 {
-		return nil
-	}
-
-	// Query IBC Update Header
-	var srcHeader ibcexported.Header
-	if err := retry.Do(func() error {
-		var err error
-		srcHeader, err = src.ChainProvider.GetIBCUpdateHeader(ctx, srch, dst.ChainProvider, dst.PathEnd.ClientID)
-		return err
-	}, retry.Context(ctx), RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
-		src.log.Info(
-			"PrependUpdateClientMsg: failed to get IBC update header",
-			zap.String("src_chain_id", src.ChainID()),
-			zap.String("dst_chain_id", dst.ChainID()),
-			zap.Uint("attempt", n+1),
-			zap.Uint("attempt_limit", RtyAttNum),
-			zap.Error(err),
-		)
-
-	})); err != nil {
-		return err
-	}
-
-	// Construct UpdateClient msg
-	var updateMsg provider.RelayerMessage
-	if err := retry.Do(func() error {
-		var err error
-		updateMsg, err = dst.ChainProvider.MsgUpdateClient(dst.PathEnd.ClientID, srcHeader)
-		return err
-	}, retry.Context(ctx), RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
-		dst.log.Info(
-			"PrependUpdateClientMsg: failed to build message",
-			zap.String("dst_chain_id", dst.ChainID()),
-			zap.Uint("attempt", n+1),
-			zap.Uint("attempt_limit", RtyAttNum),
-			zap.Error(err),
-		)
-	})); err != nil {
-		return err
-	}
-
-	// Prepend UpdateClient msg to the slice of msgs
-	*msgs = append([]provider.RelayerMessage{updateMsg}, *msgs...)
-
-	return nil
-}
diff --git a/relayer/processor/path_processor_internal.go b/relayer/processor/path_processor_internal.go
index 18b5ebb..0dcd694 100644
--- a/relayer/processor/path_processor_internal.go
+++ b/relayer/processor/path_processor_internal.go
@@ -369,7 +369,7 @@ func (pp *PathProcessor) assembleMsgUpdateClient(ctx context.Context, src, dst *
 			return nil, fmt.Errorf("observed client trusted height: %d does not equal latest client state height: %d",
 				trustedConsensusHeight.RevisionHeight, clientConsensusHeight.RevisionHeight)
 		}
-		header, err := src.chainProvider.IBCHeaderAtHeight(ctx, int64(clientConsensusHeight.RevisionHeight+1))
+		header, err := src.chainProvider.QueryIBCHeader(ctx, int64(clientConsensusHeight.RevisionHeight+1))
 		if err != nil {
 			return nil, fmt.Errorf("error getting IBC header at height: %d for chain_id: %s, %w", clientConsensusHeight.RevisionHeight+1, src.info.ChainID, err)
 		}
diff --git a/relayer/provider/matcher.go b/relayer/provider/matcher.go
index fba6fbb..7ad91e6 100644
--- a/relayer/provider/matcher.go
+++ b/relayer/provider/matcher.go
@@ -78,19 +78,19 @@ func tendermintMatcher(ctx context.Context, src, dst ChainProvider, existingClie
 		}
 
 		// Construct a header for the consensus state of the counterparty chain.
-		header, err := dst.GetLightSignedHeaderAtHeight(ctx, int64(existingClientState.GetLatestHeight().GetRevisionHeight()))
+		ibcHeader, err := dst.QueryIBCHeader(ctx, int64(existingClientState.GetLatestHeight().GetRevisionHeight()))
 		if err != nil {
 			return "", err
 		}
 
-		tmHeader, ok := header.(*tmclient.Header)
+		consensusState, ok := ibcHeader.ConsensusState().(*tmclient.ConsensusState)
 		if !ok {
-			return "", fmt.Errorf("got type(%T) expected type(*tmclient.Header)", header)
+			return "", fmt.Errorf("got type(%T) expected type(*tmclient.ConsensusState)", consensusState)
 		}
 
 		// Determine if the existing consensus state on src for the potential matching client is identical
 		// to the consensus state of the counterparty chain.
-		if isMatchingTendermintConsensusState(existingConsensusState, tmHeader.ConsensusState()) {
+		if isMatchingTendermintConsensusState(existingConsensusState, consensusState) {
 			return existingClientID, nil // found matching client
 		}
 	}
diff --git a/relayer/provider/provider.go b/relayer/provider/provider.go
index 0c00f85..e61c822 100644
--- a/relayer/provider/provider.go
+++ b/relayer/provider/provider.go
@@ -45,8 +45,9 @@ type LatestBlock struct {
 }
 
 type IBCHeader interface {
-	IBCHeaderIndicator()
 	Height() uint64
+	ConsensusState() ibcexported.ConsensusState
+	// require conversion implementation for third party chains
 }
 
 // ClientState holds the current state of a client from a single chain's perspective
@@ -189,8 +190,15 @@ type ChainProvider interface {
 	KeyProvider
 
 	Init() error
-	CreateClient(clientState ibcexported.ClientState, dstHeader ibcexported.Header, signer string) (RelayerMessage, error)
-	SubmitMisbehavior( /*TODO TBD*/ ) (RelayerMessage, error)
+
+	// [Begin] Client IBC message assembly functions
+	NewClientState(dstChainID string, dstIBCHeader IBCHeader, dstTrustingPeriod, dstUbdPeriod time.Duration, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool) (ibcexported.ClientState, error)
+
+	MsgCreateClient(clientState ibcexported.ClientState, consensusState ibcexported.ConsensusState) (RelayerMessage, error)
+
+	MsgUpgradeClient(srcClientId string, consRes *clienttypes.QueryConsensusStateResponse, clientRes *clienttypes.QueryClientStateResponse) (RelayerMessage, error)
+	// MsgSubmitMisbehavior(/*TODO*/)
+	// [End] Client IBC message assembly functions
 
 	// ValidatePacket makes sure packet is valid to be relayed.
 	// It should return TimeoutHeightError, TimeoutTimestampError, or TimeoutOnCloseError
@@ -315,24 +323,14 @@ type ChainProvider interface {
 
 	// [End] Client IBC message assembly
 
-	// TODO remove these message assembly functions in favor of the above.
-	MsgRelayAcknowledgement(ctx context.Context, dst ChainProvider, dstChanId, dstPortId, srcChanId, srcPortId string, dsth int64, packet RelayPacket) (RelayerMessage, error)
-	MsgRelayTimeout(ctx context.Context, dst ChainProvider, dsth int64, packet RelayPacket, dstChanId, dstPortId, srcChanId, srcPortId string, order chantypes.Order) (RelayerMessage, error)
-	MsgRelayRecvPacket(ctx context.Context, dst ChainProvider, dsth int64, packet RelayPacket, dstChanId, dstPortId, srcChanId, srcPortId string) (RelayerMessage, error)
-	MsgUpgradeClient(srcClientId string, consRes *clienttypes.QueryConsensusStateResponse, clientRes *clienttypes.QueryClientStateResponse) (RelayerMessage, error)
-	RelayPacketFromSequence(ctx context.Context, src, dst ChainProvider, srch, dsth, seq uint64, dstChanId, dstPortId, dstClientId, srcChanId, srcPortId, srcClientId string, order chantypes.Order) (RelayerMessage, RelayerMessage, error)
-	AcknowledgementFromSequence(ctx context.Context, dst ChainProvider, dsth, seq uint64, dstChanId, dstPortId, srcChanId, srcPortId string) (RelayerMessage, error)
+	// Query heavy relay methods. Only used for flushing old packets.
+
+	RelayPacketFromSequence(ctx context.Context, src ChainProvider, srch, dsth, seq uint64, srcChanID, srcPortID string, order chantypes.Order) (RelayerMessage, RelayerMessage, error)
+	AcknowledgementFromSequence(ctx context.Context, dst ChainProvider, dsth, seq uint64, dstChanID, dstPortID, srcChanID, srcPortID string) (RelayerMessage, error)
 
 	SendMessage(ctx context.Context, msg RelayerMessage, memo string) (*RelayerTxResponse, bool, error)
 	SendMessages(ctx context.Context, msgs []RelayerMessage, memo string) (*RelayerTxResponse, bool, error)
 
-	// TODO consolidate with IBCHeaderAtHeight
-	GetLightSignedHeaderAtHeight(ctx context.Context, h int64) (ibcexported.Header, error)
-	GetIBCUpdateHeader(ctx context.Context, srch int64, dst ChainProvider, dstClientId string) (ibcexported.Header, error)
-
-	// IBCHeaderAtHeight returns the IBC compatible block header at a specific height.
-	IBCHeaderAtHeight(ctx context.Context, h int64) (IBCHeader, error)
-
 	ChainName() string
 	ChainId() string
 	Type() string
@@ -348,11 +346,17 @@ type ChainProvider interface {
 // Do we need intermediate types? i.e. can we use the SDK types for both substrate and cosmos?
 type QueryProvider interface {
 	// chain
-	BlockTime(ctx context.Context, height int64) (int64, error)
+	BlockTime(ctx context.Context, height int64) (time.Time, error)
 	QueryTx(ctx context.Context, hashHex string) (*RelayerTxResponse, error)
 	QueryTxs(ctx context.Context, page, limit int, events []string) ([]*RelayerTxResponse, error)
 	QueryLatestHeight(ctx context.Context) (int64, error)
-	QueryHeaderAtHeight(ctx context.Context, height int64) (ibcexported.Header, error)
+
+	// QueryIBCHeader returns the IBC compatible block header at a specific height.
+	QueryIBCHeader(ctx context.Context, h int64) (IBCHeader, error)
+
+	// query packet info for sequence
+	QuerySendPacket(ctx context.Context, srcChanID, srcPortID string, sequence uint64) (PacketInfo, error)
+	QueryRecvPacket(ctx context.Context, dstChanID, dstPortID string, sequence uint64) (PacketInfo, error)
 
 	// bank
 	QueryBalance(ctx context.Context, keyName string) (sdk.Coins, error)
@@ -377,7 +381,6 @@ type QueryProvider interface {
 	GenerateConnHandshakeProof(ctx context.Context, height int64, clientId, connId string) (clientState ibcexported.ClientState,
 		clientStateProof []byte, consensusProof []byte, connectionProof []byte,
 		connectionProofHeight ibcexported.Height, err error)
-	NewClientState(dstUpdateHeader ibcexported.Header, dstTrustingPeriod, dstUbdPeriod time.Duration, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool) (ibcexported.ClientState, error)
 
 	// ics 04 - channel
 	QueryChannel(ctx context.Context, height int64, channelid, portid string) (chanRes *chantypes.QueryChannelResponse, err error)
diff --git a/relayer/query.go b/relayer/query.go
index 0b6cf50..6e6e7e6 100644
--- a/relayer/query.go
+++ b/relayer/query.go
@@ -3,7 +3,6 @@ package relayer
 import (
 	"context"
 	"fmt"
-	"strconv"
 	"strings"
 
 	"github.com/avast/retry-go/v4"
@@ -18,7 +17,7 @@ import (
 	"golang.org/x/sync/errgroup"
 )
 
-// QueryLatestHeights returns the heights of multiple chains at once
+// QueryLatestHeights queries the heights of multiple chains at once
 func QueryLatestHeights(ctx context.Context, src, dst *Chain) (srch, dsth int64, err error) {
 	eg, egCtx := errgroup.WithContext(ctx)
 	eg.Go(func() error {
@@ -35,6 +34,59 @@ func QueryLatestHeights(ctx context.Context, src, dst *Chain) (srch, dsth int64,
 	return
 }
 
+// QueryClientStates queries the client state of multiple chains at once
+func QueryClientStates(ctx context.Context,
+	srch, dsth int64,
+	src, dst *Chain,
+) (ibcexported.ClientState, ibcexported.ClientState, error) {
+	var (
+		srcClientState ibcexported.ClientState
+		dstClientState ibcexported.ClientState
+	)
+	eg, egCtx := errgroup.WithContext(ctx)
+	eg.Go(func() error {
+		return retry.Do(func() error {
+			var err error
+			srcClientState, err = src.ChainProvider.QueryClientState(egCtx, srch, src.ClientID())
+			if err != nil {
+				return err
+			}
+			return nil
+		}, retry.Context(egCtx), RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
+			src.log.Info(
+				"Failed to query client state when updating clients",
+				zap.String("client_id", src.ClientID()),
+				zap.Uint("attempt", n+1),
+				zap.Uint("max_attempts", RtyAttNum),
+				zap.Error(err),
+			)
+		}))
+	})
+
+	eg.Go(func() error {
+		return retry.Do(func() error {
+			var err error
+			dstClientState, err = dst.ChainProvider.QueryClientState(egCtx, dsth, dst.ClientID())
+			if err != nil {
+				return err
+			}
+			return nil
+		}, retry.Context(egCtx), RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
+			dst.log.Info(
+				"Failed to query client state when updating clients",
+				zap.String("client_id", dst.ClientID()),
+				zap.Uint("attempt", n+1),
+				zap.Uint("max_attempts", RtyAttNum),
+				zap.Error(err),
+			)
+		}))
+	})
+	if err := eg.Wait(); err != nil {
+		return nil, nil, err
+	}
+	return srcClientState, dstClientState, nil
+}
+
 func QueryChannel(ctx context.Context, src *Chain, channelID string) (*chantypes.IdentifiedChannel, error) {
 	var (
 		srch        int64
@@ -128,40 +180,52 @@ func QueryPortChannel(ctx context.Context, src *Chain, portID string) (*chantype
 		portID, src.ChainID(), src.ClientID(), src.ConnectionID(), sb.String())
 }
 
-// GetIBCUpdateHeaders returns a pair of IBC update headers which can be used to update an on chain light client
-func GetIBCUpdateHeaders(ctx context.Context, srch, dsth int64, src, dst provider.ChainProvider, srcClientID, dstClientID string) (srcHeader, dstHeader ibcexported.Header, err error) {
+// QueryIBCUpdateHeaders returns a pair of IBC update headers which can be used to update an on chain light client
+func QueryIBCUpdateHeaders(
+	ctx context.Context,
+	srcClientID, dstClientID string,
+	src, dst provider.ChainProvider,
+	srch, dsth int64,
+	srcTrustedH, dstTrustedH int64,
+) (srcHeader, dstHeader, srcTrustedHeader, dstTrustedHeader provider.IBCHeader, err error) {
 	eg, egCtx := errgroup.WithContext(ctx)
 	eg.Go(func() error {
 		var err error
-		srcHeader, err = src.GetIBCUpdateHeader(egCtx, srch, dst, dstClientID)
+		srcHeader, err = src.QueryIBCHeader(egCtx, srch)
 		return err
 	})
 	eg.Go(func() error {
 		var err error
-		dstHeader, err = dst.GetIBCUpdateHeader(egCtx, dsth, src, srcClientID)
+		dstHeader, err = dst.QueryIBCHeader(egCtx, dsth)
 		return err
 	})
-	if err = eg.Wait(); err != nil {
-		return nil, nil, err
-	}
+	eg.Go(func() error {
+		var err error
+		srcTrustedHeader, err = src.QueryIBCHeader(egCtx, srcTrustedH+1)
+		return err
+	})
+	eg.Go(func() error {
+		var err error
+		dstTrustedHeader, err = dst.QueryIBCHeader(egCtx, dstTrustedH+1)
+		return err
+	})
+	err = eg.Wait()
 	return
 }
 
-func GetLightSignedHeadersAtHeights(ctx context.Context, src, dst *Chain, srch, dsth int64) (srcUpdateHeader, dstUpdateHeader ibcexported.Header, err error) {
+func QueryIBCHeaders(ctx context.Context, src, dst *Chain, srch, dsth int64) (srcUpdateHeader, dstUpdateHeader provider.IBCHeader, err error) {
 	eg, egCtx := errgroup.WithContext(ctx)
 	eg.Go(func() error {
 		var err error
-		srcUpdateHeader, err = src.ChainProvider.GetLightSignedHeaderAtHeight(egCtx, srch)
+		srcUpdateHeader, err = src.ChainProvider.QueryIBCHeader(egCtx, srch)
 		return err
 	})
 	eg.Go(func() error {
 		var err error
-		dstUpdateHeader, err = dst.ChainProvider.GetLightSignedHeaderAtHeight(egCtx, dsth)
+		dstUpdateHeader, err = dst.ChainProvider.QueryIBCHeader(egCtx, dsth)
 		return err
 	})
-	if err := eg.Wait(); err != nil {
-		return nil, nil, err
-	}
+	err = eg.Wait()
 	return
 }
 
@@ -237,17 +301,3 @@ func QueryBalance(ctx context.Context, chain *Chain, address string, showDenoms
 	}
 	return out, nil
 }
-
-// QueryHeader is a helper function for query header
-func QueryHeader(ctx context.Context, chain *Chain, opts ...string) (ibcexported.Header, error) {
-	if len(opts) > 0 {
-		height, err := strconv.ParseInt(opts[0], 10, 64) //convert to int64
-		if err != nil {
-			return nil, err
-		}
-
-		return chain.ChainProvider.QueryHeaderAtHeight(ctx, height)
-	}
-
-	return chain.ChainProvider.GetLightSignedHeaderAtHeight(ctx, 0)
-}
diff --git a/relayer/relayMsgs.go b/relayer/relayMsgs.go
index fc8181c..52d95f1 100644
--- a/relayer/relayMsgs.go
+++ b/relayer/relayMsgs.go
@@ -10,6 +10,7 @@ import (
 	"go.uber.org/multierr"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
+	"golang.org/x/sync/errgroup"
 )
 
 // RelayMsgs contains the msgs that need to be sent to both a src and dst chain
@@ -37,6 +38,36 @@ func (r *RelayMsgs) Ready() bool {
 	return true
 }
 
+func (r *RelayMsgs) PrependMsgUpdateClient(
+	ctx context.Context,
+	src, dst *Chain,
+	srch, dsth int64,
+) error {
+	eg, egCtx := errgroup.WithContext(ctx)
+	if len(r.Src) > 0 {
+		eg.Go(func() error {
+			srcMsgUpdateClient, err := MsgUpdateClient(egCtx, dst, src, dsth, srch)
+			if err != nil {
+				return err
+			}
+			r.Src = append([]provider.RelayerMessage{srcMsgUpdateClient}, r.Src...)
+			return nil
+		})
+	}
+	if len(r.Dst) > 0 {
+		eg.Go(func() error {
+			dstMsgUpdateClient, err := MsgUpdateClient(egCtx, src, dst, srch, dsth)
+			if err != nil {
+				return err
+			}
+			r.Dst = append([]provider.RelayerMessage{dstMsgUpdateClient}, r.Dst...)
+			return nil
+		})
+	}
+
+	return eg.Wait()
+}
+
 func (r *RelayMsgs) IsMaxTx(msgLen, txSize uint64) bool {
 	return (r.MaxMsgLength != 0 && msgLen > r.MaxMsgLength) ||
 		(r.MaxTxSize != 0 && txSize > r.MaxTxSize)


Test patch:
diff --git a/_test/relayer_chain_test.go b/_test/relayer_chain_test.go
index 9af5891..e7367fc 100644
--- a/_test/relayer_chain_test.go
+++ b/_test/relayer_chain_test.go
@@ -14,6 +14,7 @@ import (
 	ibctestingmock "github.com/cosmos/ibc-go/v4/testing/mock"
 	"github.com/cosmos/relayer/v2/cmd"
 	"github.com/cosmos/relayer/v2/relayer"
+	"github.com/cosmos/relayer/v2/relayer/chains/cosmos"
 	"github.com/stretchr/testify/require"
 	"github.com/tendermint/tendermint/crypto/tmhash"
 	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
@@ -278,7 +279,7 @@ func TestGaiaMisbehaviourMonitoring(t *testing.T) {
 	latestHeight, err := dst.ChainProvider.QueryLatestHeight(ctx)
 	require.NoError(t, err)
 
-	header, err := dst.ChainProvider.QueryHeaderAtHeight(ctx, latestHeight)
+	header, err := dst.ChainProvider.QueryIBCHeader(ctx, latestHeight)
 	require.NoError(t, err)
 
 	clientState, err := src.QueryTMClientState(ctx, latestHeight)
@@ -296,9 +297,9 @@ func TestGaiaMisbehaviourMonitoring(t *testing.T) {
 	pubKey, err := privVal.GetPubKey()
 	require.NoError(t, err)
 
-	tmHeader, ok := header.(*tmclient.Header)
+	tmHeader, ok := header.(cosmos.CosmosIBCHeader)
 	if !ok {
-		t.Fatalf("got data of type %T but wanted tmclient.Header \n", header)
+		t.Fatalf("got data of type %T but wanted cosmos.CosmosIBCHeader", header)
 	}
 	validator := tmtypes.NewValidator(pubKey, tmHeader.ValidatorSet.Proposer.VotingPower)
 	valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator})
@@ -306,7 +307,7 @@ func TestGaiaMisbehaviourMonitoring(t *testing.T) {
 
 	// creating duplicate header
 	newHeader := createTMClientHeader(t, dst.ChainID(), int64(heightPlus1.RevisionHeight), height,
-		tmHeader.GetTime().Add(time.Minute), valSet, valSet, signers, tmHeader)
+		tmHeader.SignedHeader.Time.Add(time.Minute), valSet, valSet, signers, nil)
 
 	// update client with duplicate header
 	updateMsg, err := src.ChainProvider.MsgUpdateClient(src.PathEnd.ClientID, newHeader)
diff --git a/relayer/processor/types_test.go b/relayer/processor/types_test.go
index 12fc8b0..d85af18 100644
--- a/relayer/processor/types_test.go
+++ b/relayer/processor/types_test.go
@@ -3,15 +3,15 @@ package processor_test
 import (
 	"testing"
 
-	"github.com/stretchr/testify/require"
-
+	ibcexported "github.com/cosmos/ibc-go/v4/modules/core/exported"
 	"github.com/cosmos/relayer/v2/relayer/processor"
+	"github.com/stretchr/testify/require"
 )
 
 type mockIBCHeader struct{}
 
-func (h mockIBCHeader) IBCHeaderIndicator() {}
-func (h mockIBCHeader) Height() uint64      { return 0 }
+func (h mockIBCHeader) Height() uint64                             { return 0 }
+func (h mockIBCHeader) ConsensusState() ibcexported.ConsensusState { return nil }
 
 func TestIBCHeaderCachePrune(t *testing.T) {
 	cache := make(processor.IBCHeaderCache)