Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: adding implementation for SendPacket message server #7383

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions modules/core/04-channel/types/timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ func NewTimeout(height clienttypes.Height, timestamp uint64) Timeout {
}
}

// NewTimeoutWithTimestamp creates a new Timeout with only the timestamp set.
func NewTimeoutWithTimestamp(timestamp uint64) Timeout {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only care about timestamp in the eureka spec

return Timeout{
Height: clienttypes.ZeroHeight(),
Timestamp: timestamp,
}
}

// IsValid returns true if either the height or timestamp is non-zero.
func (t Timeout) IsValid() bool {
return !t.Height.IsZero() || t.Timestamp != 0
Expand All @@ -25,6 +33,11 @@ func (t Timeout) Elapsed(height clienttypes.Height, timestamp uint64) bool {
return t.heightElapsed(height) || t.timestampElapsed(timestamp)
}

// TimestampElapsed returns true if the provided timestamp is past the timeout timestamp.
func (t Timeout) TimestampElapsed(timestamp uint64) bool {
return t.timestampElapsed(timestamp)
}

// ErrTimeoutElapsed returns a timeout elapsed error indicating which timeout value
// has elapsed.
func (t Timeout) ErrTimeoutElapsed(height clienttypes.Height, timestamp uint64) error {
Expand Down
12 changes: 12 additions & 0 deletions modules/core/04-channel/v2/keeper/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package keeper

import (
"context"

channeltypesv2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
)

// EmitSendPacketEvents emits events for the SendPacket handler.
func EmitSendPacketEvents(ctx context.Context, packet channeltypesv2.Packet) {
// TODO: https://github.com/cosmos/ibc-go/issues/7386
}
43 changes: 40 additions & 3 deletions modules/core/04-channel/v2/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import (
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"

connectionkeeper "github.com/cosmos/ibc-go/v9/modules/core/03-connection/keeper"
channelkeeperv1 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/keeper"
channeltypesv1 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
"github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
commitmentv2types "github.com/cosmos/ibc-go/v9/modules/core/23-commitment/types/v2"
host "github.com/cosmos/ibc-go/v9/modules/core/24-host"
hostv2 "github.com/cosmos/ibc-go/v9/modules/core/24-host/v2"
"github.com/cosmos/ibc-go/v9/modules/core/exported"
Expand All @@ -23,13 +27,20 @@ import (
type Keeper struct {
cdc codec.BinaryCodec
storeService corestore.KVStoreService
ClientKeeper types.ClientKeeper
// channelKeeperV1 is used for channel aliasing only.
channelKeeperV1 *channelkeeperv1.Keeper
connectionKeeper *connectionkeeper.Keeper
}

// NewKeeper creates a new channel v2 keeper
func NewKeeper(cdc codec.BinaryCodec, storeService corestore.KVStoreService) *Keeper {
func NewKeeper(cdc codec.BinaryCodec, storeService corestore.KVStoreService, clientKeeper types.ClientKeeper, channelKeeperV1 *channelkeeperv1.Keeper, connectionKeeper *connectionkeeper.Keeper) *Keeper {
return &Keeper{
cdc: cdc,
storeService: storeService,
cdc: cdc,
storeService: storeService,
channelKeeperV1: channelKeeperV1,
connectionKeeper: connectionKeeper,
ClientKeeper: clientKeeper,
}
}

Expand Down Expand Up @@ -162,3 +173,29 @@ func (k *Keeper) SetNextSequenceSend(ctx context.Context, sourceID string, seque
panic(err)
}
}

// AliasV1Channel returns a version 2 channel for the given port and channel ID
// by converting the channel into a version 2 channel.
func (k *Keeper) AliasV1Channel(ctx context.Context, portID, channelID string) (types.Counterparty, bool) {
channel, ok := k.channelKeeperV1.GetChannel(ctx, portID, channelID)
if !ok {
return types.Counterparty{}, false
}
// Do not allow channel to be converted into a version 2 counterparty
// if the channel is not OPEN or if it is ORDERED
if channel.State != channeltypesv1.OPEN || channel.Ordering == channeltypesv1.ORDERED {
return types.Counterparty{}, false
}
connection, ok := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
if !ok {
return types.Counterparty{}, false
}
merklePathPrefix := commitmentv2types.NewMerklePath(connection.Counterparty.Prefix.KeyPrefix, []byte(""))

counterparty := types.Counterparty{
CounterpartyChannelId: channel.Counterparty.ChannelId,
ClientId: connection.ClientId,
MerklePathPrefix: merklePathPrefix,
}
return counterparty, true
}
109 changes: 109 additions & 0 deletions modules/core/04-channel/v2/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package keeper_test

import (
"testing"

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

"github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
channeltypes2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
commitmentv2types "github.com/cosmos/ibc-go/v9/modules/core/23-commitment/types/v2"
ibctesting "github.com/cosmos/ibc-go/v9/testing"
)

func TestKeeperTestSuite(t *testing.T) {
testifysuite.Run(t, new(KeeperTestSuite))
}

type KeeperTestSuite struct {
testifysuite.Suite

coordinator *ibctesting.Coordinator

// testing chains used for convenience and readability
chainA *ibctesting.TestChain
chainB *ibctesting.TestChain
chainC *ibctesting.TestChain
}

func (suite *KeeperTestSuite) SetupTest() {
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3)
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1))
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2))
suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3))
}

func (suite *KeeperTestSuite) TestAliasV1Channel() {
var path *ibctesting.Path

testCases := []struct {
name string
malleate func()
expPass bool
}{
{
"success",
func() {},
true,
},
{
"failure: channel not found",
func() {
path.EndpointA.ChannelID = ""
},
false,
},
{
"failure: channel not OPEN",
func() {
path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.State = types.TRYOPEN })
},
false,
},
{
"failure: channel is ORDERED",
func() {
path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.Ordering = types.ORDERED })
},
false,
},
{
"failure: connection not found",
func() {
path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.ConnectionHops = []string{ibctesting.InvalidID} })
},
false,
},
}

for _, tc := range testCases {
tc := tc

suite.Run(tc.name, func() {
suite.SetupTest() // reset

// create a previously existing path on chainA to change the identifiers
// between the path between chainA and chainB
path1 := ibctesting.NewPath(suite.chainA, suite.chainC)
path1.Setup()

path = ibctesting.NewPath(suite.chainA, suite.chainB)
path.Setup()

tc.malleate()

counterparty, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2.AliasV1Channel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)

if tc.expPass {
suite.Require().True(found)

merklePath := commitmentv2types.NewMerklePath([]byte("ibc"), []byte(""))
expCounterparty := channeltypes2.NewCounterparty(path.EndpointA.ClientID, path.EndpointB.ChannelID, merklePath)
suite.Require().Equal(counterparty, expCounterparty)
} else {
suite.Require().False(found)
suite.Require().Equal(counterparty, channeltypes2.Counterparty{})
}
})
}
}
56 changes: 56 additions & 0 deletions modules/core/04-channel/v2/keeper/msg_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package keeper

import (
"context"
errorsmod "cosmossdk.io/errors"

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

channeltypesv2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
)

var _ channeltypesv2.PacketMsgServer = &Keeper{}

// SendPacket implements the PacketMsgServer SendPacket method.
func (k *Keeper) SendPacket(ctx context.Context, msg *channeltypesv2.MsgSendPacket) (*channeltypesv2.MsgSendPacketResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
sequence, err := k.sendPacket(ctx, msg.SourceId, msg.TimeoutTimestamp, msg.PacketData)
if err != nil {
sdkCtx.Logger().Error("send packet failed", "source-id", msg.SourceId, "error", errorsmod.Wrap(err, "send packet failed"))
return nil, errorsmod.Wrapf(err, "send packet failed for source id: %s", msg.SourceId)
}

signer, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
sdkCtx.Logger().Error("send packet failed", "error", errorsmod.Wrap(err, "invalid address for msg Signer"))
return nil, errorsmod.Wrap(err, "invalid address for msg Signer")
}

_ = signer

// TODO: implement once app router is wired up.
// https://github.com/cosmos/ibc-go/issues/7384
// for _, pd := range msg.PacketData {
// cbs := k.PortKeeper.AppRouter.Route(pd.SourcePort)
// err := cbs.OnSendPacket(ctx, msg.SourceId, sequence, msg.TimeoutTimestamp, pd, signer)
// if err != nil {
// return nil, err
// }
// }

return &channeltypesv2.MsgSendPacketResponse{Sequence: sequence}, nil
}

func (k Keeper) Acknowledgement(ctx context.Context, acknowledgement *channeltypesv2.MsgAcknowledgement) (*channeltypesv2.MsgAcknowledgementResponse, error) {

Check failure on line 44 in modules/core/04-channel/v2/keeper/msg_server.go

View workflow job for this annotation

GitHub Actions / lint

unused-receiver: method receiver 'k' is not referenced in method's body, consider removing or renaming it as _ (revive)

Check failure on line 44 in modules/core/04-channel/v2/keeper/msg_server.go

View workflow job for this annotation

GitHub Actions / lint

unused-receiver: method receiver 'k' is not referenced in method's body, consider removing or renaming it as _ (revive)
panic("implement me")
}

// RecvPacket implements the PacketMsgServer RecvPacket method.
func (k *Keeper) RecvPacket(ctx context.Context, packet *channeltypesv2.MsgRecvPacket) (*channeltypesv2.MsgRecvPacketResponse, error) {

Check failure on line 49 in modules/core/04-channel/v2/keeper/msg_server.go

View workflow job for this annotation

GitHub Actions / lint

unused-receiver: method receiver 'k' is not referenced in method's body, consider removing or renaming it as _ (revive)

Check failure on line 49 in modules/core/04-channel/v2/keeper/msg_server.go

View workflow job for this annotation

GitHub Actions / lint

unused-receiver: method receiver 'k' is not referenced in method's body, consider removing or renaming it as _ (revive)
panic("implement me")
}

// Timeout implements the PacketMsgServer Timeout method.
func (k *Keeper) Timeout(ctx context.Context, timeout *channeltypesv2.MsgTimeout) (*channeltypesv2.MsgTimeoutResponse, error) {

Check failure on line 54 in modules/core/04-channel/v2/keeper/msg_server.go

View workflow job for this annotation

GitHub Actions / lint

unused-receiver: method receiver 'k' is not referenced in method's body, consider removing or renaming it as _ (revive)

Check failure on line 54 in modules/core/04-channel/v2/keeper/msg_server.go

View workflow job for this annotation

GitHub Actions / lint

unused-receiver: method receiver 'k' is not referenced in method's body, consider removing or renaming it as _ (revive)
panic("implement me")
}
89 changes: 89 additions & 0 deletions modules/core/04-channel/v2/keeper/relay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package keeper

import (
"context"
"strconv"

errorsmod "cosmossdk.io/errors"

clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
channeltypesv2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
"github.com/cosmos/ibc-go/v9/modules/core/exported"
"github.com/cosmos/ibc-go/v9/modules/core/packet-server/types"
)

// sendPacket constructs a packet from the input arguments, writes a packet commitment to state
// in order for the packet to be sent to the counterparty.
func (k *Keeper) sendPacket(
ctx context.Context,
sourceID string,
timeoutTimestamp uint64,
data []channeltypesv2.PacketData,
) (uint64, error) {
// Lookup counterparty associated with our source channel to retrieve the destination channel
counterparty, ok := k.GetCounterparty(ctx, sourceID)
if !ok {
// If the counterparty is not found, attempt to retrieve a v1 channel from the channel keeper
// if it exists, then we will convert it to a v2 counterparty and store it in the packet server keeper
// for future use.
// TODO: figure out how aliasing will work when more than one packet data is sent.
if counterparty, ok = k.AliasV1Channel(ctx, data[0].SourcePort, sourceID); ok {
// we can key on just the source channel here since channel ids are globally unique
k.SetCounterparty(ctx, sourceID, counterparty)
} else {
// if neither a counterparty nor channel is found then simply return an error
return 0, errorsmod.Wrap(types.ErrCounterpartyNotFound, sourceID)
}
}

destID := counterparty.CounterpartyChannelId
clientId := counterparty.ClientId

Check failure on line 41 in modules/core/04-channel/v2/keeper/relay.go

View workflow job for this annotation

GitHub Actions / lint

var-naming: var clientId should be clientID (revive)

Check failure on line 41 in modules/core/04-channel/v2/keeper/relay.go

View workflow job for this annotation

GitHub Actions / lint

var-naming: var clientId should be clientID (revive)

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

// construct packet from given fields and channel state
packet := channeltypesv2.NewPacket(sequence, sourceID, destID, timeoutTimestamp, data...)

if err := packet.ValidateBasic(); err != nil {
return 0, errorsmod.Wrapf(channeltypes.ErrInvalidPacket, "constructed packet failed basic validation: %v", err)
}

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

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

latestTimestamp, err := k.ClientKeeper.GetClientTimestampAtHeight(ctx, clientId, latestHeight)
if err != nil {
return 0, err
}
// check if packet is timed out on the receiving chain
timeout := channeltypes.NewTimeoutWithTimestamp(timeoutTimestamp)
if timeout.TimestampElapsed(latestTimestamp) {
return 0, errorsmod.Wrap(timeout.ErrTimeoutElapsed(latestHeight, latestTimestamp), "invalid packet timeout")
}

commitment := channeltypesv2.CommitPacket(packet)

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

k.Logger(ctx).Info("packet sent", "sequence", strconv.FormatUint(packet.Sequence, 10), "dest_id", packet.DestinationId, "src_id", packet.SourceId)

EmitSendPacketEvents(ctx, packet)

return sequence, nil
}
34 changes: 34 additions & 0 deletions modules/core/04-channel/v2/types/counterparty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package types

import (
errorsmod "cosmossdk.io/errors"

commitmenttypes "github.com/cosmos/ibc-go/v9/modules/core/23-commitment/types/v2"
host "github.com/cosmos/ibc-go/v9/modules/core/24-host"
)

// NewCounterparty creates a new Counterparty instance
func NewCounterparty(clientID, counterpartyChannelID string, merklePathPrefix commitmenttypes.MerklePath) Counterparty {
return Counterparty{
ClientId: clientID,
CounterpartyChannelId: counterpartyChannelID,
MerklePathPrefix: merklePathPrefix,
}
}

// Validate validates the Counterparty
func (c Counterparty) Validate() error {
if err := host.ClientIdentifierValidator(c.ClientId); err != nil {
return err
}

if err := host.ChannelIdentifierValidator(c.CounterpartyChannelId); err != nil {
return err
}

if err := c.MerklePathPrefix.ValidateAsPrefix(); err != nil {
return errorsmod.Wrap(ErrInvalidCounterparty, err.Error())
}

return nil
}
Loading
Loading