diff --git a/modules/core/04-channel/types/channel.pb.go b/modules/core/04-channel/types/channel.pb.go index 2263ee69c4e..ab5c5ed3121 100644 --- a/modules/core/04-channel/types/channel.pb.go +++ b/modules/core/04-channel/types/channel.pb.go @@ -280,6 +280,25 @@ type Packet struct { TimeoutTimestamp uint64 `protobuf:"varint,8,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` } +type EurekaPacket struct { + // number corresponds to the order of sends and receives, where a Packet + // with an earlier sequence number must be sent and received before a Packet + // with a later sequence number. + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // identifies the port on the sending chain. + SourcePort string `protobuf:"bytes,2,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // identifies the channel end on the sending chain. + SourceChannel string `protobuf:"bytes,3,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty"` + // identifies the port on the receiving chain. + DestinationPort string `protobuf:"bytes,4,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` + // identifies the channel end on the receiving chain. + DestinationChannel string `protobuf:"bytes,5,opt,name=destination_channel,json=destinationChannel,proto3" json:"destination_channel,omitempty"` + // block height after which the packet times out + TimeoutHeight types.Height `protobuf:"bytes,7,opt,name=timeout_height,json=timeoutHeight,proto3" json:"timeout_height"` + // block timestamp (in nanoseconds) after which the packet times out + TimeoutTimestamp uint64 `protobuf:"varint,8,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` +} + func (m *Packet) Reset() { *m = Packet{} } func (m *Packet) String() string { return proto.CompactTextString(m) } func (*Packet) ProtoMessage() {} diff --git a/modules/core/04-channel/types/packet.go b/modules/core/04-channel/types/packet.go index b585f093e50..7cc850fec22 100644 --- a/modules/core/04-channel/types/packet.go +++ b/modules/core/04-channel/types/packet.go @@ -62,6 +62,23 @@ func NewPacket( } } +func NewEurekaPacket( + data []byte, + sequence uint64, sourcePort, sourceChannel, + destinationPort, destinationChannel string, + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, +) EurekaPacket { + return EurekaPacket{ + Sequence: sequence, + SourcePort: sourcePort, + SourceChannel: sourceChannel, + DestinationPort: destinationPort, + DestinationChannel: destinationChannel, + TimeoutHeight: timeoutHeight, + TimeoutTimestamp: timeoutTimestamp, + } +} + // GetSequence implements PacketI interface func (p Packet) GetSequence() uint64 { return p.Sequence } diff --git a/testing/events.go b/testing/events.go index 56426bb34e2..96266a67ebc 100644 --- a/testing/events.go +++ b/testing/events.go @@ -67,6 +67,14 @@ func ParsePacketFromEvents(events []abci.Event) (channeltypes.Packet, error) { return packets[0], nil } +func ParseEurekaPacketFromEvents(events []abci.Event) (channeltypes.EurekaPacket, error) { + packets, err := ParseEurekaPacketsFromEvents(channeltypes.EventTypeSendPacket, events) + if err != nil { + return channeltypes.EurekaPacket{}, err + } + return packets[0], nil +} + // ParseRecvPacketFromEvents parses events emitted from a MsgRecvPacket and returns // the first EventTypeRecvPacket packet found. // Returns an error if no packet is found. @@ -147,6 +155,66 @@ func ParsePacketsFromEvents(eventType string, events []abci.Event) ([]channeltyp return packets, nil } +func ParseEurekaPacketsFromEvents(eventType string, events []abci.Event) ([]channeltypes.EurekaPacket, error) { + ferr := func(err error) ([]channeltypes.EurekaPacket, error) { + return nil, fmt.Errorf("ibctesting.ParseEurekaPacketsFromEvents: %w", err) + } + var packets []channeltypes.EurekaPacket + for _, ev := range events { + if ev.Type == eventType { + var packet channeltypes.EurekaPacket + for _, attr := range ev.Attributes { + switch attr.Key { + case channeltypes.AttributeKeySequence: + seq, err := strconv.ParseUint(attr.Value, 10, 64) + if err != nil { + return ferr(err) + } + + packet.Sequence = seq + + case channeltypes.AttributeKeySrcPort: + packet.SourcePort = attr.Value + + case channeltypes.AttributeKeySrcChannel: + packet.SourceChannel = attr.Value + + case channeltypes.AttributeKeyDstPort: + packet.DestinationPort = attr.Value + + case channeltypes.AttributeKeyDstChannel: + packet.DestinationChannel = attr.Value + + case channeltypes.AttributeKeyTimeoutHeight: + height, err := clienttypes.ParseHeight(attr.Value) + if err != nil { + return ferr(err) + } + + packet.TimeoutHeight = height + + case channeltypes.AttributeKeyTimeoutTimestamp: + timestamp, err := strconv.ParseUint(attr.Value, 10, 64) + if err != nil { + return ferr(err) + } + + packet.TimeoutTimestamp = timestamp + + default: + continue + } + } + + packets = append(packets, packet) + } + } + if len(packets) == 0 { + return ferr(errors.New("acknowledgement event attribute not found")) + } + return packets, nil +} + // ParseAckFromEvents parses events emitted from a MsgRecvPacket and returns the // acknowledgement. func ParseAckFromEvents(events []abci.Event) ([]byte, error) { diff --git a/testing/events_test.go b/testing/events_test.go index b8725a27e65..d8deefafea9 100644 --- a/testing/events_test.go +++ b/testing/events_test.go @@ -219,3 +219,200 @@ func TestParsePacketsFromEvents(t *testing.T) { }) } } + +func TestParseEurekaPacketsFromEvents(t *testing.T) { + testCases := []struct { + name string + events []abci.Event + expectedPackets []channeltypes.EurekaPacket + expectedError string + }{ + { + name: "success", + events: []abci.Event{ + { + Type: "xxx", + }, + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeySequence, + Value: "42", + }, + { + Key: channeltypes.AttributeKeySrcPort, + Value: "srcPort", + }, + { + Key: channeltypes.AttributeKeySrcChannel, + Value: "srcChannel", + }, + { + Key: channeltypes.AttributeKeyDstPort, + Value: "dstPort", + }, + { + Key: channeltypes.AttributeKeyDstChannel, + Value: "dstChannel", + }, + { + Key: channeltypes.AttributeKeyTimeoutHeight, + Value: "1-2", + }, + { + Key: channeltypes.AttributeKeyTimeoutTimestamp, + Value: "1000", + }, + }, + }, + { + Type: "yyy", + }, + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeySequence, + Value: "43", + }, + { + Key: channeltypes.AttributeKeySrcPort, + Value: "srcPort", + }, + { + Key: channeltypes.AttributeKeySrcChannel, + Value: "srcChannel", + }, + { + Key: channeltypes.AttributeKeyDstPort, + Value: "dstPort", + }, + { + Key: channeltypes.AttributeKeyDstChannel, + Value: "dstChannel", + }, + { + Key: channeltypes.AttributeKeyTimeoutHeight, + Value: "1-3", + }, + { + Key: channeltypes.AttributeKeyTimeoutTimestamp, + Value: "1001", + }, + }, + }, + }, + expectedPackets: []channeltypes.EurekaPacket{ + { + Sequence: 42, + SourcePort: "srcPort", + SourceChannel: "srcChannel", + DestinationPort: "dstPort", + DestinationChannel: "dstChannel", + TimeoutHeight: types.Height{ + RevisionNumber: 1, + RevisionHeight: 2, + }, + TimeoutTimestamp: 1000, + }, + { + Sequence: 43, + SourcePort: "srcPort", + SourceChannel: "srcChannel", + DestinationPort: "dstPort", + DestinationChannel: "dstChannel", + TimeoutHeight: types.Height{ + RevisionNumber: 1, + RevisionHeight: 3, + }, + TimeoutTimestamp: 1001, + }, + }, + }, + + { + name: "fail: no events", + events: []abci.Event{}, + expectedError: "acknowledgement event attribute not found", + }, + { + name: "fail: events without packet", + events: []abci.Event{ + { + Type: "xxx", + }, + { + Type: "yyy", + }, + }, + expectedError: "acknowledgement event attribute not found", + }, + { + name: "fail: event packet with invalid AttributeKeySequence", + events: []abci.Event{ + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeySequence, + Value: "x", + }, + }, + }, + }, + expectedError: "strconv.ParseUint: parsing \"x\": invalid syntax", + }, + { + name: "fail: event packet with invalid AttributeKeyTimeoutHeight", + events: []abci.Event{ + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeyTimeoutHeight, + Value: "x", + }, + }, + }, + }, + expectedError: "expected height string format: {revision}-{height}. Got: x: invalid height", + }, + { + name: "fail: event packet with invalid AttributeKeyTimeoutTimestamp", + events: []abci.Event{ + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeyTimeoutTimestamp, + Value: "x", + }, + }, + }, + }, + expectedError: "strconv.ParseUint: parsing \"x\": invalid syntax", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + allPackets, err := ibctesting.ParseEurekaPacketsFromEvents(channeltypes.EventTypeSendPacket, tc.events) + + if tc.expectedError == "" { + require.NoError(t, err) + require.Equal(t, tc.expectedPackets, allPackets) + } else { + require.ErrorContains(t, err, tc.expectedError) + } + + firstPacket, err := ibctesting.ParseEurekaPacketFromEvents(tc.events) + + if tc.expectedError == "" { + require.NoError(t, err) + require.Equal(t, tc.expectedPackets[0], firstPacket) + } else { + require.ErrorContains(t, err, tc.expectedError) + } + }) + } +}