Skip to content

Commit ef9c475

Browse files
mergify[bot]charleenfeicrodriguezvega
authored
chore: denom traces migration handler (backport #1680) (#1754)
* chore: denom traces migration handler (#1680) * update code & test * register migrator service (cherry picked from commit be5ccf3) # Conflicts: # CHANGELOG.md # docs/migrations/support-denoms-with-slashes.md # docs/migrations/v3-to-v4.md # modules/apps/transfer/types/trace.go * fix conflicts * fix go version package * put back entry that got removed by mistake Co-authored-by: Charly <charly@interchain.berlin> Co-authored-by: crodriguezvega <carlos@interchain.io>
1 parent d85319b commit ef9c475

File tree

7 files changed

+246
-63
lines changed

7 files changed

+246
-63
lines changed

CHANGELOG.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
4747

4848
### Improvements
4949

50-
* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal.
51-
50+
* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal.
51+
* (app/20-transfer) [\#1680](https://github.com/cosmos/ibc-go/pull/1680) Adds migration to correct any malformed trace path information of tokens with denoms that contains slashes. The transfer module consensus version has been bumped to 2.
52+
* (app/20-transfer) [\#1730](https://github.com/cosmos/ibc-go/pull/1730) parse the ics20 denomination provided via a packet using the channel identifier format specified by ibc-go.
53+
5254
### Features
5355

5456
### Bug Fixes

docs/migrations/support-denoms-with-slashes.md

+4-25
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ There are four sections based on the four potential user groups of this document
99
- Relayers
1010
- IBC Light Clients
1111

12-
This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.1.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do *NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading.
12+
This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading.
1313

1414
If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect.
1515

@@ -28,37 +28,16 @@ The transfer module will now support slashes in base denoms, so we must iterate
2828
### Upgrade Proposal
2929

3030
```go
31-
// Here the upgrade name is the upgrade name set by the chain
32-
app.UpgradeKeeper.SetUpgradeHandler("supportSlashedDenomsUpgrade",
31+
app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces",
3332
func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
34-
// list of traces that must replace the old traces in store
35-
var newTraces []ibctransfertypes.DenomTrace
36-
app.TransferKeeper.IterateDenomTraces(ctx,
37-
func(dt ibctransfertypes.DenomTrace) bool {
38-
// check if the new way of splitting FullDenom
39-
// into Trace and BaseDenom passes validation and
40-
// is the same as the current DenomTrace.
41-
// If it isn't then store the new DenomTrace in the list of new traces.
42-
newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath())
43-
if err := newTrace.Validate(); err == nil && !reflect.DeepEqual(newTrace, dt) {
44-
newTraces = append(newTraces, newTrace)
45-
}
46-
47-
return false
48-
})
49-
50-
// replace the outdated traces with the new trace information
51-
for _, nt := range newTraces {
52-
app.TransferKeeper.SetDenomTrace(ctx, nt)
53-
}
54-
33+
// transfer module consensus version has been bumped to 2
5534
return app.mm.RunMigrations(ctx, app.configurator, fromVM)
5635
})
5736
```
5837

5938
This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety.
6039

61-
For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1527).
40+
For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680).
6241

6342
### Genesis Migration
6443

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package keeper
2+
3+
import (
4+
"fmt"
5+
6+
sdk "github.com/cosmos/cosmos-sdk/types"
7+
8+
"github.com/cosmos/ibc-go/v3/modules/apps/transfer/types"
9+
)
10+
11+
// Migrator is a struct for handling in-place store migrations.
12+
type Migrator struct {
13+
keeper Keeper
14+
}
15+
16+
// NewMigrator returns a new Migrator.
17+
func NewMigrator(keeper Keeper) Migrator {
18+
return Migrator{keeper: keeper}
19+
}
20+
21+
// MigrateTraces migrates the DenomTraces to the correct format, accounting for slashes in the BaseDenom.
22+
func (m Migrator) MigrateTraces(ctx sdk.Context) error {
23+
24+
// list of traces that must replace the old traces in store
25+
var newTraces []types.DenomTrace
26+
m.keeper.IterateDenomTraces(ctx,
27+
func(dt types.DenomTrace) (stop bool) {
28+
// check if the new way of splitting FullDenom
29+
// is the same as the current DenomTrace.
30+
// If it isn't then store the new DenomTrace in the list of new traces.
31+
newTrace := types.ParseDenomTrace(dt.GetFullDenomPath())
32+
err := newTrace.Validate()
33+
if err != nil {
34+
panic(err)
35+
}
36+
37+
if dt.IBCDenom() != newTrace.IBCDenom() {
38+
// The new form of parsing will result in a token denomination change.
39+
// A bank migration is required. A panic should occur to prevent the
40+
// chain from using corrupted state.
41+
panic(fmt.Sprintf("migration will result in corrupted state. Previous IBC token (%s) requires a bank migration. Expected denom trace (%s)", dt, newTrace))
42+
}
43+
44+
if !equalTraces(newTrace, dt) {
45+
newTraces = append(newTraces, newTrace)
46+
}
47+
return false
48+
})
49+
50+
// replace the outdated traces with the new trace information
51+
for _, nt := range newTraces {
52+
m.keeper.SetDenomTrace(ctx, nt)
53+
}
54+
return nil
55+
}
56+
57+
func equalTraces(dtA, dtB types.DenomTrace) bool {
58+
return dtA.BaseDenom == dtB.BaseDenom && dtA.Path == dtB.Path
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package keeper_test
2+
3+
import (
4+
"fmt"
5+
6+
transferkeeper "github.com/cosmos/ibc-go/v3/modules/apps/transfer/keeper"
7+
transfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types"
8+
)
9+
10+
func (suite *KeeperTestSuite) TestMigratorMigrateTraces() {
11+
12+
testCases := []struct {
13+
msg string
14+
malleate func()
15+
expectedTraces transfertypes.Traces
16+
}{
17+
18+
{
19+
"success: two slashes in base denom",
20+
func() {
21+
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(
22+
suite.chainA.GetContext(),
23+
transfertypes.DenomTrace{
24+
BaseDenom: "pool/1", Path: "transfer/channel-0/gamm",
25+
})
26+
},
27+
transfertypes.Traces{
28+
{
29+
BaseDenom: "gamm/pool/1", Path: "transfer/channel-0",
30+
},
31+
},
32+
},
33+
{
34+
"success: one slash in base denom",
35+
func() {
36+
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(
37+
suite.chainA.GetContext(),
38+
transfertypes.DenomTrace{
39+
BaseDenom: "0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", Path: "transfer/channel-149/erc",
40+
})
41+
},
42+
transfertypes.Traces{
43+
{
44+
BaseDenom: "erc/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", Path: "transfer/channel-149",
45+
},
46+
},
47+
},
48+
{
49+
"success: multiple slashes in a row in base denom",
50+
func() {
51+
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(
52+
suite.chainA.GetContext(),
53+
transfertypes.DenomTrace{
54+
BaseDenom: "1", Path: "transfer/channel-5/gamm//pool",
55+
})
56+
},
57+
transfertypes.Traces{
58+
{
59+
BaseDenom: "gamm//pool/1", Path: "transfer/channel-5",
60+
},
61+
},
62+
},
63+
{
64+
"success: multihop base denom",
65+
func() {
66+
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(
67+
suite.chainA.GetContext(),
68+
transfertypes.DenomTrace{
69+
BaseDenom: "transfer/channel-1/uatom", Path: "transfer/channel-0",
70+
})
71+
},
72+
transfertypes.Traces{
73+
{
74+
BaseDenom: "uatom", Path: "transfer/channel-0/transfer/channel-1",
75+
},
76+
},
77+
},
78+
{
79+
"success: non-standard port",
80+
func() {
81+
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(
82+
suite.chainA.GetContext(),
83+
transfertypes.DenomTrace{
84+
BaseDenom: "customport/channel-7/uatom", Path: "transfer/channel-0/transfer/channel-1",
85+
})
86+
},
87+
transfertypes.Traces{
88+
{
89+
BaseDenom: "uatom", Path: "transfer/channel-0/transfer/channel-1/customport/channel-7",
90+
},
91+
},
92+
},
93+
}
94+
95+
for _, tc := range testCases {
96+
suite.Run(fmt.Sprintf("case %s", tc.msg), func() {
97+
suite.SetupTest() // reset
98+
99+
tc.malleate() // explicitly set up denom traces
100+
101+
migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper)
102+
err := migrator.MigrateTraces(suite.chainA.GetContext())
103+
suite.Require().NoError(err)
104+
105+
traces := suite.chainA.GetSimApp().TransferKeeper.GetAllDenomTraces(suite.chainA.GetContext())
106+
suite.Require().Equal(tc.expectedTraces, traces)
107+
})
108+
}
109+
}
110+
111+
func (suite *KeeperTestSuite) TestMigratorMigrateTracesCorruptionDetection() {
112+
// IBCDenom() previously would return "customport/channel-0/uatom", but now should return ibc/{hash}
113+
corruptedDenomTrace := transfertypes.DenomTrace{
114+
BaseDenom: "customport/channel-0/uatom",
115+
Path: "",
116+
}
117+
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), corruptedDenomTrace)
118+
119+
migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper)
120+
suite.Panics(func() {
121+
migrator.MigrateTraces(suite.chainA.GetContext())
122+
})
123+
}

modules/apps/transfer/module.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier {
120120
func (am AppModule) RegisterServices(cfg module.Configurator) {
121121
types.RegisterMsgServer(cfg.MsgServer(), am.keeper)
122122
types.RegisterQueryServer(cfg.QueryServer(), am.keeper)
123+
124+
m := keeper.NewMigrator(am.keeper)
125+
if err := cfg.RegisterMigration(types.ModuleName, 1, m.MigrateTraces); err != nil {
126+
panic(fmt.Sprintf("failed to migrate transfer app from version 1 to 2: %v", err))
127+
}
123128
}
124129

125130
// InitGenesis performs genesis initialization for the ibc-transfer module. It returns
@@ -139,7 +144,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw
139144
}
140145

141146
// ConsensusVersion implements AppModule/ConsensusVersion.
142-
func (AppModule) ConsensusVersion() uint64 { return 1 }
147+
func (AppModule) ConsensusVersion() uint64 { return 2 }
143148

144149
// BeginBlock implements the AppModule interface
145150
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {

modules/apps/transfer/types/trace.go

+20-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
tmbytes "github.com/tendermint/tendermint/libs/bytes"
1313
tmtypes "github.com/tendermint/tendermint/types"
1414

15+
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
1516
host "github.com/cosmos/ibc-go/v3/modules/core/24-host"
1617
)
1718

@@ -20,9 +21,9 @@ import (
2021
//
2122
// Examples:
2223
//
23-
// - "transfer/channelidone/uatom" => DenomTrace{Path: "transfer/channelidone", BaseDenom: "uatom"}
24-
// - "transfer/channelidone/transfer/channelidtwo/uatom" => DenomTrace{Path: "transfer/channelidone/transfer/channelidtwo", BaseDenom: "uatom"}
25-
// - "transfer/channelidone/gamm/pool/1" => DenomTrace{Path: "transfer/channelidone", BaseDenom: "gamm/pool/1"}
24+
// - "portidone/channel-0/uatom" => DenomTrace{Path: "portidone/channel-0", BaseDenom: "uatom"}
25+
// - "portidone/channel-0/portidtwo/channel-1/uatom" => DenomTrace{Path: "portidone/channel-0/portidtwo/channel-1", BaseDenom: "uatom"}
26+
// - "portidone/channel-0/gamm/pool/1" => DenomTrace{Path: "portidone/channel-0", BaseDenom: "gamm/pool/1"}
2627
// - "gamm/pool/1" => DenomTrace{Path: "", BaseDenom: "gamm/pool/1"}
2728
// - "uatom" => DenomTrace{Path: "", BaseDenom: "uatom"}
2829
func ParseDenomTrace(rawDenom string) DenomTrace {
@@ -77,11 +78,23 @@ func (dt DenomTrace) GetFullDenomPath() string {
7778
// extractPathAndBaseFromFullDenom returns the trace path and the base denom from
7879
// the elements that constitute the complete denom.
7980
func extractPathAndBaseFromFullDenom(fullDenomItems []string) (string, string) {
80-
var path []string
81-
var baseDenom []string
81+
var (
82+
path []string
83+
baseDenom []string
84+
)
85+
8286
length := len(fullDenomItems)
8387
for i := 0; i < length; i = i + 2 {
84-
if i < length-1 && length > 2 && fullDenomItems[i] == PortID {
88+
// The IBC specification does not guarentee the expected format of the
89+
// destination port or destination channel identifier. A short term solution
90+
// to determine base denomination is to expect the channel identifier to be the
91+
// one ibc-go specifies. A longer term solution is to separate the path and base
92+
// denomination in the ICS20 packet. If an intermediate hop prefixes the full denom
93+
// with a channel identifier format different from our own, the base denomination
94+
// will be incorrectly parsed, but the token will continue to be treated correctly
95+
// as an IBC denomination. The hash used to store the token internally on our chain
96+
// will be the same value as the base denomination being correctly parsed.
97+
if i < length-1 && length > 2 && channeltypes.IsValidChannelID(fullDenomItems[i+1]) {
8598
path = append(path, fullDenomItems[i], fullDenomItems[i+1])
8699
} else {
87100
baseDenom = fullDenomItems[i:]
@@ -165,7 +178,7 @@ func (t Traces) Sort() Traces {
165178
// ValidatePrefixedDenom checks that the denomination for an IBC fungible token packet denom is correctly prefixed.
166179
// The function will return no error if the given string follows one of the two formats:
167180
//
168-
// - Prefixed denomination: 'transfer/{channelIDN}/.../transfer/{channelID0}/baseDenom'
181+
// - Prefixed denomination: '{portIDN}/{channelIDN}/.../{portID0}/{channelID0}/baseDenom'
169182
// - Unprefixed denomination: 'baseDenom'
170183
//
171184
// 'baseDenom' may or may not contain '/'s

0 commit comments

Comments
 (0)