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

x/ibc-transfer: ADR001 source tracing implementation #6871

Merged
merged 54 commits into from
Aug 14, 2020
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
44e4bc9
x/ibc-transfer: ADR001 source tracing implementation
fedekunze Jul 28, 2020
2c0529f
gRPC proto file
fedekunze Jul 28, 2020
dac7de3
validation
fedekunze Jul 28, 2020
f20b583
fix validation
fedekunze Jul 28, 2020
6b2bb81
Merge branch 'master' of github.com:cosmos/cosmos-sdk into fedekunze/…
fedekunze Jul 29, 2020
fa9efa5
import export genesis
fedekunze Jul 29, 2020
1e9d878
relay.go updates
fedekunze Jul 29, 2020
7ef0f14
gRPC service methods
fedekunze Jul 29, 2020
41af314
client CLI
fedekunze Jul 29, 2020
02fcd77
fix conflicts
fedekunze Aug 3, 2020
1c1273d
update implementation
fedekunze Aug 3, 2020
b42f1a8
build
fedekunze Aug 3, 2020
9fb1c95
trace test
fedekunze Aug 3, 2020
c877d39
fix CLI tx args
fedekunze Aug 3, 2020
76552c8
genesis import/export tests
fedekunze Aug 3, 2020
f5bf4b1
update comments
fedekunze Aug 3, 2020
1611cc0
update proto files
fedekunze Aug 3, 2020
441fe73
GRPC tests
fedekunze Aug 3, 2020
3e7935f
remove field from packet
fedekunze Aug 3, 2020
54788a4
fix coin validation bug
fedekunze Aug 3, 2020
0d9aea5
more validations
fedekunze Aug 4, 2020
0d6f408
update comments
fedekunze Aug 4, 2020
69b5982
minor refactor
fedekunze Aug 4, 2020
61c5434
update relay.go
fedekunze Aug 4, 2020
86e3603
Merge branch 'master' of github.com:cosmos/cosmos-sdk into fedekunze/…
fedekunze Aug 4, 2020
a86a15c
try fix test
fedekunze Aug 4, 2020
7a86bf8
merge master
fedekunze Aug 10, 2020
22469aa
Merge branch 'master' of github.com:cosmos/cosmos-sdk into fedekunze/…
fedekunze Aug 10, 2020
632c00e
minor updates
fedekunze Aug 10, 2020
db04cb5
fix tests
fedekunze Aug 11, 2020
014e08d
fix test
fedekunze Aug 11, 2020
229c856
Merge branch 'master' into fedekunze/adr-001-implementation
fedekunze Aug 11, 2020
0eabe48
ADR updates and comments
fedekunze Aug 11, 2020
116bf64
Merge branch 'fedekunze/adr-001-implementation' of github.com:cosmos/…
fedekunze Aug 11, 2020
d3a519d
Merge branch 'master' of github.com:cosmos/cosmos-sdk into fedekunze/…
fedekunze Aug 11, 2020
6570d75
merge master
fedekunze Aug 12, 2020
f623f19
merge master
fedekunze Aug 14, 2020
050fc63
build
fedekunze Aug 14, 2020
40a8f34
Apply suggestions from code review
fedekunze Aug 14, 2020
6914e05
address a few comments from review
fedekunze Aug 14, 2020
9839d96
Merge branch 'fedekunze/adr-001-implementation' of github.com:cosmos/…
fedekunze Aug 14, 2020
d9e8fed
Merge branch 'master' of github.com:cosmos/cosmos-sdk into fedekunze/…
fedekunze Aug 14, 2020
372d530
gRPC annotations
fedekunze Aug 14, 2020
9181de3
update proto files
fedekunze Aug 14, 2020
c527795
Apply suggestions from code review
fedekunze Aug 14, 2020
8e26f54
address comments
fedekunze Aug 14, 2020
7fb9871
fix conflicts
fedekunze Aug 14, 2020
1c00f9d
docs and changelog
fedekunze Aug 14, 2020
9429f90
sort traces
fedekunze Aug 14, 2020
1aedd3a
final changes to ADR
fedekunze Aug 14, 2020
cd19a79
client support for full path denom prefixes
fedekunze Aug 14, 2020
ceab722
address @AdityaSripal comments
fedekunze Aug 14, 2020
7723ca2
address TODO
fedekunze Aug 14, 2020
eb7e988
increase test timeouts
fedekunze Aug 14, 2020
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
20 changes: 7 additions & 13 deletions docs/architecture/adr-001-coin-source-tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,16 @@ func (msg MsgTransfer) ValidateBasic() error {
if err := msg.Trace.Validate(); err != nil {
return err
}
if err := ValidateIBCDenom(msg.Token.Denom, msg.Trace); err != nil {
if err := ValidatePrefixedDenom(msg.Token.Denom, msg.Trace); err != nil {
return err
}
// ...
}
```

```golang
// ValidateIBCDenom checks that the denomination for an IBC fungible token is valid. It returns error if the trace `hash` is invalid
func ValidateIBCDenom(denom string, trace DenomTrace) error {
// ValidatePrefixedDenom checks that the denomination for an IBC fungible token is valid. It returns error if the trace `hash` is invalid
func ValidatePrefixedDenom(denom string, trace DenomTrace) error {
// Validate that base denominations are equal if the trace info is not provided
if trace.Trace == "" {
if trace.BaseDenom != denom {
Expand Down Expand Up @@ -277,19 +277,13 @@ The denomination trace info only needs to be updated when token is received:
// NOTE: We use SourcePort and SourceChannel here, because the counterparty
// chain would have prefixed with DestPort and DestChannel when originally
// receiving this coin as seen in the "sender chain is the source" condition.
voucherPrefix := GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel())

if ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) {
// sender chain is not the source, unescrow tokens

// remove prefix added by sender chain
if err := denomTrace.RemovePrefix(); err != nil {
return err
}

// NOTE: since the sender is a sink chain, we already know the unprefixed denomination trace info

token := sdk.NewCoin(denomTrace.IBCDenom(), sdk.NewIntFromUint64(data.Amount))
voucherPrefix := types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel())
unprefixedDenom := data.Denom[len(voucherPrefix):]
token := sdk.NewCoin(unprefixedDenom, sdk.NewIntFromUint64(data.Amount))

// unescrow tokens
escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel())
Expand All @@ -299,7 +293,7 @@ if ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data
// sender chain is the source, mint vouchers

// construct the denomination trace from the full raw denomination
denomTrace := NewDenomTraceFromRawDenom(data.Denom)
denomTrace := NewDenomTrace(data.Denom)

// since SendPacket did not prefix the denomination with the voucherPrefix, we must add it here
denomTrace.AddPrefix(packet.GetDestPort(), packet.GetDestChannel())
Expand Down
19 changes: 12 additions & 7 deletions proto/ibc/transfer/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ package ibc.transfer;
option go_package = "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types";

import "gogoproto/gogo.proto";
import "ibc/transfer/transfer.proto";

// GenesisState is currently only used to ensure that the InitGenesis gets run
// by the module manager
message GenesisState {
string port_id = 1 [
(gogoproto.customname) = "PortID",
(gogoproto.moretags) = "yaml:\"port_id\""
];
// GenesisState defines the ibc-transfer genesis state
message GenesisState{
string port_id = 1 [
(gogoproto.customname) = "PortID",
(gogoproto.moretags) = "yaml:\"port_id\""
];
repeated DenomTrace denom_traces = 2 [
(gogoproto.castrepeated) = "Traces",
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"denom_traces\""
];
}
45 changes: 45 additions & 0 deletions proto/ibc/transfer/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
syntax = "proto3";
package ibc.transfer;

import "gogoproto/gogo.proto";
import "cosmos/query/pagination.proto";
import "ibc/transfer/transfer.proto";

option go_package = "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types";

// Query provides defines the gRPC querier service
service Query {
// DenomTrace queries a denomination trace info.
rpc DenomTrace(QueryDenomTraceRequest) returns (QueryDenomTraceResponse) {}

// DenomTraces queries all denomination traces.
rpc DenomTraces(QueryDenomTracesRequest) returns (QueryDenomTracesResponse) {}
}

// QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC method
message QueryDenomTraceRequest {
// hex hash of the denomination trace info
string hash = 1;
}

// QueryDenomTraceResponse is the response type for the Query/DenomTrace RPC method.
message QueryDenomTraceResponse {
// denomination trace associated with the request hash
DenomTrace denom_trace = 1;
}

// QueryConnectionsRequest is the request type for the Query/DenomTraces RPC method
message QueryDenomTracesRequest {
cosmos.query.PageRequest pagination = 1;
}

// QueryConnectionsResponse is the response type for the Query/DenomTraces RPC method.
message QueryDenomTracesResponse {
// list of stored connections of the chain.
repeated DenomTrace denom_traces = 1 [
(gogoproto.castrepeated) = "Traces",
(gogoproto.nullable) = false
];
// pagination response
cosmos.query.PageResponse pagination = 2;
}
15 changes: 11 additions & 4 deletions proto/ibc/transfer/transfer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@ message MsgTransfer {
// the tokens to be transferred
cosmos.Coin token = 3 [(gogoproto.nullable) = false];
// the sender address
bytes sender = 4
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
bytes sender = 4 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
// the recipient address on the destination chain
string receiver = 5;
// Timeout height relative to the current block height.
// The timeout is disabled when set to 0.
uint64 timeout_height = 6 [(gogoproto.moretags) = "yaml:\"timeout_height\""];
// Timeout timestamp (in nanoseconds) relative to the current block timestamp.
// The timeout is disabled when set to 0.
uint64 timeout_timestamp = 7
[(gogoproto.moretags) = "yaml:\"timeout_timestamp\""];
uint64 timeout_timestamp = 7 [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""];
}

// FungibleTokenPacketData defines a struct for the packet payload
Expand All @@ -52,3 +50,12 @@ message FungibleTokenPacketAcknowledgement {
bool success = 1;
string error = 2;
}

// DenomTrace contains the base denomination for ICS20 fungible tokens and the source tracing
// information
message DenomTrace {
// chain of port/channel identifiers used for tracing the source of the fungible token
string trace = 1;
// base denomination of the relayed fungible token
string base_denom = 2;
}
17 changes: 7 additions & 10 deletions types/coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ func validate(denom string, amount Int) error {

// IsValid returns true if the Coin has a non-negative amount and the denom is vaild.
func (coin Coin) IsValid() bool {
if err := validate(coin.Denom, coin.Amount); err != nil {
return false
}
return true
return validate(coin.Denom, coin.Amount) == nil
}

// IsZero returns if this represents no money
Expand Down Expand Up @@ -91,7 +88,7 @@ func (coin Coin) IsEqual(other Coin) bool {
return coin.Amount.Equal(other.Amount)
}

// Adds amounts of two coins with same denom. If the coins differ in denom then
// Add adds amounts of two coins with same denom. If the coins differ in denom then
// it panics.
func (coin Coin) Add(coinB Coin) Coin {
if coin.Denom != coinB.Denom {
Expand All @@ -101,7 +98,7 @@ func (coin Coin) Add(coinB Coin) Coin {
return Coin{coin.Denom, coin.Amount.Add(coinB.Amount)}
}

// Subtracts amounts of two coins with same denom. If the coins differ in denom
// Sub subtracts amounts of two coins with same denom. If the coins differ in denom
// then it panics.
func (coin Coin) Sub(coinB Coin) Coin {
if coin.Denom != coinB.Denom {
Expand Down Expand Up @@ -201,7 +198,7 @@ func (coins Coins) IsValid() bool {

lowDenom := coins[0].Denom
for _, coin := range coins[1:] {
if strings.ToLower(coin.Denom) != coin.Denom {
if err := ValidateDenom(coin.Denom); err != nil {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
return false
}
if coin.Denom <= lowDenom {
Expand Down Expand Up @@ -463,7 +460,7 @@ func (coins Coins) Empty() bool {
return len(coins) == 0
}

// Returns the amount of a denom from coins
// AmountOf returns the amount of a denom from coins
func (coins Coins) AmountOf(denom string) Int {
mustValidateDenom(denom)

Expand Down Expand Up @@ -576,8 +573,8 @@ func (coins Coins) Sort() Coins {
// Parsing

var (
// Denominations can be 3 ~ 64 characters long.
reDnmString = `[a-z][a-z0-9/]{2,63}`
// Denominations can be 3 ~ 128 characters long.
reDnmString = `[a-z][a-zA-Z0-9/]{2,127}`
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
reAmt = `[[:digit:]]+`
reDecAmt = `[[:digit:]]*\.[[:digit:]]+`
reSpc = `[[:space:]]*`
Expand Down
12 changes: 9 additions & 3 deletions types/coin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ func TestCoinIsValid(t *testing.T) {
{Coin{"Atom", NewInt(1)}, false},
{Coin{"a", NewInt(1)}, false},
{Coin{"a very long coin denom", NewInt(1)}, false},
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
{Coin{"atOm", NewInt(1)}, false},
{Coin{"atOm", NewInt(1)}, true},
{Coin{"ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", NewInt(1)}, true},
{Coin{" ", NewInt(1)}, false},
}

Expand Down Expand Up @@ -403,6 +404,10 @@ func TestCoins(t *testing.T) {
mixedCase3 := Coins{
{"gAs", NewInt(1)},
}
multipleIBCDenoms := Coins{
{"ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", NewInt(1)},
{"ibc/876563AAAACF739EB061C67CDB5EDF2B7C9FD4AA9D876450CC21210807C2820A", NewInt(2)},
}
empty := NewCoins()
badSort1 := Coins{
{"tree", NewInt(1)},
Expand Down Expand Up @@ -433,8 +438,9 @@ func TestCoins(t *testing.T) {

assert.True(t, good.IsValid(), "Coins are valid")
assert.False(t, mixedCase1.IsValid(), "Coins denoms contain upper case characters")
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
assert.False(t, mixedCase2.IsValid(), "First Coins denoms contain upper case characters")
assert.False(t, mixedCase3.IsValid(), "Single denom in Coins contains upper case characters")
assert.True(t, mixedCase2.IsValid(), "First Coins denoms contain upper case characters")
assert.True(t, mixedCase3.IsValid(), "Single denom in Coins contains upper case characters")
assert.True(t, multipleIBCDenoms.IsValid(), "IBC denominations as per ADR001 should be valid")
assert.True(t, good.IsAllPositive(), "Expected coins to be positive: %v", good)
assert.False(t, empty.IsAllPositive(), "Expected coins to not be positive: %v", empty)
assert.True(t, good.IsAllGTE(empty), "Expected %v to be >= %v", good, empty)
Expand Down
17 changes: 17 additions & 0 deletions x/ibc-transfer/client/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ import (
"github.com/cosmos/cosmos-sdk/client"
)

// GetQueryCmd returns the query commands for IBC connections
func GetQueryCmd() *cobra.Command {
queryCmd := &cobra.Command{
Use: "ibc-transfer",
Short: "IBC fungible token transfer query subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
}

queryCmd.AddCommand(
GetCmdQueryDenomTrace(),
GetCmdQueryDenomTraces(),
)

return queryCmd
}

// NewTxCmd returns the transaction commands for IBC fungible token transfer
func NewTxCmd() *cobra.Command {
txCmd := &cobra.Command{
Expand Down
87 changes: 87 additions & 0 deletions x/ibc-transfer/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package cli

import (
"context"
"fmt"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/types"
)

// GetCmdQueryDenomTraces defines the command to query all the denomination trace infos
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
// that this chain mantains.
func GetCmdQueryDenomTraces() *cobra.Command {
cmd := &cobra.Command{
Use: "denom-traces",
Short: "Query the trace info for all token denominations",
Long: "Query the trace info for all token denominations",
Example: fmt.Sprintf("%s query ibc-transfer denom-traces", version.AppName),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadQueryCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

req := &types.QueryDenomTracesRequest{
Pagination: pageReq,
}

res, err := queryClient.DenomTraces(context.Background(), req)
if err != nil {
return err
}

return clientCtx.PrintOutput(res)
},
}
flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "denominations trace")
fedekunze marked this conversation as resolved.
Show resolved Hide resolved

return cmd
}

// GetCmdQueryDenomTrace defines the command to query a a denomination trace from a given hash.
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
func GetCmdQueryDenomTrace() *cobra.Command {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
cmd := &cobra.Command{
Use: "denom-trace [hash]",
Short: "Query the denom trace info from a given trace hash",
Long: "Query the denom trace info from a given trace hash",
Example: fmt.Sprintf("%s query ibc-transfer denom-trace [hash]", version.AppName),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadQueryCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

req := &types.QueryDenomTraceRequest{
Hash: args[0],
}

res, err := queryClient.DenomTrace(context.Background(), req)
if err != nil {
return err
}

return clientCtx.PrintOutput(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
return cmd
}
2 changes: 1 addition & 1 deletion x/ibc-transfer/client/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func RegisterRoutes(clientCtx client.Context, r *mux.Router) {
// TransferTxReq defines the properties of a transfer tx request's body.
type TransferTxReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Amount sdk.Coin `json:"amount" yaml:"amount"`
Token sdk.Coin `json:"token" yaml:"token"`
Receiver string `json:"receiver" yaml:"receiver"`
TimeoutHeight uint64 `json:"timeout_height" yaml:"timeout_height"`
TimeoutTimestamp uint64 `json:"timeout_timestamp" yaml:"timeout_timestamp"`
Expand Down
2 changes: 1 addition & 1 deletion x/ibc-transfer/client/rest/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func transferHandlerFn(clientCtx client.Context) http.HandlerFunc {
msg := types.NewMsgTransfer(
portID,
channelID,
req.Amount,
req.Token,
fromAddr,
req.Receiver,
req.TimeoutHeight,
Expand Down
Loading