-
Notifications
You must be signed in to change notification settings - Fork 625
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
chore: denom traces migration handler #1680
Changes from 24 commits
96f3c49
250e45f
bf3b96b
ed4a153
8018627
08e71e7
05f50c4
169ead2
7c46b84
27998dd
952b114
7a1d263
a5b3ff6
b33f4df
f673132
0b2f4c2
78d4616
3084982
b1cd41f
3c07902
6110232
87a97e6
ecbd10a
35ea75e
7660328
cd8c867
0f91661
9a44480
96aabb5
b608e89
b86cdd8
45fdc09
60785a6
38c93bc
515e0e7
a2fe4ae
d999103
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,52 @@ | ||
package keeper | ||
|
||
import ( | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
"github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" | ||
charleenfei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
// Migrator is a struct for handling in-place store migrations. | ||
type Migrator struct { | ||
keeper Keeper | ||
} | ||
|
||
// NewMigrator returns a new Migrator. | ||
func NewMigrator(keeper Keeper) Migrator { | ||
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. is the intention that all transfer related migrations will happen here? 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. yes -- the cosmos sdk has all migrations in a separate directory but imo that might be overkill for us. wdyt? 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 think for us it makes sense to have them here, we can worry about additional directories if we end up with a large number of migrations. |
||
return Migrator{keeper: keeper} | ||
} | ||
|
||
// MigrateTraces migrates the DenomTraces to the correct format, accounting for slashes in the BaseDenom. | ||
func (m Migrator) MigrateTraces(ctx sdk.Context) error { | ||
var iterErr error | ||
|
||
// list of traces that must replace the old traces in store | ||
var newTraces []types.DenomTrace | ||
m.keeper.IterateDenomTraces(ctx, | ||
func(dt types.DenomTrace) (stop bool) { | ||
// check if the new way of splitting FullDenom | ||
// into Trace and BaseDenom passes validation and | ||
// is the same as the current DenomTrace. | ||
// If it isn't then store the new DenomTrace in the list of new traces. | ||
newTrace := types.ParseDenomTrace(dt.GetFullDenomPath()) | ||
err := newTrace.Validate() | ||
if err != nil { | ||
iterErr = err | ||
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. Just wondering if there's anything we should do if this (unlikely) scenario happens: if there's a corrupt denom trace and that blocks the migration to complete, is there anything we can do or document to help chains? 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 it should just panic and then have the chain devs export a snapshot and manually address the situation? What do you think? 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 think this is extremely unlikely to happen unless the migration happens from an exported genesis which was manually corrupted -- i think it could make sense rather than panicking the err here to simply pass back to the UpgradeHandler and have the app devs deal with it how they want... does that 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. In which case, couldn't we return the error immediately? Instead of continuing to do unnecessary work... I'm assuming that the upgrade handler and 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 do stop the parsing and then return the error in the current code, is there another way to return the error that you think would make more 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. Ah sorry, I missed the |
||
return true | ||
} | ||
if !equalTraces(newTrace, dt) { | ||
newTraces = append(newTraces, newTrace) | ||
} | ||
return false | ||
}) | ||
|
||
// replace the outdated traces with the new trace information | ||
for _, nt := range newTraces { | ||
m.keeper.SetDenomTrace(ctx, nt) | ||
} | ||
return iterErr | ||
crodriguezvega marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
func equalTraces(dtA, dtB types.DenomTrace) bool { | ||
return dtA.BaseDenom == dtB.BaseDenom && dtA.Path == dtB.Path | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package keeper_test | ||
|
||
import ( | ||
"fmt" | ||
|
||
transferkeeper "github.com/cosmos/ibc-go/v4/modules/apps/transfer/keeper" | ||
transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" | ||
) | ||
|
||
func (suite *KeeperTestSuite) TestMigratorMigrateTraces() { | ||
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. 🥇 |
||
|
||
testCases := []struct { | ||
msg string | ||
malleate func() | ||
expectedTraces transfertypes.Traces | ||
}{ | ||
|
||
{ | ||
"success: two slashes in base denom", | ||
func() { | ||
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( | ||
suite.chainA.GetContext(), | ||
transfertypes.DenomTrace{ | ||
BaseDenom: "1", Path: "transfer/channel-0/gamm/pool", | ||
}) | ||
}, | ||
transfertypes.Traces{ | ||
{ | ||
BaseDenom: "gamm/pool/1", Path: "transfer/channel-0", | ||
}, | ||
}, | ||
}, | ||
{ | ||
"success: one slash in base denom", | ||
func() { | ||
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( | ||
suite.chainA.GetContext(), | ||
transfertypes.DenomTrace{ | ||
BaseDenom: "", Path: "transfer/channel-149/erc/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", | ||
}) | ||
}, | ||
transfertypes.Traces{ | ||
{ | ||
BaseDenom: "erc/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", Path: "transfer/channel-149", | ||
}, | ||
}, | ||
}, | ||
{ | ||
"success: multiple slashes in a row in base denom", | ||
func() { | ||
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( | ||
suite.chainA.GetContext(), | ||
transfertypes.DenomTrace{ | ||
BaseDenom: "1", Path: "transfer/channel-5/gamm//pool", | ||
}) | ||
}, | ||
transfertypes.Traces{ | ||
{ | ||
BaseDenom: "gamm//pool/1", Path: "transfer/channel-5", | ||
}, | ||
}, | ||
}, | ||
{ | ||
"success: multihop base denom", | ||
func() { | ||
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( | ||
suite.chainA.GetContext(), | ||
transfertypes.DenomTrace{ | ||
BaseDenom: "transfer/channel-1/uatom", Path: "transfer/channel-0", | ||
}) | ||
}, | ||
transfertypes.Traces{ | ||
{ | ||
BaseDenom: "uatom", Path: "transfer/channel-0/transfer/channel-1", | ||
}, | ||
}, | ||
}, | ||
{ | ||
// the expected value will change with the implementation of the fix for #1698 | ||
"no change: non-standard port", | ||
func() { | ||
suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( | ||
suite.chainA.GetContext(), | ||
transfertypes.DenomTrace{ | ||
BaseDenom: "customport/channel-7/uatom", Path: "transfer/channel-0/transfer/channel-1", | ||
}) | ||
}, | ||
transfertypes.Traces{ | ||
{ | ||
BaseDenom: "customport/channel-7/uatom", Path: "transfer/channel-0/transfer/channel-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. I know that this unlikely to happen in production, but should we add a test case for a denom trace that fails in the validation? If only, at least this will increase code coverage... :) 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. see below comment 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 agree this situation is unlikely. It can still be useful to add a test case to ensure there is an if statement checking that iterErr is non nil (I have seen those missing if statements go unnoticed before) |
||
|
||
for _, tc := range testCases { | ||
suite.Run(fmt.Sprintf("case %s", tc.msg), func() { | ||
suite.SetupTest() // reset | ||
|
||
tc.malleate() // explicitly set up denom traces | ||
|
||
migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper) | ||
err := migrator.MigrateTraces(suite.chainA.GetContext()) | ||
suite.Require().NoError(err) | ||
|
||
traces := suite.chainA.GetSimApp().TransferKeeper.GetAllDenomTraces(suite.chainA.GetContext()) | ||
suite.Require().Equal(tc.expectedTraces, traces) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,6 +71,9 @@ func (dt DenomTrace) GetFullDenomPath() string { | |
if dt.Path == "" { | ||
return dt.BaseDenom | ||
} | ||
if dt.BaseDenom == "" { | ||
return dt.Path | ||
} | ||
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. why was this added? I think in this situation, there should be a panic 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 added it bc in the case that the incorrectly parsed BaseDenom is an empty string for whatever reason, the 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 think we can cleanup a lot of this code when we look into refactoring transfer and improving upon it. For now, I think it'd be best to remove this check and ensure during migrations that the previously set denom trace passes the 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. but i dont think some of the previous set denom traces would pass the Validate() call... ie something like wouldnt this make the point of the migration code redundant if we are trying to 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. previous invalid denom traces aren't possible in the current code as they would fail on packet Validation. The purpose of the migration code is to fix the stored trace info, not validate the denomination. denomTrace.Validate() should pass for all existing denominations The specific example, "gamm/pool/1", should have failed due to port/channel identifier validation (insufficient length) when being received or sent in a packet 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. got it, that makes sense! |
||
return dt.GetPrefix() + dt.BaseDenom | ||
} | ||
|
||
|
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.
Where/how is
RunMigrations
defined? Does this get run at the sdk level as a callback?This one liner is very nice compared to previous implementation btw, nice job!
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.
see docs. Core IBC has also had its "module version" incremented previously