Skip to content

Commit

Permalink
Handle closing of channels (#863)
Browse files Browse the repository at this point in the history
* WIP: handle closing ordered channels

* treat ordered timeouts as different packet flows ending with channel close confirm

* cleanup

* remove dead code and cleanup `orderFromString`

* purge relevant packet flow messages in the case of a channel closing for ordered channels

* remove incorrect code

* properly purge the relevant send packet and timeout messages

* update comments + delete the relevant messages for the src and counterparty

* fix deleting messages on wrong channel end

* improve readability

Co-authored-by: Andrew Gouin <[email protected]>
  • Loading branch information
jtieri and agouin authored Jul 27, 2022
1 parent 234c886 commit e1c8740
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 48 deletions.
2 changes: 1 addition & 1 deletion relayer/chains/cosmos/message_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (ccp *CosmosChainProcessor) handleChannelMessage(eventType string, ci provi
ccp.channelStateCache[channelKey] = false
case chantypes.EventTypeChannelOpenAck, chantypes.EventTypeChannelOpenConfirm:
ccp.channelStateCache[channelKey] = true
case chantypes.EventTypeChannelCloseInit, chantypes.EventTypeChannelCloseConfirm:
case chantypes.EventTypeChannelCloseConfirm:
for k := range ccp.channelStateCache {
if k.PortID == ci.PortID && k.ChannelID == ci.ChannelID {
ccp.channelStateCache[k] = false
Expand Down
35 changes: 34 additions & 1 deletion relayer/processor/path_end_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,9 @@ func (pathEnd *pathEndRuntime) shouldSendChannelMessage(message channelIBCMessag
// remove all retention of this connection handshake in pathEnd.messagesCache.ConnectionHandshake and counterparty
toDelete := make(map[string][]ChannelKey)
toDeleteCounterparty := make(map[string][]ChannelKey)
toDeletePacket := make(map[string][]uint64)
toDeleteCounterpartyPacket := make(map[string][]uint64)

counterpartyKey := channelKey.Counterparty()
switch eventType {
case chantypes.EventTypeChannelOpenTry:
Expand All @@ -493,9 +496,39 @@ func (pathEnd *pathEndRuntime) shouldSendChannelMessage(message channelIBCMessag
toDeleteCounterparty[chantypes.EventTypeChannelOpenAck] = []ChannelKey{counterpartyKey}
toDelete[chantypes.EventTypeChannelOpenTry] = []ChannelKey{channelKey}
toDeleteCounterparty[chantypes.EventTypeChannelOpenInit] = []ChannelKey{counterpartyKey.msgInitKey()}
case chantypes.EventTypeChannelCloseConfirm:
toDeleteCounterparty[chantypes.EventTypeChannelCloseInit] = []ChannelKey{counterpartyKey}
toDelete[chantypes.EventTypeChannelCloseConfirm] = []ChannelKey{channelKey}

// Gather relevant send packet messages, for this channel key, that should be deleted if we
// are operating on an ordered channel.
if messageCache, ok := pathEnd.messageCache.PacketFlow[channelKey]; ok {
if seqCache, ok := messageCache[chantypes.EventTypeSendPacket]; ok {
for seq, packetInfo := range seqCache {
if packetInfo.ChannelOrder == chantypes.ORDERED.String() {
toDeletePacket[chantypes.EventTypeSendPacket] = append(toDeletePacket[chantypes.EventTypeSendPacket], seq)
}
}
}
}

// Gather relevant timeout messages, for this counterparty channel key, that should be deleted if we
// are operating on an ordered channel.
if messageCache, ok := counterparty.messageCache.PacketFlow[counterpartyKey]; ok {
if seqCache, ok := messageCache[chantypes.EventTypeTimeoutPacket]; ok {
for seq, packetInfo := range seqCache {
if packetInfo.ChannelOrder == chantypes.ORDERED.String() {
toDeleteCounterpartyPacket[chantypes.EventTypeTimeoutPacket] = append(toDeleteCounterpartyPacket[chantypes.EventTypeTimeoutPacket], seq)
}
}
}
}
}

// delete in progress send for this specific message
pathEnd.channelProcessing.deleteMessages(map[string][]ChannelKey{eventType: []ChannelKey{channelKey}})
pathEnd.channelProcessing.deleteMessages(map[string][]ChannelKey{eventType: {channelKey}})
pathEnd.messageCache.PacketFlow[channelKey].DeleteMessages(toDeletePacket)
counterparty.messageCache.PacketFlow[counterpartyKey].DeleteMessages(toDeleteCounterpartyPacket)

// delete all connection handshake retention history for this channel
pathEnd.messageCache.ChannelHandshake.DeleteMessages(toDelete)
Expand Down
136 changes: 107 additions & 29 deletions relayer/processor/path_processor_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ func (pp *PathProcessor) assemblePacketIBCMessage(

func (pp *PathProcessor) getUnrelayedPacketsAndAcksAndToDelete(ctx context.Context, pathEndPacketFlowMessages pathEndPacketFlowMessages) pathEndPacketFlowResponse {
res := pathEndPacketFlowResponse{
ToDeleteSrc: make(map[string][]uint64),
ToDeleteDst: make(map[string][]uint64),
ToDeleteSrc: make(map[string][]uint64),
ToDeleteDst: make(map[string][]uint64),
ToDeleteDstChannel: make(map[string][]ChannelKey),
}

MsgTransferLoop:
Expand All @@ -51,12 +52,42 @@ MsgTransferLoop:
continue MsgTransferLoop
}
}
for timeoutSeq := range pathEndPacketFlowMessages.SrcMsgTimeout {

for timeoutSeq, msgTimeout := range pathEndPacketFlowMessages.SrcMsgTimeout {
if transferSeq == timeoutSeq {
// we have a timeout for this packet, so packet flow is complete
// remove all retention of this sequence number
res.ToDeleteSrc[chantypes.EventTypeSendPacket] = append(res.ToDeleteSrc[chantypes.EventTypeSendPacket], transferSeq)
res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket] = append(res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket], transferSeq)
if msgTimeout.ChannelOrder == chantypes.ORDERED.String() {
// For ordered channel packets, flow is not done until channel-close-confirm is observed.
if pathEndPacketFlowMessages.DstMsgChannelCloseConfirm == nil {
// have not observed a channel-close-confirm yet for this channel, send it if ready.
// will come back through here next block if not yet ready.
closeChan := channelIBCMessage{
eventType: chantypes.EventTypeChannelCloseConfirm,
info: provider.ChannelInfo{
Height: msgTimeout.Height,
PortID: msgTimeout.SourcePort,
ChannelID: msgTimeout.SourceChannel,
CounterpartyPortID: msgTimeout.DestPort,
CounterpartyChannelID: msgTimeout.DestChannel,
Order: orderFromString(msgTimeout.ChannelOrder),
},
}

if pathEndPacketFlowMessages.Src.shouldSendChannelMessage(closeChan, pathEndPacketFlowMessages.Dst) {
res.DstChannelMessage = append(res.DstChannelMessage, closeChan)
}
} else {
// ordered channel, and we have a channel close confirm, so packet-flow and channel-close-flow is complete.
// remove all retention of this sequence number and this channel-close-confirm.
res.ToDeleteDstChannel[chantypes.EventTypeChannelCloseConfirm] = append(res.ToDeleteDstChannel[chantypes.EventTypeChannelCloseConfirm], pathEndPacketFlowMessages.ChannelKey.Counterparty())
res.ToDeleteSrc[chantypes.EventTypeSendPacket] = append(res.ToDeleteSrc[chantypes.EventTypeSendPacket], transferSeq)
res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket] = append(res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket], transferSeq)
}
} else {
// unordered channel, and we have a timeout for this packet, so packet flow is complete
// remove all retention of this sequence number
res.ToDeleteSrc[chantypes.EventTypeSendPacket] = append(res.ToDeleteSrc[chantypes.EventTypeSendPacket], transferSeq)
res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket] = append(res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket], transferSeq)
}
continue MsgTransferLoop
}
}
Expand Down Expand Up @@ -128,9 +159,18 @@ MsgTransferLoop:
res.ToDeleteDst[chantypes.EventTypeRecvPacket] = append(res.ToDeleteDst[chantypes.EventTypeRecvPacket], ackSeq)
res.ToDeleteSrc[chantypes.EventTypeAcknowledgePacket] = append(res.ToDeleteSrc[chantypes.EventTypeAcknowledgePacket], ackSeq)
}
for timeoutSeq := range pathEndPacketFlowMessages.SrcMsgTimeout {
res.ToDeleteSrc[chantypes.EventTypeSendPacket] = append(res.ToDeleteSrc[chantypes.EventTypeSendPacket], timeoutSeq)
res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket] = append(res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket], timeoutSeq)
for timeoutSeq, msgTimeout := range pathEndPacketFlowMessages.SrcMsgTimeout {
if msgTimeout.ChannelOrder == chantypes.ORDERED.String() {
if pathEndPacketFlowMessages.DstMsgChannelCloseConfirm != nil {
// For ordered channel packets, flow is not done until channel-close-confirm is observed.
res.ToDeleteDstChannel[chantypes.EventTypeChannelCloseConfirm] = append(res.ToDeleteDstChannel[chantypes.EventTypeChannelCloseConfirm], pathEndPacketFlowMessages.ChannelKey.Counterparty())
res.ToDeleteSrc[chantypes.EventTypeSendPacket] = append(res.ToDeleteSrc[chantypes.EventTypeSendPacket], timeoutSeq)
res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket] = append(res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket], timeoutSeq)
}
} else {
res.ToDeleteSrc[chantypes.EventTypeSendPacket] = append(res.ToDeleteSrc[chantypes.EventTypeSendPacket], timeoutSeq)
res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket] = append(res.ToDeleteSrc[chantypes.EventTypeTimeoutPacket], timeoutSeq)
}
}
for timeoutOnCloseSeq := range pathEndPacketFlowMessages.SrcMsgTimeoutOnClose {
res.ToDeleteSrc[chantypes.EventTypeSendPacket] = append(res.ToDeleteSrc[chantypes.EventTypeSendPacket], timeoutOnCloseSeq)
Expand Down Expand Up @@ -512,25 +552,41 @@ func (pp *PathProcessor) processLatestMessages(ctx context.Context, messageLifec
pathEnd2ProcessRes := make([]pathEndPacketFlowResponse, len(channelPairs))

for i, pair := range channelPairs {
var pathEnd1ChannelCloseConfirm, pathEnd2ChannelCloseConfirm *provider.ChannelInfo

if pathEnd1ChanCloseConfirmMsgs, ok := pp.pathEnd1.messageCache.ChannelHandshake[chantypes.EventTypeChannelCloseConfirm]; ok {
if pathEnd1ChannelCloseConfirmMsg, ok := pathEnd1ChanCloseConfirmMsgs[pair.pathEnd1ChannelKey]; ok {
pathEnd1ChannelCloseConfirm = &pathEnd1ChannelCloseConfirmMsg
}
}

if pathEnd2ChanCloseConfirmMsgs, ok := pp.pathEnd2.messageCache.ChannelHandshake[chantypes.EventTypeChannelCloseConfirm]; ok {
if pathEnd2ChannelCloseConfirmMsg, ok := pathEnd2ChanCloseConfirmMsgs[pair.pathEnd2ChannelKey]; ok {
pathEnd2ChannelCloseConfirm = &pathEnd2ChannelCloseConfirmMsg
}
}

pathEnd1PacketFlowMessages := pathEndPacketFlowMessages{
Src: pp.pathEnd1,
Dst: pp.pathEnd2,
ChannelKey: pair.pathEnd1ChannelKey,
SrcMsgTransfer: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeSendPacket],
DstMsgRecvPacket: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeRecvPacket],
SrcMsgAcknowledgement: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeAcknowledgePacket],
SrcMsgTimeout: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeTimeoutPacket],
SrcMsgTimeoutOnClose: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeTimeoutPacketOnClose],
Src: pp.pathEnd1,
Dst: pp.pathEnd2,
ChannelKey: pair.pathEnd1ChannelKey,
SrcMsgTransfer: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeSendPacket],
DstMsgRecvPacket: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeRecvPacket],
SrcMsgAcknowledgement: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeAcknowledgePacket],
SrcMsgTimeout: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeTimeoutPacket],
SrcMsgTimeoutOnClose: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeTimeoutPacketOnClose],
DstMsgChannelCloseConfirm: pathEnd2ChannelCloseConfirm,
}
pathEnd2PacketFlowMessages := pathEndPacketFlowMessages{
Src: pp.pathEnd2,
Dst: pp.pathEnd1,
ChannelKey: pair.pathEnd2ChannelKey,
SrcMsgTransfer: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeSendPacket],
DstMsgRecvPacket: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeRecvPacket],
SrcMsgAcknowledgement: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeAcknowledgePacket],
SrcMsgTimeout: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeTimeoutPacket],
SrcMsgTimeoutOnClose: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeTimeoutPacketOnClose],
Src: pp.pathEnd2,
Dst: pp.pathEnd1,
ChannelKey: pair.pathEnd2ChannelKey,
SrcMsgTransfer: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeSendPacket],
DstMsgRecvPacket: pp.pathEnd1.messageCache.PacketFlow[pair.pathEnd1ChannelKey][chantypes.EventTypeRecvPacket],
SrcMsgAcknowledgement: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeAcknowledgePacket],
SrcMsgTimeout: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeTimeoutPacket],
SrcMsgTimeoutOnClose: pp.pathEnd2.messageCache.PacketFlow[pair.pathEnd2ChannelKey][chantypes.EventTypeTimeoutPacketOnClose],
DstMsgChannelCloseConfirm: pathEnd1ChannelCloseConfirm,
}

pathEnd1ProcessRes[i] = pp.getUnrelayedPacketsAndAcksAndToDelete(ctx, pathEnd1PacketFlowMessages)
Expand All @@ -540,7 +596,10 @@ func (pp *PathProcessor) processLatestMessages(ctx context.Context, messageLifec
// concatenate applicable messages for pathend
pathEnd1ConnectionMessages, pathEnd2ConnectionMessages := pp.connectionMessagesToSend(pathEnd1ConnectionHandshakeRes, pathEnd2ConnectionHandshakeRes)
pathEnd1ChannelMessages, pathEnd2ChannelMessages := pp.channelMessagesToSend(pathEnd1ChannelHandshakeRes, pathEnd2ChannelHandshakeRes)
pathEnd1PacketMessages, pathEnd2PacketMessages := pp.packetMessagesToSend(channelPairs, pathEnd1ProcessRes, pathEnd2ProcessRes)

pathEnd1PacketMessages, pathEnd2PacketMessages, pathEnd1ChanCloseMessages, pathEnd2ChanCloseMessages := pp.packetMessagesToSend(channelPairs, pathEnd1ProcessRes, pathEnd2ProcessRes)
pathEnd1ChannelMessages = append(pathEnd1ChannelMessages, pathEnd1ChanCloseMessages...)
pathEnd2ChannelMessages = append(pathEnd2ChannelMessages, pathEnd2ChanCloseMessages...)

pathEnd1Messages := pathEndMessages{
connectionMessages: pathEnd1ConnectionMessages,
Expand Down Expand Up @@ -626,6 +685,7 @@ func (pp *PathProcessor) assembleMessage(
return
}
om.Append(message)

}

func (pp *PathProcessor) assembleAndSendMessages(
Expand Down Expand Up @@ -869,29 +929,47 @@ func (pp *PathProcessor) connectionMessagesToSend(pathEnd1ConnectionHandshakeRes
return pathEnd1ConnectionMessages, pathEnd2ConnectionMessages
}

func (pp *PathProcessor) packetMessagesToSend(channelPairs []channelPair, pathEnd1ProcessRes []pathEndPacketFlowResponse, pathEnd2ProcessRes []pathEndPacketFlowResponse) ([]packetIBCMessage, []packetIBCMessage) {
func (pp *PathProcessor) packetMessagesToSend(
channelPairs []channelPair,
pathEnd1ProcessRes []pathEndPacketFlowResponse,
pathEnd2ProcessRes []pathEndPacketFlowResponse,
) ([]packetIBCMessage, []packetIBCMessage, []channelIBCMessage, []channelIBCMessage) {
pathEnd1PacketLen := 0
pathEnd2PacketLen := 0
pathEnd1ChannelLen := 0
pathEnd2ChannelLen := 0

for i := 0; i < len(channelPairs); i++ {
pathEnd1PacketLen += len(pathEnd2ProcessRes[i].DstMessages) + len(pathEnd1ProcessRes[i].SrcMessages)
pathEnd2PacketLen += len(pathEnd1ProcessRes[i].DstMessages) + len(pathEnd2ProcessRes[i].SrcMessages)
pathEnd1ChannelLen += len(pathEnd2ProcessRes[i].DstChannelMessage)
pathEnd2ChannelLen += len(pathEnd1ProcessRes[i].DstChannelMessage)
}

pathEnd1PacketMessages := make([]packetIBCMessage, 0, pathEnd1PacketLen)
pathEnd2PacketMessages := make([]packetIBCMessage, 0, pathEnd2PacketLen)

pathEnd1ChannelMessage := make([]channelIBCMessage, 0, pathEnd1ChannelLen)
pathEnd2ChannelMessage := make([]channelIBCMessage, 0, pathEnd2ChannelLen)

for i, channelPair := range channelPairs {
pathEnd1PacketMessages = append(pathEnd1PacketMessages, pathEnd2ProcessRes[i].DstMessages...)
pathEnd1PacketMessages = append(pathEnd1PacketMessages, pathEnd1ProcessRes[i].SrcMessages...)

pathEnd2PacketMessages = append(pathEnd2PacketMessages, pathEnd1ProcessRes[i].DstMessages...)
pathEnd2PacketMessages = append(pathEnd2PacketMessages, pathEnd2ProcessRes[i].SrcMessages...)

pathEnd1ChannelMessage = append(pathEnd1ChannelMessage, pathEnd2ProcessRes[i].DstChannelMessage...)
pathEnd2ChannelMessage = append(pathEnd2ChannelMessage, pathEnd1ProcessRes[i].DstChannelMessage...)

pp.pathEnd1.messageCache.ChannelHandshake.DeleteMessages(pathEnd2ProcessRes[i].ToDeleteDstChannel)
pp.pathEnd2.messageCache.ChannelHandshake.DeleteMessages(pathEnd1ProcessRes[i].ToDeleteDstChannel)

pp.pathEnd1.messageCache.PacketFlow[channelPair.pathEnd1ChannelKey].DeleteMessages(pathEnd1ProcessRes[i].ToDeleteSrc, pathEnd2ProcessRes[i].ToDeleteDst)
pp.pathEnd2.messageCache.PacketFlow[channelPair.pathEnd2ChannelKey].DeleteMessages(pathEnd2ProcessRes[i].ToDeleteSrc, pathEnd1ProcessRes[i].ToDeleteDst)
pp.pathEnd1.packetProcessing[channelPair.pathEnd1ChannelKey].deleteMessages(pathEnd1ProcessRes[i].ToDeleteSrc, pathEnd2ProcessRes[i].ToDeleteDst)
pp.pathEnd2.packetProcessing[channelPair.pathEnd2ChannelKey].deleteMessages(pathEnd2ProcessRes[i].ToDeleteSrc, pathEnd1ProcessRes[i].ToDeleteDst)
}

return pathEnd1PacketMessages, pathEnd2PacketMessages
return pathEnd1PacketMessages, pathEnd2PacketMessages, pathEnd1ChannelMessage, pathEnd2ChannelMessage
}
45 changes: 28 additions & 17 deletions relayer/processor/types_internal.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package processor

import (
"strings"
"sync"

chantypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"
"github.com/cosmos/relayer/v2/relayer/provider"
)

Expand Down Expand Up @@ -101,14 +103,15 @@ func (c connectionProcessingCache) deleteMessages(toDelete ...map[string][]Conne
// contains MsgRecvPacket from counterparty
// entire packet flow
type pathEndPacketFlowMessages struct {
Src *pathEndRuntime
Dst *pathEndRuntime
ChannelKey ChannelKey
SrcMsgTransfer PacketSequenceCache
DstMsgRecvPacket PacketSequenceCache
SrcMsgAcknowledgement PacketSequenceCache
SrcMsgTimeout PacketSequenceCache
SrcMsgTimeoutOnClose PacketSequenceCache
Src *pathEndRuntime
Dst *pathEndRuntime
ChannelKey ChannelKey
SrcMsgTransfer PacketSequenceCache
DstMsgRecvPacket PacketSequenceCache
SrcMsgAcknowledgement PacketSequenceCache
SrcMsgTimeout PacketSequenceCache
SrcMsgTimeoutOnClose PacketSequenceCache
DstMsgChannelCloseConfirm *provider.ChannelInfo
}

type pathEndConnectionHandshakeMessages struct {
Expand All @@ -129,19 +132,15 @@ type pathEndChannelHandshakeMessages struct {
DstMsgChannelOpenConfirm ChannelMessageCache
}

type pathEndChannelCloseMessages struct {
Src *pathEndRuntime
Dst *pathEndRuntime
SrcMsgChannelCloseInit ChannelMessageCache
DstMsgChannelCloseConfirm ChannelMessageCache
}

type pathEndPacketFlowResponse struct {
SrcMessages []packetIBCMessage
DstMessages []packetIBCMessage

ToDeleteSrc map[string][]uint64
ToDeleteDst map[string][]uint64
DstChannelMessage []channelIBCMessage

ToDeleteSrc map[string][]uint64
ToDeleteDst map[string][]uint64
ToDeleteDstChannel map[string][]ChannelKey
}

type pathEndChannelHandshakeResponse struct {
Expand Down Expand Up @@ -220,3 +219,15 @@ type channelMessageToTrack struct {
msg channelIBCMessage
assembled bool
}

// orderFromString parses a string into a channel order byte.
func orderFromString(order string) chantypes.Order {
switch strings.ToUpper(order) {
case chantypes.UNORDERED.String():
return chantypes.UNORDERED
case chantypes.ORDERED.String():
return chantypes.ORDERED
default:
return chantypes.NONE
}
}

0 comments on commit e1c8740

Please sign in to comment.