-
Notifications
You must be signed in to change notification settings - Fork 624
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
(test) Add tests for callback execution when forwarding a packet #6805
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
package ibccallbacks_test | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
"github.com/cosmos/ibc-go/modules/apps/callbacks/testing/simapp" | ||
callbacktypes "github.com/cosmos/ibc-go/modules/apps/callbacks/types" | ||
"github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" | ||
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" | ||
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" | ||
ibctesting "github.com/cosmos/ibc-go/v8/testing" | ||
) | ||
|
||
func init() { | ||
ibctesting.DefaultTestingAppInit = SetupTestingApp | ||
} | ||
|
||
type CallbacksForwardingTestSuite struct { | ||
suite.Suite | ||
|
||
coordinator *ibctesting.Coordinator | ||
|
||
chainA *ibctesting.TestChain | ||
chainB *ibctesting.TestChain | ||
chainC *ibctesting.TestChain | ||
|
||
pathAtoB *ibctesting.Path | ||
pathBtoC *ibctesting.Path | ||
} | ||
|
||
// setupChains sets up a coordinator with 3 test chains. | ||
func (s *CallbacksForwardingTestSuite) setupChains() { | ||
s.coordinator = ibctesting.NewCoordinator(s.T(), 3) | ||
s.chainA = s.coordinator.GetChain(ibctesting.GetChainID(1)) | ||
s.chainB = s.coordinator.GetChain(ibctesting.GetChainID(2)) | ||
s.chainC = s.coordinator.GetChain(ibctesting.GetChainID(3)) | ||
|
||
s.pathAtoB = ibctesting.NewTransferPath(s.chainA, s.chainB) | ||
s.pathBtoC = ibctesting.NewTransferPath(s.chainB, s.chainC) | ||
} | ||
|
||
func (s *CallbacksForwardingTestSuite) SetupTest() { | ||
s.setupChains() | ||
|
||
s.pathAtoB.Setup() | ||
s.pathBtoC.Setup() | ||
} | ||
|
||
func TestIBCCallbacksForwardingTestsuite(t *testing.T) { | ||
suite.Run(t, new(CallbacksForwardingTestSuite)) | ||
} | ||
|
||
// TestForwardingWithMemoCallback tests that, when forwarding a packet with memo from A to B to C, | ||
// the callback is executed only on the final hop. | ||
// NOTE: this does not test the full forwarding behaviour (assert on amounts, packets, acks etc) | ||
// as this is covered in other forwarding tests. | ||
func (s *CallbacksForwardingTestSuite) TestForwardingWithMemoCallback() { | ||
testCases := []struct { | ||
name string | ||
testMemo string | ||
expCallbackMapOnChainB map[callbacktypes.CallbackType]int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We discussed this with @chatton and our understanding is that when the memo specifies If that's not the case, please let me know! I was surprised to see some callbacks executed on B but after some reasoning it started to make sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the current implementation it makes sense that this would be the case.. but it is a little weird imo. Wonder if we should have a testcase for ensuring that the send_packet callback is never invoked on chainA There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yea very strange, but I guess not much we can do. Would be great to add into the docs somewhere? With multi packetdata, it should become more explicit when and where callbacks are executed (hopefully 😅) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I opened #6883 to ensure we document this properly |
||
expCallbackMapOnChainC map[callbacktypes.CallbackType]int | ||
}{ | ||
{ | ||
name: "no memo", | ||
expCallbackMapOnChainB: map[callbacktypes.CallbackType]int{}, | ||
expCallbackMapOnChainC: map[callbacktypes.CallbackType]int{}, | ||
}, | ||
{ | ||
name: "recv callback", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's still not super clear to me what's the mapping between Memo content and which callbacks are executed. Does anyone have a pointer to learn this better? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the main thing is that the special keys in the callback data specify on which chain the callback should be executed. Which is why that in this test case, it is only being executed on the |
||
testMemo: fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.SuccessContract), | ||
expCallbackMapOnChainB: map[callbacktypes.CallbackType]int{}, | ||
expCallbackMapOnChainC: map[callbacktypes.CallbackType]int{callbacktypes.CallbackTypeReceivePacket: 1}, | ||
}, | ||
{ | ||
name: "ack callback", | ||
testMemo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), | ||
expCallbackMapOnChainB: map[callbacktypes.CallbackType]int{callbacktypes.CallbackTypeAcknowledgementPacket: 1}, | ||
expCallbackMapOnChainC: map[callbacktypes.CallbackType]int{}, | ||
}, | ||
Comment on lines
+80
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might be worth adding a memo that contains both
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. {
name: "ack and recv callback",
testMemo: fmt.Sprintf(`{"src_callback": {"address": "%s"}, "dest_callback": {"address": "%s"}}`, simapp.SuccessContract, simapp.SuccessContract),
expCallbackMapOnChainB: map[callbacktypes.CallbackType]int{callbacktypes.CallbackTypeAcknowledgementPacket: 1},
expCallbackMapOnChainC: map[callbacktypes.CallbackType]int{callbacktypes.CallbackTypeReceivePacket: 1},
}, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good one thank you! added! |
||
{ | ||
name: "ack and recv callback", | ||
testMemo: fmt.Sprintf(`{"src_callback": {"address": "%s"}, "dest_callback": {"address": "%s"}}`, simapp.SuccessContract, simapp.SuccessContract), | ||
expCallbackMapOnChainB: map[callbacktypes.CallbackType]int{callbacktypes.CallbackTypeAcknowledgementPacket: 1}, | ||
expCallbackMapOnChainC: map[callbacktypes.CallbackType]int{callbacktypes.CallbackTypeReceivePacket: 1}, | ||
}, | ||
{ | ||
name: "ack callback with low gas (error)", | ||
testMemo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogErrorContract), | ||
expCallbackMapOnChainB: map[callbacktypes.CallbackType]int{callbacktypes.CallbackTypeAcknowledgementPacket: 1}, | ||
expCallbackMapOnChainC: map[callbacktypes.CallbackType]int{}, | ||
}, | ||
{ | ||
name: "recv callback with low gas (error)", | ||
testMemo: fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.OogErrorContract), | ||
expCallbackMapOnChainB: map[callbacktypes.CallbackType]int{}, | ||
expCallbackMapOnChainC: map[callbacktypes.CallbackType]int{callbacktypes.CallbackTypeReceivePacket: 1}, | ||
}, | ||
} | ||
for _, tc := range testCases { | ||
s.Run(tc.name, func() { | ||
s.SetupTest() | ||
|
||
coinOnA := ibctesting.TestCoin | ||
sender := s.chainA.SenderAccounts[0].SenderAccount | ||
receiver := s.chainC.SenderAccounts[0].SenderAccount | ||
forwarding := types.NewForwarding(false, types.NewHop( | ||
s.pathBtoC.EndpointA.ChannelConfig.PortID, | ||
s.pathBtoC.EndpointA.ChannelID, | ||
)) | ||
successAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) | ||
|
||
transferMsg := types.NewMsgTransfer( | ||
s.pathAtoB.EndpointA.ChannelConfig.PortID, | ||
s.pathAtoB.EndpointA.ChannelID, | ||
sdk.NewCoins(coinOnA), | ||
sender.GetAddress().String(), | ||
receiver.GetAddress().String(), | ||
clienttypes.ZeroHeight(), | ||
s.chainA.GetTimeoutTimestamp(), | ||
tc.testMemo, | ||
forwarding, | ||
) | ||
|
||
result, err := s.chainA.SendMsgs(transferMsg) | ||
s.Require().NoError(err) // message committed | ||
|
||
// parse the packet from result events and recv packet on chainB | ||
packetFromAtoB, err := ibctesting.ParsePacketFromEvents(result.Events) | ||
s.Require().NoError(err) | ||
s.Require().NotNil(packetFromAtoB) | ||
|
||
err = s.pathAtoB.EndpointB.UpdateClient() | ||
s.Require().NoError(err) | ||
|
||
result, err = s.pathAtoB.EndpointB.RecvPacketWithResult(packetFromAtoB) | ||
s.Require().NoError(err) | ||
s.Require().NotNil(result) | ||
|
||
packetFromBtoC, err := ibctesting.ParsePacketFromEvents(result.Events) | ||
s.Require().NoError(err) | ||
s.Require().NotNil(packetFromBtoC) | ||
|
||
err = s.pathBtoC.EndpointA.UpdateClient() | ||
s.Require().NoError(err) | ||
|
||
err = s.pathBtoC.EndpointB.UpdateClient() | ||
s.Require().NoError(err) | ||
|
||
result, err = s.pathBtoC.EndpointB.RecvPacketWithResult(packetFromBtoC) | ||
s.Require().NoError(err) | ||
s.Require().NotNil(result) | ||
|
||
packetOnC, err := ibctesting.ParseRecvPacketFromEvents(result.Events) | ||
s.Require().NoError(err) | ||
s.Require().NotNil(packetOnC) | ||
|
||
// Ack back to B | ||
err = s.pathBtoC.EndpointB.UpdateClient() | ||
s.Require().NoError(err) | ||
|
||
err = s.pathBtoC.EndpointA.AcknowledgePacket(packetFromBtoC, successAck.Acknowledgement()) | ||
s.Require().NoError(err) | ||
|
||
// Ack back to A | ||
err = s.pathAtoB.EndpointA.UpdateClient() | ||
s.Require().NoError(err) | ||
|
||
err = s.pathAtoB.EndpointA.AcknowledgePacket(packetFromAtoB, successAck.Acknowledgement()) | ||
s.Require().NoError(err) | ||
|
||
// We never expect chainA to have executed any callback | ||
s.Require().Empty(GetSimApp(s.chainA).MockContractKeeper.Counters, "chainA's callbacks counter map is not empty") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this covers my thoughts on any source chain callback being invoked on A! |
||
|
||
// We expect chainB to have executed callbacks when the memo is of type `src_callback` | ||
chainBCallbackMap := GetSimApp(s.chainB).MockContractKeeper.Counters | ||
s.Require().Equal(tc.expCallbackMapOnChainB, chainBCallbackMap, "chainB: expected callback counters map to be %v, got %v instead", tc.expCallbackMapOnChainB, chainBCallbackMap) | ||
|
||
// We expect chainC to have executed callbacks when the memo is of type `dest_callback` | ||
chainCCallbackMap := GetSimApp(s.chainC).MockContractKeeper.Counters | ||
s.Require().Equal(tc.expCallbackMapOnChainC, chainCCallbackMap, "chainC: expected callback counters map to be %v, got %v instead", tc.expCallbackMapOnChainC, chainCCallbackMap) | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to add a new file here in the callbacks module because it's the only module in which we can check the callbacks counter. This required setting up a new, small test suite that creates 3 chains.