Skip to content

Commit

Permalink
feat(core/eureka): add packet handler (#7048)
Browse files Browse the repository at this point in the history
* send packet eureka

* test progress

* add tests

* lint

* nit

* lint moar

* refactor tests
  • Loading branch information
AdityaSripal authored Aug 6, 2024
1 parent dcff30b commit 567e730
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 12 deletions.
2 changes: 1 addition & 1 deletion modules/core/04-channel/keeper/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func emitChannelCloseConfirmEvent(ctx sdk.Context, portID string, channelID stri

// emitSendPacketEvent emits an event with packet data along with other packet information for relayer
// to pick up and relay to other chain
func emitSendPacketEvent(ctx sdk.Context, packet types.Packet, channel types.Channel, timeoutHeight exported.Height) {
func EmitSendPacketEvent(ctx sdk.Context, packet types.Packet, channel types.Channel, timeoutHeight exported.Height) {
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeSendPacket,
Expand Down
2 changes: 1 addition & 1 deletion modules/core/04-channel/keeper/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (k *Keeper) SendPacket(
k.SetNextSequenceSend(ctx, sourcePort, sourceChannel, sequence+1)
k.SetPacketCommitment(ctx, sourcePort, sourceChannel, packet.GetSequence(), commitment)

emitSendPacketEvent(ctx, packet, channel, timeoutHeight)
EmitSendPacketEvent(ctx, packet, channel, timeoutHeight)

k.Logger(ctx).Info(
"packet sent",
Expand Down
80 changes: 80 additions & 0 deletions modules/core/packet-server/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package keeper

import (
errorsmod "cosmossdk.io/errors"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"

capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types"
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
channelkeeper "github.com/cosmos/ibc-go/v9/modules/core/04-channel/keeper"
channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
"github.com/cosmos/ibc-go/v9/modules/core/exported"
"github.com/cosmos/ibc-go/v9/modules/core/packet-server/types"
)

Expand All @@ -19,3 +27,75 @@ func NewKeeper(cdc codec.BinaryCodec, channelKeeper types.ChannelKeeper, clientK
ClientKeeper: clientKeeper,
}
}

func (k Keeper) SendPacket(
ctx sdk.Context,
_ *capabilitytypes.Capability,
sourceChannel string,
sourcePort string,
destPort string,
timeoutHeight clienttypes.Height,
timeoutTimestamp uint64,
version string,
data []byte,
) (uint64, error) {
// Lookup counterparty associated with our source channel to retrieve the destination channel
counterparty, ok := k.ClientKeeper.GetCounterparty(ctx, sourceChannel)
if !ok {
return 0, channeltypes.ErrChannelNotFound
}
destChannel := counterparty.ClientId

// retrieve the sequence send for this channel
// if no packets have been sent yet, initialize the sequence to 1.
sequence, found := k.ChannelKeeper.GetNextSequenceSend(ctx, sourcePort, sourceChannel)
if !found {
sequence = 1
}

// construct packet from given fields and channel state
packet := channeltypes.NewPacketWithVersion(data, sequence, sourcePort, sourceChannel,
destPort, destChannel, timeoutHeight, timeoutTimestamp, version)

if err := packet.ValidateBasic(); err != nil {
return 0, errorsmod.Wrap(err, "constructed packet failed basic validation")
}

// check that the client of receiver chain is still active
if status := k.ClientKeeper.GetClientStatus(ctx, sourceChannel); status != exported.Active {
return 0, errorsmod.Wrapf(clienttypes.ErrClientNotActive, "client state is not active: %s", status)
}

// retrieve latest height and timestamp of the client of receiver chain
latestHeight := k.ClientKeeper.GetClientLatestHeight(ctx, sourceChannel)
if latestHeight.IsZero() {
return 0, errorsmod.Wrapf(clienttypes.ErrInvalidHeight, "cannot send packet using client (%s) with zero height", sourceChannel)
}

latestTimestamp, err := k.ClientKeeper.GetClientTimestampAtHeight(ctx, sourceChannel, latestHeight)
if err != nil {
return 0, err
}

// check if packet is timed out on the receiving chain
timeout := channeltypes.NewTimeout(packet.GetTimeoutHeight().(clienttypes.Height), packet.GetTimeoutTimestamp())
if timeout.Elapsed(latestHeight, latestTimestamp) {
return 0, errorsmod.Wrap(timeout.ErrTimeoutElapsed(latestHeight, latestTimestamp), "invalid packet timeout")
}

commitment := channeltypes.CommitPacket(packet)

// bump the sequence and set the packet commitment so it is provable by the counterparty
k.ChannelKeeper.SetNextSequenceSend(ctx, sourcePort, sourceChannel, sequence+1)
k.ChannelKeeper.SetPacketCommitment(ctx, sourcePort, sourceChannel, packet.GetSequence(), commitment)

// create sentinel channel for events
channel := channeltypes.Channel{
Ordering: channeltypes.ORDERED,
ConnectionHops: []string{sourceChannel},
}
channelkeeper.EmitSendPacketEvent(ctx, packet, channel, timeoutHeight)

// return the sequence
return sequence, nil
}
81 changes: 71 additions & 10 deletions modules/core/packet-server/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package keeper_test

import (
"fmt"
"testing"

testifysuite "github.com/stretchr/testify/suite"

clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
tmtypes "github.com/cosmos/ibc-go/v9/modules/light-clients/07-tendermint"
ibctesting "github.com/cosmos/ibc-go/v9/testing"
"github.com/cosmos/ibc-go/v9/testing/mock"
)

// KeeperTestSuite is a testing suite to test keeper functions.
Expand Down Expand Up @@ -35,16 +40,72 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.coordinator.CommitNBlocks(suite.chainB, 2)
}

// TODO: Remove, just testing the testing setup.
func (suite *KeeperTestSuite) TestCreateEurekaClients() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()
func (suite *KeeperTestSuite) TestSendPacket() {
var (
path *ibctesting.Path
packet channeltypes.Packet
)

// Assert counterparty set and creator deleted
_, found := suite.chainA.App.GetPacketServer().ClientKeeper.GetCounterparty(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().True(found)
testCases := []struct {
name string
malleate func()
expError error
}{
{"success", func() {}, nil},
{"counterparty not found", func() {
packet.SourceChannel = ibctesting.FirstChannelID
}, channeltypes.ErrChannelNotFound},
{"packet failed basic validation", func() {
// invalid data
packet.Data = nil
}, channeltypes.ErrInvalidPacket},
{"client status invalid", func() {
// make underlying client Frozen to get invalid client status
clientState, ok := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().True(ok, "could not retrieve client state")
tmClientState, ok := clientState.(*tmtypes.ClientState)
suite.Require().True(ok, "client is not tendermint client")
tmClientState.FrozenHeight = clienttypes.NewHeight(0, 1)
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmClientState)
}, clienttypes.ErrClientNotActive},
{"timeout elapsed", func() {
packet.TimeoutTimestamp = 1
}, channeltypes.ErrTimeoutElapsed},
}

// Assert counterparty set and creator deleted
_, found = suite.chainB.App.GetPacketServer().ClientKeeper.GetCounterparty(suite.chainB.GetContext(), path.EndpointB.ClientID)
suite.Require().True(found)
for i, tc := range testCases {
tc := tc
suite.Run(fmt.Sprintf("Case %s, %d/%d tests", tc.name, i, len(testCases)), func() {
suite.SetupTest() // reset

// create clients and set counterparties on both chains
path = ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

// create standard packet that can be malleated
packet = channeltypes.NewPacketWithVersion(mock.MockPacketData, 1, mock.PortID,
path.EndpointA.ClientID, mock.PortID, path.EndpointB.ClientID, clienttypes.NewHeight(1, 100), 0, mock.Version)

// malleate the test case
tc.malleate()

// send packet
seq, err := suite.chainA.App.GetPacketServer().SendPacket(suite.chainA.GetContext(), nil, packet.SourceChannel, packet.SourcePort,
packet.DestinationPort, packet.TimeoutHeight, packet.TimeoutTimestamp, packet.AppVersion, packet.Data)

expPass := tc.expError == nil
if expPass {
suite.Require().NoError(err)
suite.Require().Equal(uint64(1), seq)
expCommitment := channeltypes.CommitPacket(packet)
suite.Require().Equal(expCommitment, suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(suite.chainA.GetContext(), packet.SourcePort, packet.SourceChannel, seq))
} else {
suite.Require().Error(err)
suite.Require().ErrorIs(err, tc.expError)
suite.Require().Equal(uint64(0), seq)
suite.Require().Nil(suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(suite.chainA.GetContext(), packet.SourcePort, packet.SourceChannel, seq))

}
})
}
}
7 changes: 7 additions & 0 deletions modules/core/packet-server/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,11 @@ type ClientKeeper interface {
// the executing chain
// This is a private path that is only used by the IBC lite module
GetCounterparty(ctx sdk.Context, clientID string) (clienttypes.Counterparty, bool)
// GetClientStatus returns the status of a client given the client ID
GetClientStatus(ctx sdk.Context, clientID string) exported.Status
// GetClientLatestHeight returns the latest height of a client given the client ID
GetClientLatestHeight(ctx sdk.Context, clientID string) clienttypes.Height
// GetClientTimestampAtHeight returns the timestamp for a given height on the client
// given its client ID and height
GetClientTimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error)
}

0 comments on commit 567e730

Please sign in to comment.