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

feat(core, apps): 'PacketDataProvider' interface added and implemented #4199

Merged
merged 18 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
27 changes: 27 additions & 0 deletions modules/apps/27-interchain-accounts/types/packet.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package types

import (
"encoding/json"
"time"

errorsmod "cosmossdk.io/errors"

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

ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
)

var _ ibcexported.PacketDataProvider = (*InterchainAccountPacketData)(nil)

// MaxMemoCharLength defines the maximum length for the InterchainAccountPacketData memo field
const MaxMemoCharLength = 256

Expand Down Expand Up @@ -64,3 +69,25 @@ func (ct CosmosTx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {

return nil
}

// GetCustomPacketData returns a json object from the memo as `map[string]interface{}` so that it
// can be interpreted as a json object with keys.
// If the key is missing or the memo is not properly formatted, then nil is returned.
func (iapd InterchainAccountPacketData) GetCustomPacketData(key string) interface{} {
if len(iapd.Memo) == 0 {
return nil
}

jsonObject := make(map[string]interface{})
err := json.Unmarshal([]byte(iapd.Memo), &jsonObject)
if err != nil {
return nil
}

memoData, ok := jsonObject[key].(map[string]interface{})
if !ok {
return nil
}

return memoData
}
70 changes: 70 additions & 0 deletions modules/apps/27-interchain-accounts/types/packet_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package types_test

import (
"fmt"

"github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
ibctesting "github.com/cosmos/ibc-go/v7/testing"
)

var largeMemo = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
Expand Down Expand Up @@ -82,3 +85,70 @@ func (suite *TypesTestSuite) TestValidateBasic() {
})
}
}

func (suite *TypesTestSuite) TestPacketDataProvider() {
expCallbackAddr := ibctesting.TestAccAddress

testCases := []struct {
name string
packetData types.InterchainAccountPacketData
expAdditionalData map[string]interface{}
srdtrk marked this conversation as resolved.
Show resolved Hide resolved
}{
{
"success: src_callback key in memo",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, expCallbackAddr),
},
map[string]interface{}{
"address": expCallbackAddr,
},
},
{
"success: src_callback key in memo with additional fields",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "200000"}}`, expCallbackAddr),
},
map[string]interface{}{
"address": expCallbackAddr,
"gas_limit": "200000",
},
},
{
"failure: empty memo",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: "",
},
nil,
},
{
"failure: non-json memo",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: "invalid",
},
nil,
},
{
"failure: invalid src_callback key",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: `{"src_callback": "invalid"}`,
},
nil,
},
}

for _, tc := range testCases {
additionalData, ok := tc.packetData.GetCustomPacketData("src_callback").(map[string]interface{})
suite.Require().Equal(ok, additionalData != nil)
suite.Require().Equal(tc.expAdditionalData, additionalData)
}
}
26 changes: 26 additions & 0 deletions modules/apps/transfer/types/packet.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"encoding/json"
"strings"
"time"

Expand All @@ -10,8 +11,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"

ibcerrors "github.com/cosmos/ibc-go/v7/modules/core/errors"
ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
)

var _ ibcexported.PacketDataProvider = (*FungibleTokenPacketData)(nil)

var (
// DefaultRelativePacketTimeoutHeight is the default packet timeout height (in blocks) relative
// to the current block height of the counterparty chain provided by the client state. The
Expand Down Expand Up @@ -64,3 +68,25 @@ func (ftpd FungibleTokenPacketData) ValidateBasic() error {
func (ftpd FungibleTokenPacketData) GetBytes() []byte {
return sdk.MustSortJSON(mustProtoMarshalJSON(&ftpd))
}

// GetCustomPacketData returns a json object from the memo as `map[string]interface{}` so that
// it can be interpreted as a json object with keys.
// If the key is missing or the memo is not properly formatted, then nil is returned.
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
func (ftpd FungibleTokenPacketData) GetCustomPacketData(key string) interface{} {
if len(ftpd.Memo) == 0 {
return nil
}

jsonObject := make(map[string]interface{})
err := json.Unmarshal([]byte(ftpd.Memo), &jsonObject)
if err != nil {
return nil
}

memoData, ok := jsonObject[key].(map[string]interface{})
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return nil
}

return memoData
}
82 changes: 82 additions & 0 deletions modules/apps/transfer/types/packet_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -43,3 +44,84 @@ func TestFungibleTokenPacketDataValidateBasic(t *testing.T) {
}
}
}

func (suite *TypesTestSuite) TestPacketDataProvider() {
testCases := []struct {
name string
packetData types.FungibleTokenPacketData
expAdditionalData map[string]interface{}
Copy link
Contributor

Choose a reason for hiding this comment

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

can also maybe call expCustomData?

Copy link
Member Author

Choose a reason for hiding this comment

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

done

expPacketSender string
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
}{
{
"success: src_callback key in memo",
types.FungibleTokenPacketData{
Denom: denom,
Amount: amount,
Sender: sender,
Receiver: receiver,
Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, receiver),
},
map[string]interface{}{
"address": receiver,
},
sender,
},
{
"success: src_callback key in memo with additional fields",
types.FungibleTokenPacketData{
Denom: denom,
Amount: amount,
Sender: sender,
Receiver: receiver,
Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "200000"}}`, receiver),
},
map[string]interface{}{
"address": receiver,
"gas_limit": "200000",
},
sender,
},
{
"failure: empty memo",
types.FungibleTokenPacketData{
Denom: denom,
Amount: amount,
Sender: sender,
Receiver: receiver,
Memo: "",
},
nil,
sender,
},
{
"failure: non-json memo",
types.FungibleTokenPacketData{
Denom: denom,
Amount: amount,
Sender: sender,
Receiver: receiver,
Memo: "invalid",
},
nil,
sender,
},
{
"failure: invalid src_callback key",
types.FungibleTokenPacketData{
Denom: denom,
Amount: amount,
Sender: sender,
Receiver: receiver,
Memo: `{"src_callback": "invalid"}`,
},
nil,
sender,
},
}

for _, tc := range testCases {
additionalData, ok := tc.packetData.GetCustomPacketData("src_callback").(map[string]interface{})
suite.Require().Equal(ok, additionalData != nil)
suite.Require().Equal(tc.expAdditionalData, additionalData)
}
}
33 changes: 0 additions & 33 deletions modules/core/exported/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,3 @@ type CounterpartyChannelI interface {
GetChannelID() string
ValidateBasic() error
}

// PacketI defines the standard interface for IBC packets
type PacketI interface {
GetSequence() uint64
GetTimeoutHeight() Height
GetTimeoutTimestamp() uint64
GetSourcePort() string
GetSourceChannel() string
GetDestPort() string
GetDestChannel() string
GetData() []byte
ValidateBasic() error
}

// Acknowledgement defines the interface used to return acknowledgements in the OnRecvPacket callback.
// The Acknowledgement interface is used by core IBC to ensure partial state changes are not committed
// when packet receives have not properly succeeded (typically resulting in an error acknowledgement being returned).
// The interface also allows core IBC to obtain the acknowledgement bytes whose encoding is determined by each IBC application or middleware.
// Each custom acknowledgement type must implement this interface.
type Acknowledgement interface {
// Success determines if the IBC application state should be persisted when handling `RecvPacket`.
// During `OnRecvPacket` IBC application callback execution, all state changes are held in a cache store and committed if:
// - the acknowledgement.Success() returns true
// - a nil acknowledgement is returned (asynchronous acknowledgements)
//
// Note 1: IBC application callback events are always persisted so long as `RecvPacket` succeeds without error.
//
// Note 2: The return value should account for the success of the underlying IBC application or middleware. Thus the `acknowledgement.Success` is representative of the entire IBC stack's success when receiving a packet. The individual success of each acknowledgement associated with an IBC application or middleware must be determined by obtaining the actual acknowledgement type after decoding the acknowledgement bytes.
//
// See https://github.com/cosmos/ibc-go/blob/v7.0.0/docs/ibc/apps.md for further explanations.
Success() bool
Acknowledgement() []byte
}
45 changes: 45 additions & 0 deletions modules/core/exported/packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package exported

// PacketI defines the standard interface for IBC packets
type PacketI interface {
GetSequence() uint64
GetTimeoutHeight() Height
GetTimeoutTimestamp() uint64
GetSourcePort() string
GetSourceChannel() string
GetDestPort() string
GetDestChannel() string
GetData() []byte
ValidateBasic() error
}

// Acknowledgement defines the interface used to return acknowledgements in the OnRecvPacket callback.
// The Acknowledgement interface is used by core IBC to ensure partial state changes are not committed
// when packet receives have not properly succeeded (typically resulting in an error acknowledgement being returned).
// The interface also allows core IBC to obtain the acknowledgement bytes whose encoding is determined by each IBC application or middleware.
// Each custom acknowledgement type must implement this interface.
type Acknowledgement interface {
// Success determines if the IBC application state should be persisted when handling `RecvPacket`.
// During `OnRecvPacket` IBC application callback execution, all state changes are held in a cache store and committed if:
// - the acknowledgement.Success() returns true
// - a nil acknowledgement is returned (asynchronous acknowledgements)
//
// Note 1: IBC application callback events are always persisted so long as `RecvPacket` succeeds without error.
//
// Note 2: The return value should account for the success of the underlying IBC application or middleware. Thus the `acknowledgement.Success` is representative of the entire IBC stack's success when receiving a packet. The individual success of each acknowledgement associated with an IBC application or middleware must be determined by obtaining the actual acknowledgement type after decoding the acknowledgement bytes.
//
// See https://github.com/cosmos/ibc-go/blob/v7.0.0/docs/ibc/apps.md for further explanations.
Success() bool
Acknowledgement() []byte
}

// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application.
// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information.
// A short term hack was introduced into several packet data's to utilize a memo to carry this information on behalf of another application.
srdtrk marked this conversation as resolved.
Show resolved Hide resolved
// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time.
type PacketDataProvider interface {
// GetCustomPacketData returns the packet data held on behalf of another application.
// The name the information is stored under should be provided as the key
// If no custom packet data exists for the key, nil is returned.
GetCustomPacketData(key string) interface{}
srdtrk marked this conversation as resolved.
Show resolved Hide resolved
}