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 50 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,10 @@ Buffers for state serialization instead of Amino.
### Improvements
* (x/ibc-transfer) [\#6871](https://github.com/cosmos/cosmos-sdk/pull/6871) Implement [ADR 001 - Coin Source Tracing](./docs/architecture/adr-001-coin-source-tracing.md).
* (types) [\#7027](https://github.com/cosmos/cosmos-sdk/pull/7027) `Coin(s)` and `DecCoin(s)` updates:
* Bump denomination max length to 128
* Allow unicode letters and numbers in denominations
* Allow uppercase letters and numbers in denominations to support [ADR 001](./docs/architecture/adr-001-coin-source-tracing.md)
* Added `Validate` function that returns a descriptive error
* (baseapp) [\#6186](https://github.com/cosmos/cosmos-sdk/issues/6186) Support emitting events during `AnteHandler` execution.
* (x/auth) [\#5702](https://github.com/cosmos/cosmos-sdk/pull/5702) Add parameter querying support for `x/auth`.
Expand Down
160 changes: 79 additions & 81 deletions docs/architecture/adr-001-coin-source-tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
## Changelog

- 2020-07-09: Initial Draft
- 2020-08-11: Implementation changes

## Status

Proposed
Accepted, Implemented

## Context

Expand Down Expand Up @@ -114,7 +115,7 @@ trace the token back to the originating chain, as specified on ICS20.
The new proposed format will be the following:

```golang
ibcDenom = "ibc/" + hash(trace + "/" + base denom)
ibcDenom = "ibc/" + hash(trace path + "/" + base denom)
```

The hash function will be a SHA256 hash of the fields of the `DenomTrace`:
Expand All @@ -124,7 +125,7 @@ The hash function will be a SHA256 hash of the fields of the `DenomTrace`:
// information
message DenomTrace {
// chain of port/channel identifiers used for tracing the source of the fungible token
string trace = 1;
string path = 1;
// base denomination of the relayed fungible token
string base_denom = 2;
}
Expand All @@ -133,50 +134,23 @@ message DenomTrace {
The `IBCDenom` function constructs the `Coin` denomination used when creating the ICS20 fungible token packet data:

```golang
// Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields.
// Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields using the following formula:
//
// hash = sha256(tracePath + "/" + baseDenom)
func (dt DenomTrace) Hash() tmbytes.HexBytes {
return tmhash.Sum(dt.Trace + "/" + dt.BaseDenom)
return tmhash.Sum(dt.Path + "/" + dt.BaseDenom)
}

// IBCDenom a coin denomination for an ICS20 fungible token in the format 'ibc/{hash(trace + baseDenom)}'. If the trace is empty, it will return the base denomination.
// IBCDenom a coin denomination for an ICS20 fungible token in the format 'ibc/{hash(tracePath + baseDenom)}'.
// If the trace is empty, it will return the base denomination.
func (dt DenomTrace) IBCDenom() string {
if dt.Trace != "" {
if dt.Path != "" {
return fmt.Sprintf("ibc/%s", dt.Hash())
}
return dt.BaseDenom
}
```

In order to trim the denomination trace prefix when sending/receiving fungible tokens, the `RemovePrefix` function is provided.

> NOTE: the prefix addition must be done on the client side.
```golang
// RemovePrefix trims the first portID/channelID pair from the trace info. If the trace is already empty it will perform a no-op. If the trace is incorrectly constructed or doesn't have separators it will return an error.
func (dt *DenomTrace) RemovePrefix() error {
if dt.Trace == "" {
return nil
}

traceSplit := strings.SplitN(dt.Trace, "/", 3)

var err error
switch {
// NOTE: other cases are checked during msg validation
case len(traceSplit) == 2:
dt.Trace = ""
case len(traceSplit) == 3:
dt.Trace = traceSplit[2]
}

if err != nil {
return err
}

return nil
}
```

### `x/ibc-transfer` Changes

In order to retrieve the trace information from an IBC denomination, a lookup table needs to be
Expand Down Expand Up @@ -217,44 +191,30 @@ hash, if the trace info is provided, or that the base denominations matches:
```golang
func (msg MsgTransfer) ValidateBasic() error {
// ...
if err := msg.Trace.Validate(); err != nil {
return err
}
if err := ValidateIBCDenom(msg.Token.Denom, msg.Trace); err != nil {
return err
}
// ...
return ValidateIBCDenom(msg.Token.Denom)
}
```

```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 {
// Validate that base denominations are equal if the trace info is not provided
if trace.Trace == "" {
if trace.BaseDenom != denom {
return Wrapf(
ErrInvalidDenomForTransfer,
"token denom must match the trace base denom (%s%s)",
denom, trace.BaseDenom,
)
}
return nil
}

// ValidateIBCDenom validates that the given denomination is either:
//
// - A valid base denomination (eg: 'uatom')
// - A valid fungible token representation (i.e 'ibc/{hash}') per ADR 001 https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-001-coin-source-tracing.md
func ValidateIBCDenom(denom string) error {
denomSplit := strings.SplitN(denom, "/", 2)

switch {
case denomSplit[0] != "ibc":
return Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom)
case len(denomSplit) == 2:
return tmtypes.ValidateHash([]byte(denomSplit[1]))
case strings.TrimSpace(denom) == "",
len(denomSplit) == 1 && denomSplit[0] == "ibc",
len(denomSplit) == 2 && (denomSplit[0] != "ibc" || strings.TrimSpace(denomSplit[1]) == ""):
return sdkerrors.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom)

case denomSplit[0] == denom && strings.TrimSpace(denom) != "":
return sdk.ValidateDenom(denom)
}

denomTraceHash := denomSplit[1]
traceHash := trace.Hash()
if !bytes.Equal(traceHash.Bytes(), denomTraceHash.Bytes()) {
return Errorf("token denomination trace hash mismatch, expected %s got %s", traceHash, denomTraceHash)
if _, err := ParseHexHash(denomSplit[1]); err != nil {
return Wrapf(err, "invalid denom trace hash %s", denomSplit[1])
}

return nil
Expand All @@ -266,6 +226,46 @@ The denomination trace info only needs to be updated when token is received:
- Receiver is **source** chain: The receiver created the token and must have the trace lookup already stored (if necessary _ie_ native token case wouldn't need a lookup).
- Receiver is **not source** chain: Store the received info. For example, during step 1, when chain `B` receives `transfer/channelToA/denom`.

```golang
// SendTransfer
// ...

fullDenomPath := token.Denom

// deconstruct the token denomination into the denomination trace info
// to determine if the sender is the source chain
if strings.HasPrefix(token.Denom, "ibc/") {
fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom)
if err != nil {
return err
}
}

if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) {
//...
```
```golang
// DenomPathFromHash returns the full denomination path prefix from an ibc denom with a hash
// component.
func (k Keeper) DenomPathFromHash(ctx sdk.Context, denom string) (string, error) {
hexHash := denom[4:]
hash, err := ParseHexHash(hexHash)
if err != nil {
return "", Wrap(ErrInvalidDenomForTransfer, err.Error())
}

denomTrace, found := k.GetDenomTrace(ctx, hash)
if !found {
return "", Wrap(ErrTraceNotFound, hexHash)
}

fullDenomPath := denomTrace.GetFullDenomPath()
return fullDenomPath, nil
}
```
```golang
// OnRecvPacket
// ...
Expand All @@ -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 @@ -298,19 +292,22 @@ 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)
// since SendPacket did not prefix the denomination, we must prefix denomination here
sourcePrefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel())
// NOTE: sourcePrefix contains the trailing "/"
prefixedDenom := sourcePrefix + data.Denom

// since SendPacket did not prefix the denomination with the voucherPrefix, we must add it here
denomTrace.AddPrefix(packet.GetDestPort(), packet.GetDestChannel())
// construct the denomination trace from the full raw denomination
denomTrace := types.ParseDenomTrace(prefixedDenom)

// set the value to the lookup table if not stored already
traceHash := denomTrace.Hash()
if !k.HasDenomTrace(ctx, traceHash) {
k.SetDenomTrace(ctx, traceHash, denomTrace)
}

voucher := sdk.NewCoin(denomTrace.IBCDenom(), sdk.NewIntFromUint64(data.Amount))
voucherDenom := denomTrace.IBCDenom()
voucher := sdk.NewCoin(voucherDenom, sdk.NewIntFromUint64(data.Amount))

// mint new tokens if the source of the transfer is the same chain
if err := k.bankKeeper.MintCoins(
Expand Down Expand Up @@ -347,7 +344,8 @@ The coin denomination validation will need to be updated to reflect these change
function will now:
- Accept slash separators (`"/"`) and uppercase characters (due to the `HexBytes` format)
- Bump the maximum character length to 64
- Bump the maximum character length to 128, as the hex representation used by Tendermint's
`HexBytes` type contains 64 characters.
Additional validation logic, such as verifying the length of the hash, the may be added to the bank module in the future if the [custom base denomination validation](https://github.com/cosmos/cosmos-sdk/pull/6755) is integrated into the SDK.
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/adr-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
## Status

> A decision may be "proposed" if the project stakeholders haven't agreed with it yet, or "accepted" once it is agreed. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement.
> {Deprecated|Proposed|Accepted}
> {Deprecated|Proposed|Accepted} {Implemented|Not Implemented}
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
## Context

Expand Down
17 changes: 11 additions & 6 deletions proto/ibc/transfer/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ 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.moretags) = "yaml:\"port_id\""
];
// GenesisState defines the ibc-transfer genesis state
message GenesisState{
string port_id = 1 [
(gogoproto.moretags) = "yaml:\"port_id\""
];
repeated DenomTrace denom_traces = 2 [
(gogoproto.castrepeated) = "Traces",
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"denom_traces\""
];
}
51 changes: 51 additions & 0 deletions proto/ibc/transfer/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
syntax = "proto3";
package ibc.transfer;

import "gogoproto/gogo.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "ibc/transfer/transfer.proto";
import "google/api/annotations.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 information.
rpc DenomTrace(QueryDenomTraceRequest) returns (QueryDenomTraceResponse) {
option (google.api.http).get = "/ibc_transfer/v1beta1/denom_traces/{hash}";
}

// DenomTraces queries all denomination traces.
rpc DenomTraces(QueryDenomTracesRequest) returns (QueryDenomTracesResponse) {
option (google.api.http).get = "/ibc_transfer/v1beta1/denom_traces";
}
}

// QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC method
message QueryDenomTraceRequest {
// hash (in hex format) of the denomination trace information.
string hash = 1;
}

// QueryDenomTraceResponse is the response type for the Query/DenomTrace RPC method.
message QueryDenomTraceResponse {
// denom_trace returns the requested denomination trace information.
DenomTrace denom_trace = 1;
}

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

// QueryConnectionsResponse is the response type for the Query/DenomTraces RPC method.
message QueryDenomTracesResponse {
// denom_traces returns all denominations trace information.
repeated DenomTrace denom_traces = 1 [
(gogoproto.castrepeated) = "Traces",
(gogoproto.nullable) = false
];
// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
16 changes: 12 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.base.v1beta1.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,13 @@ message FungibleTokenPacketAcknowledgement {
bool success = 1;
string error = 2;
}

// DenomTrace contains the base denomination for ICS20 fungible tokens and the source tracing
// information path.
message DenomTrace {
// path defines the chain of port/channel identifiers used for tracing the source of the fungible
// token.
string path = 1;
// base denomination of the relayed fungible token.
string base_denom = 2;
}
1 change: 1 addition & 0 deletions types/coin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func TestCoinIsValid(t *testing.T) {
{Coin{"ATOM", OneInt()}, true},
{Coin{"a", OneInt()}, false},
{Coin{loremIpsum, OneInt()}, false},
{Coin{"ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", OneInt()}, true},
{Coin{"atOm", OneInt()}, true},
{Coin{" ", OneInt()}, false},
}
Expand Down
Loading