Skip to content

Commit 1d69301

Browse files
authored
Merge pull request #2261 from CosmWasm/aw/ibc2_ack_recv
IBCv2 Acknowledge Receive
2 parents adea2f9 + b88345d commit 1d69301

File tree

16 files changed

+382
-11
lines changed

16 files changed

+382
-11
lines changed

.envrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
use flake

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@ dependency-graph.png
4545
*.aux
4646
*.out
4747
*.synctex.gz
48+
49+
# Dev environment
50+
.direnv/

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Add IBC v2 SendPacket message encoding [\#2110](https://github.com/CosmWasm/wasmd/issues/2110)
1414
- Remove IBC fee handling, as it is no longer supported by the IBC protocol [\#2254](https://github.com/CosmWasm/wasmd/pull/2254)
1515
- Add support for IBCv2 Send entry point [\#2252](https://github.com/CosmWasm/wasmd/pull/2252)
16+
- Introduce IBCv2 endpoint for receiving message acknowledgements [\#2261](https://github.com/CosmWasm/wasmd/pull/2261)
1617

1718
## [v0.55.0](https://github.com/CosmWasm/wasmd/tree/v0.55.0) (2025-03-11)
1819

flake.lock

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
inputs = {
3+
flake-utils.url = "github:numtide/flake-utils";
4+
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
5+
};
6+
7+
outputs =
8+
{
9+
self,
10+
flake-utils,
11+
nixpkgs,
12+
}:
13+
flake-utils.lib.eachDefaultSystem (
14+
system:
15+
let
16+
pkgs = import nixpkgs {
17+
inherit system;
18+
};
19+
in
20+
{
21+
formatter = pkgs.nixfmt-rfc-style;
22+
23+
devShells.default = pkgs.mkShell {
24+
buildInputs = with pkgs; [
25+
go
26+
gofumpt
27+
golangci-lint
28+
];
29+
};
30+
}
31+
);
32+
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ require (
180180
github.com/sagikazarmark/locafero v0.6.0 // indirect
181181
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
182182
github.com/sasha-s/go-deadlock v0.3.5 // indirect
183-
github.com/shamaton/msgpack/v2 v2.2.0 // indirect
183+
github.com/shamaton/msgpack/v2 v2.2.3 // indirect
184184
github.com/sourcegraph/conc v0.3.0 // indirect
185185
github.com/spf13/afero v1.11.0 // indirect
186186
github.com/subosito/gotenv v1.6.0 // indirect
@@ -204,7 +204,7 @@ require (
204204
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
205205
golang.org/x/net v0.38.0 // indirect
206206
golang.org/x/oauth2 v0.25.0 // indirect
207-
golang.org/x/sys v0.31.0 // indirect
207+
golang.org/x/sys v0.33.0 // indirect
208208
golang.org/x/term v0.30.0 // indirect
209209
golang.org/x/text v0.23.0 // indirect
210210
golang.org/x/time v0.9.0 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -951,8 +951,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
951951
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
952952
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
953953
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
954-
github.com/shamaton/msgpack/v2 v2.2.0 h1:IP1m01pHwCrMa6ZccP9B3bqxEMKMSmMVAVKk54g3L/Y=
955-
github.com/shamaton/msgpack/v2 v2.2.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
954+
github.com/shamaton/msgpack/v2 v2.2.3 h1:uDOHmxQySlvlUYfQwdjxyybAOzjlQsD1Vjy+4jmO9NM=
955+
github.com/shamaton/msgpack/v2 v2.2.3/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
956956
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
957957
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
958958
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -1330,8 +1330,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
13301330
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
13311331
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
13321332
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1333-
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
1334-
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
1333+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
1334+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
13351335
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
13361336
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
13371337
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

tests/e2e/ibc2_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type QueryMsg struct {
2323

2424
// ibc2 contract response type
2525
type State struct {
26+
IBC2PacketAckCounter uint32 `json:"ibc2_packet_ack_counter"`
2627
IBC2PacketReceiveCounter uint32 `json:"ibc2_packet_receive_counter"`
2728
IBC2PacketTimeoutCounter uint32 `json:"ibc2_packet_timeout_counter"`
2829
LastChannelID string `json:"last_channel_id"`
@@ -36,6 +37,60 @@ type IbcPayload struct {
3637
SendAsyncAckForPrevMsg bool `json:"send_async_ack_for_prev_msg"`
3738
}
3839

40+
func TestIBC2AckMsg(t *testing.T) {
41+
coord := wasmibctesting.NewCoordinator(t, 2)
42+
chainA := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(1)))
43+
chainB := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(2)))
44+
contractCodeA := chainA.StoreCodeFile("./testdata/ibc2.wasm").CodeID
45+
contractAddrA := chainA.InstantiateContract(contractCodeA, []byte(`{}`))
46+
contractPortA := wasmkeeper.PortIDForContractV2(contractAddrA)
47+
require.NotEmpty(t, contractAddrA)
48+
49+
contractCodeB := chainB.StoreCodeFile("./testdata/ibc2.wasm").CodeID
50+
// Skip initial contract address to not overlap with ChainA
51+
_ = chainB.InstantiateContract(contractCodeB, []byte(`{}`))
52+
contractAddrB := chainB.InstantiateContract(contractCodeB, []byte(`{}`))
53+
contractPortB := wasmkeeper.PortIDForContractV2(contractAddrB)
54+
require.NotEmpty(t, contractAddrB)
55+
56+
path := wasmibctesting.NewWasmPath(chainA, chainB)
57+
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
58+
PortID: contractPortA,
59+
Version: ibctransfertypes.V1,
60+
Order: channeltypes.UNORDERED,
61+
}
62+
path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{
63+
PortID: contractPortB,
64+
Version: ibctransfertypes.V1,
65+
Order: channeltypes.UNORDERED,
66+
}
67+
68+
path.Path.SetupV2()
69+
70+
// IBC v2 Payload from contract on Chain B to contract on Chain A
71+
payload := mockv2.NewMockPayload(contractPortB, contractPortA)
72+
var err error
73+
payload.Value, err = json.Marshal(IbcPayload{ResponseWithoutAck: false, SendAsyncAckForPrevMsg: false})
74+
require.NoError(t, err)
75+
76+
// Message timeout
77+
timeoutTimestamp := uint64(chainB.GetContext().BlockTime().Add(time.Minute * 5).Unix())
78+
79+
_, err = path.EndpointB.MsgSendPacket(timeoutTimestamp, payload)
80+
require.NoError(t, err)
81+
82+
// First message send through test
83+
err = wasmibctesting.RelayPendingPacketsWithAcksV2(path)
84+
require.NoError(t, err)
85+
86+
// Check if counter was incremented in the recv entry point
87+
var response State
88+
89+
err = chainB.SmartQuery(contractAddrB.String(), QueryMsg{QueryState: struct{}{}}, &response)
90+
require.NoError(t, err)
91+
require.Equal(t, uint32(1), response.IBC2PacketAckCounter)
92+
}
93+
3994
func TestIBC2SendReceiveMsg(t *testing.T) {
4095
coord := wasmibctesting.NewCoordinator(t, 2)
4196
chainA := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(1)))

tests/e2e/testdata/ibc2.wasm

102 KB
Binary file not shown.

tests/wasmibctesting/utils.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ func (app WasmTestApp) GetTxConfig() client.TxConfig {
5454
return app.TxConfig()
5555
}
5656

57+
type PendingAckPacketV2 struct {
58+
channeltypesv2.Packet
59+
Ack []byte
60+
}
61+
5762
type WasmTestChain struct {
5863
*ibctesting.TestChain
5964

@@ -361,6 +366,99 @@ func RelayPacketWithoutAck(path *ibctesting.Path, packet channeltypes.Packet) er
361366
return fmt.Errorf("packet commitment does not exist on either endpoint for provided packet")
362367
}
363368

369+
func MsgRecvPacketWithResultV2(endpoint *ibctesting.Endpoint, packet channeltypesv2.Packet) (*abci.ExecTxResult, error) {
370+
// get proof of packet commitment from chainA
371+
packetKey := hostv2.PacketCommitmentKey(packet.SourceClient, packet.Sequence)
372+
proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey)
373+
374+
msg := channeltypesv2.NewMsgRecvPacket(packet, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String())
375+
376+
res, err := endpoint.Chain.SendMsgs(msg)
377+
if err != nil {
378+
return nil, err
379+
}
380+
381+
return res, endpoint.Counterparty.UpdateClient()
382+
}
383+
384+
func RelayPacketV2(path *WasmPath, packet channeltypesv2.Packet) error {
385+
pc := path.EndpointA.Chain.App.GetIBCKeeper().ChannelKeeperV2.GetPacketCommitment(path.EndpointA.Chain.GetContext(), packet.GetSourceClient(), packet.GetSequence())
386+
if bytes.Equal(pc, channeltypesv2.CommitPacket(packet)) {
387+
// packet found, relay from A to B
388+
if err := path.EndpointB.UpdateClient(); err != nil {
389+
return err
390+
}
391+
392+
res, err := MsgRecvPacketWithResultV2(path.EndpointB, packet)
393+
if err != nil {
394+
return err
395+
}
396+
397+
ack, err := ParseAckFromEventsV2(res.GetEvents())
398+
if err != nil {
399+
return fmt.Errorf("tried to relay packet without ack but got ack")
400+
}
401+
402+
var msg channeltypesv2.Acknowledgement
403+
err = proto.Unmarshal(ack, &msg)
404+
if err != nil {
405+
return err
406+
}
407+
408+
// packet found, relay from A to B
409+
if err := path.EndpointA.UpdateClient(); err != nil {
410+
return err
411+
}
412+
413+
path.chainA.Logf("sending ack to other chain")
414+
err = path.EndpointA.MsgAcknowledgePacket(packet, msg)
415+
if err != nil {
416+
return err
417+
}
418+
419+
return nil
420+
}
421+
422+
pc = path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeperV2.GetPacketCommitment(path.EndpointB.Chain.GetContext(), packet.GetSourceClient(), packet.GetSequence())
423+
if bytes.Equal(pc, channeltypesv2.CommitPacket(packet)) {
424+
// packet found, relay B to A
425+
if err := path.EndpointA.UpdateClient(); err != nil {
426+
return err
427+
}
428+
429+
res, err := MsgRecvPacketWithResultV2(path.EndpointA, packet)
430+
if err != nil {
431+
return err
432+
}
433+
434+
ack, err := ParseAckFromEventsV2(res.GetEvents())
435+
if err != nil {
436+
return fmt.Errorf("tried to relay packet without ack but got ack")
437+
}
438+
439+
var msg channeltypesv2.Acknowledgement
440+
err = proto.Unmarshal(ack, &msg)
441+
if err != nil {
442+
return err
443+
}
444+
445+
// packet found, relay from A to B
446+
if err := path.EndpointB.UpdateClient(); err != nil {
447+
return err
448+
}
449+
450+
path.chainA.Logf("sending ack to other chain")
451+
err = path.EndpointB.MsgAcknowledgePacket(packet, msg)
452+
if err != nil {
453+
return err
454+
}
455+
456+
return nil
457+
}
458+
459+
return fmt.Errorf("packet commitment does not exist on either endpointV2 for provided packet")
460+
}
461+
364462
// RelayPacketWithoutAckV2 attempts to relay the packet first on EndpointA and then on EndpointB
365463
// if EndpointA does not contain a packet commitment for that packet. An error is returned
366464
// if a relay step fails or the packet commitment does not exist on either endpoint.
@@ -469,6 +567,34 @@ func RelayPendingPacketsV2(path *WasmPath) error {
469567
return nil
470568
}
471569

570+
// RelayPendingPacketsWithAcksV2 sends pending packages between path.EndpointA and path.EndpointB along with acks
571+
func RelayPendingPacketsWithAcksV2(path *WasmPath) error {
572+
// get all the packet to relay src->dest
573+
src := path.EndpointA
574+
require.NoError(path.chainA, src.UpdateClient())
575+
path.chainA.Logf("Relay: %d PacketsV2 A->B, %d PacketsV2 B->A\n", len(*path.chainA.PendingSendPacketsV2), len(*path.chainB.PendingSendPacketsV2))
576+
for _, v := range *path.chainA.PendingSendPacketsV2 {
577+
err := RelayPacketV2(path, v)
578+
if err != nil {
579+
return err
580+
}
581+
582+
*path.chainA.PendingSendPacketsV2 = (*path.chainA.PendingSendPacketsV2)[1:]
583+
}
584+
585+
src = path.EndpointB
586+
require.NoError(path.chainB, src.UpdateClient())
587+
for _, v := range *path.chainB.PendingSendPacketsV2 {
588+
err := RelayPacketV2(path, v)
589+
if err != nil {
590+
return err
591+
}
592+
593+
*path.chainB.PendingSendPacketsV2 = (*path.chainB.PendingSendPacketsV2)[1:]
594+
}
595+
return nil
596+
}
597+
472598
// TimeoutPendingPackets returns the package to source chain to let the IBC app revert any operation.
473599
// from A to B
474600
func TimeoutPendingPackets(coord *ibctesting.Coordinator, path *WasmPath) error {
@@ -679,6 +805,26 @@ func ParseAckFromEvents(events []abci.Event) ([]byte, error) {
679805
return nil, fmt.Errorf("acknowledgement event attribute not found")
680806
}
681807

808+
// ParseAckFromEventsV2 parses events emitted from a MsgRecvPacket and returns the
809+
// acknowledgement.
810+
func ParseAckFromEventsV2(events []abci.Event) ([]byte, error) {
811+
for _, ev := range events {
812+
if ev.Type == channeltypes.EventTypeWriteAck {
813+
for _, attr := range ev.Attributes {
814+
if attr.Key == channeltypesv2.AttributeKeyEncodedAckHex {
815+
// The first two bytes is a noise
816+
bz, err := hex.DecodeString(attr.Value)
817+
if err != nil {
818+
panic(err)
819+
}
820+
return bz, nil
821+
}
822+
}
823+
}
824+
}
825+
return nil, fmt.Errorf("acknowledgement event attribute not found")
826+
}
827+
682828
// NewCoordinator initializes Coordinator with N TestChain's
683829
func NewCoordinator(t *testing.T, n int, opts ...[]wasmkeeper.Option) *ibctesting.Coordinator {
684830
t.Helper()

0 commit comments

Comments
 (0)