Skip to content

Commit

Permalink
Simplify WriteAck API for async acks (#7869)
Browse files Browse the repository at this point in the history
* simplify writeack api

* fix tests
  • Loading branch information
AdityaSripal authored Jan 23, 2025
1 parent ddce441 commit ffdb9d0
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 6 deletions.
32 changes: 32 additions & 0 deletions modules/core/04-channel/v2/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,35 @@ func (k *Keeper) SetNextSequenceSend(ctx context.Context, clientID string, seque
panic(err)
}
}

// SetAsyncPacket writes the packet under the async path
func (k *Keeper) SetAsyncPacket(ctx context.Context, clientID string, sequence uint64, packet types.Packet) {
store := k.KVStoreService.OpenKVStore(ctx)
bz := k.cdc.MustMarshal(&packet)
if err := store.Set(types.AsyncPacketKey(clientID, sequence), bz); err != nil {
panic(err)
}
}

// GetAsyncPacket fetches the packet from the async path
func (k *Keeper) GetAsyncPacket(ctx context.Context, clientID string, sequence uint64) (types.Packet, bool) {
store := k.KVStoreService.OpenKVStore(ctx)
bz, err := store.Get(types.AsyncPacketKey(clientID, sequence))
if err != nil {
panic(err)
}
if len(bz) == 0 {
return types.Packet{}, false
}
var packet types.Packet
k.cdc.MustUnmarshal(bz, &packet)
return packet, true
}

// DeleteAsyncPacket deletes the packet from the async path
func (k *Keeper) DeleteAsyncPacket(ctx context.Context, clientID string, sequence uint64) {
store := k.KVStoreService.OpenKVStore(ctx)
if err := store.Delete(types.AsyncPacketKey(clientID, sequence)); err != nil {
panic(err)
}
}
5 changes: 4 additions & 1 deletion modules/core/04-channel/v2/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,12 @@ func (k *Keeper) RecvPacket(ctx context.Context, msg *types.MsgRecvPacket) (*typ
// Set packet acknowledgement only if the acknowledgement is not async.
// NOTE: IBC applications modules may call the WriteAcknowledgement asynchronously if the
// acknowledgement is async.
if err := k.WriteAcknowledgement(ctx, msg.Packet, ack); err != nil {
if err := k.writeAcknowledgement(ctx, msg.Packet, ack); err != nil {
return nil, err
}
} else {
// store the packet temporarily until the application returns an acknowledgement
k.SetAsyncPacket(ctx, msg.Packet.DestinationClient, msg.Packet.Sequence, msg.Packet)
}

// TODO: store the packet for async applications to access if required.
Expand Down
34 changes: 30 additions & 4 deletions modules/core/04-channel/v2/keeper/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ func (k *Keeper) recvPacket(
return nil
}

// WriteAcknowledgement writes the acknowledgement to the store.
// TODO: change this function to accept destPort, destChannel, sequence, ack
func (k Keeper) WriteAcknowledgement(
// writeAcknowledgement writes the acknowledgement to the store and emits the packet and acknowledgement
// for relayers to relay the acknowledgement to the counterparty chain.
func (k Keeper) writeAcknowledgement(
ctx context.Context,
packet types.Packet,
ack types.Acknowledgement,
Expand Down Expand Up @@ -184,7 +184,33 @@ func (k Keeper) WriteAcknowledgement(

emitWriteAcknowledgementEvents(ctx, packet, ack)

// TODO: delete the packet that has been stored in ibc-core.
return nil
}

// WriteAcknowledgement writes the acknowledgement and emits events for asynchronous acknowledgements
// this is the method to be called by external apps when they want to write an acknowledgement asyncrhonously
func (k *Keeper) WriteAcknowledgement(ctx context.Context, clientID string, sequence uint64, ack types.Acknowledgement) error {
sdkCtx := sdk.UnwrapSDKContext(ctx)

// Validate the acknowledgement
if err := ack.Validate(); err != nil {
sdkCtx.Logger().Error("write acknowledgement failed", "error", errorsmod.Wrap(err, "invalid acknowledgement"))
return errorsmod.Wrap(err, "invalid acknowledgement")
}

packet, ok := k.GetAsyncPacket(ctx, clientID, sequence)
if !ok {
return errorsmod.Wrapf(types.ErrInvalidAcknowledgement, "packet with clientID (%s) and sequence (%d) not found for async acknowledgement", clientID, sequence)
}

// Write the acknowledgement to the store
if err := k.writeAcknowledgement(ctx, packet, ack); err != nil {
sdkCtx.Logger().Error("write acknowledgement failed", "error", errorsmod.Wrap(err, "write acknowledgement failed"))
return errorsmod.Wrap(err, "write acknowledgement failed")
}

// Delete the packet from the async store
k.DeleteAsyncPacket(ctx, clientID, sequence)

return nil
}
Expand Down
16 changes: 15 additions & 1 deletion modules/core/04-channel/v2/keeper/packet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,17 @@ func (suite *KeeperTestSuite) TestWriteAcknowledgement() {
"failure: client not found",
func() {
packet.DestinationClient = ibctesting.InvalidID
suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence)
suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, packet)
},
clienttypes.ErrCounterpartyNotFound,
},
{
"failure: counterparty client identifier different than source client",
func() {
packet.SourceClient = unusedChannel
suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence)
suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, packet)
},
clienttypes.ErrInvalidCounterparty,
},
Expand All @@ -275,9 +279,17 @@ func (suite *KeeperTestSuite) TestWriteAcknowledgement() {
"failure: receipt not found for packet",
func() {
packet.Sequence = 2
suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, packet)
},
types.ErrInvalidPacket,
},
{
"failure: async packet not found",
func() {
suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.DeleteAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence)
},
types.ErrInvalidAcknowledgement,
},
}

for _, tc := range testCases {
Expand All @@ -301,11 +313,13 @@ func (suite *KeeperTestSuite) TestWriteAcknowledgement() {
AppAcknowledgements: [][]byte{mockv2.MockRecvPacketResult.Acknowledgement},
}

// mock receive with async acknowledgement
suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence)
suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, packet)

tc.malleate()

err := suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.WriteAcknowledgement(suite.chainB.GetContext(), packet, ack)
err := suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.WriteAcknowledgement(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, ack)

expPass := tc.expError == nil
if expPass {
Expand Down
11 changes: 11 additions & 0 deletions modules/core/04-channel/v2/types/keys.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
package types

import "fmt"

const (
// SubModuleName defines the channelv2 module name.
SubModuleName = "channelv2"

// KeyAsyncPacket defines the key to store the async packet.
KeyAsyncPacket = "async_packet"
)

// AsyncPacketKey returns the key under which the packet is stored
// if the receiving application returns an async acknowledgement.
func AsyncPacketKey(clientID string, sequence uint64) []byte {
return []byte(fmt.Sprintf("%s/%s/%d", KeyAsyncPacket, clientID, sequence))
}

0 comments on commit ffdb9d0

Please sign in to comment.