-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
R4R: Dynamic Capabilities with Routing #5888
Conversation
x/ibc/05-port/types/module.go
Outdated
packet channeltypes.Packet, | ||
) (*sdk.Result, error) | ||
|
||
GetCapability(ctx sdk.Context, name string) (capability.Capability, bool) |
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.
Put Get/Claim capability callbacks as well so that ibc can request a module to provide the right capability or pass a module a capability that it can claim
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.
Hmm, I'm a bit confused by these - why would IBC need to request a capability from a module or vice-versa? Generally in an object-capability system it is very dangerous for a module to expose an API that allows other modules to just fetch capabilities.
@@ -13,20 +13,6 @@ func NewHandler(k Keeper) sdk.Handler { | |||
switch msg := msg.(type) { | |||
case MsgTransfer: | |||
return handleMsgTransfer(ctx, k, msg) | |||
case channeltypes.MsgPacket: |
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.
No longer handled in 20-transfer
's Handler
} | ||
} | ||
|
||
func (am AppModule) OnAcknowledgementPacket( |
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.
Currently Acknowledgement callback doesn't exist, so i have a no-op. Will implement spec later
x/ibc/handler.go
Outdated
@@ -37,22 +38,75 @@ func NewHandler(k Keeper) sdk.Handler { | |||
|
|||
// IBC channel msgs | |||
case channel.MsgChannelOpenInit: | |||
res, cap, err := channel.HandleMsgChannelOpenInit(ctx, k.ChannelKeeper, msg) | |||
cbs := k.PortKeeper.LookupModule(ctx, msg.PortID) | |||
portCap, _ := cbs.GetCapability(ctx, ibctypes.PortPath(msg.PortID)) |
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.
Get port capability from module using callback
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.
Why do we need to look this up? The module should already have the port capability.
x/ibc/handler.go
Outdated
res, cap, err := channel.HandleMsgChannelOpenInit(ctx, k.ChannelKeeper, msg) | ||
cbs := k.PortKeeper.LookupModule(ctx, msg.PortID) | ||
portCap, _ := cbs.GetCapability(ctx, ibctypes.PortPath(msg.PortID)) | ||
err := cbs.OnChanOpenInit(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortID, msg.ChannelID, msg.Channel.Counterparty, msg.Channel.Version) |
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.
Call user defined OnChanOpenInit
callback
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.
Hmm, should this happen before or after HandleMsgChannelOpenInit
?
Both are safe as long as the transaction is atomic. If it's after then it can expect the channel state to be there.
x/ibc/handler.go
Outdated
return nil, err | ||
} | ||
res, cap, err := channel.HandleMsgChannelOpenInit(ctx, k.ChannelKeeper, portCap, msg) | ||
cbs.ClaimCapability(ctx, cap, ibctypes.ChannelCapabilityPath(msg.PortID, msg.ChannelID)) |
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.
Module claims created channel capability using callback
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.
Instead of doing it this way, how about passing the channel capability to OnChanOpenInit
? I think that's cleaner.
x/ibc/handler.go
Outdated
|
||
// IBC packet msgs get routed to the appropriate module callback | ||
case channel.MsgPacket: | ||
cbs := k.PortKeeper.LookupModule(ctx, msg.Packet.DestinationPort) |
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.
Rather than assuming portID == moduleName and using baseapp Router, we lookup module here in IBC handler and call appropriate callback
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.
This actually needs to be based on the channel capability, not the port capability
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.
Unfortunately I don't think we can safely use a map in this way - the routing table needs to be persisted in an analogous way to how dynamic capabilities are - perhaps the moduleName
on scoped capability keepers can be used to help us out here? When an incoming packet comes in, for example, we should look up (in the capability ownership table) which module owns the channel capability associated with that packet, then call the callback on that module. Since the module set is fixed in app.go
in all SDK apps, it's fine to have a static map of moduleName
to callbacks.
x/ibc/05-port/keeper/keeper.go
Outdated
@@ -45,6 +47,8 @@ func (k *Keeper) BindPort(ctx sdk.Context, portID string) capability.Capability | |||
panic(err.Error()) | |||
} | |||
|
|||
k.router[portID] = cbs |
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.
The problem is that BindPort
will not be re-called when e.g. gaiad
is stopped & re-started
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.
Can we delete this now?
x/ibc/05-port/types/module.go
Outdated
packet channeltypes.Packet, | ||
) (*sdk.Result, error) | ||
|
||
GetCapability(ctx sdk.Context, name string) (capability.Capability, bool) |
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.
Hmm, I'm a bit confused by these - why would IBC need to request a capability from a module or vice-versa? Generally in an object-capability system it is very dangerous for a module to expose an API that allows other modules to just fetch capabilities.
counterparty channeltypes.Counterparty, | ||
version string, | ||
) error { | ||
// TODO: Enforce ordering, currently relayers use ORDERED channels |
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.
In principle, token transfer channels can safely be unordered. I think we should test the relayer with both.
) error { | ||
// TODO: Enforce ordering, currently relayers use ORDERED channels | ||
|
||
if counterparty.PortID != types.PortID { |
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.
Not necessarily, it's not "safety-critical", just convention. I think it's useful convention for now though.
x/ibc/handler.go
Outdated
@@ -37,22 +38,75 @@ func NewHandler(k Keeper) sdk.Handler { | |||
|
|||
// IBC channel msgs | |||
case channel.MsgChannelOpenInit: | |||
res, cap, err := channel.HandleMsgChannelOpenInit(ctx, k.ChannelKeeper, msg) | |||
cbs := k.PortKeeper.LookupModule(ctx, msg.PortID) | |||
portCap, _ := cbs.GetCapability(ctx, ibctypes.PortPath(msg.PortID)) |
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.
Why do we need to look this up? The module should already have the port capability.
x/ibc/handler.go
Outdated
res, cap, err := channel.HandleMsgChannelOpenInit(ctx, k.ChannelKeeper, msg) | ||
cbs := k.PortKeeper.LookupModule(ctx, msg.PortID) | ||
portCap, _ := cbs.GetCapability(ctx, ibctypes.PortPath(msg.PortID)) | ||
err := cbs.OnChanOpenInit(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortID, msg.ChannelID, msg.Channel.Counterparty, msg.Channel.Version) |
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.
Hmm, should this happen before or after HandleMsgChannelOpenInit
?
Both are safe as long as the transaction is atomic. If it's after then it can expect the channel state to be there.
x/ibc/handler.go
Outdated
return nil, err | ||
} | ||
res, cap, err := channel.HandleMsgChannelOpenInit(ctx, k.ChannelKeeper, portCap, msg) | ||
cbs.ClaimCapability(ctx, cap, ibctypes.ChannelCapabilityPath(msg.PortID, msg.ChannelID)) |
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.
Instead of doing it this way, how about passing the channel capability to OnChanOpenInit
? I think that's cleaner.
x/ibc/handler.go
Outdated
|
||
// IBC packet msgs get routed to the appropriate module callback | ||
case channel.MsgPacket: | ||
cbs := k.PortKeeper.LookupModule(ctx, msg.Packet.DestinationPort) |
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.
This actually needs to be based on the channel capability, not the port capability
Basic pattern
Notes
|
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.
Almost there! A few minor concerns / cleanup remaining.
x/ibc/04-channel/keeper/keeper.go
Outdated
} | ||
// the module that owns the channel, for now, is the first module that isn't "ibc" | ||
// that owns the module | ||
module := capOwners.Owners[1].Module |
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.
Are we sure that the ordering will always be this way? Isn't it dependent on the module name?
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.
yeah this needs a test case for sure
x/ibc/05-port/keeper/keeper.go
Outdated
@@ -45,6 +47,8 @@ func (k *Keeper) BindPort(ctx sdk.Context, portID string) capability.Capability | |||
panic(err.Error()) | |||
} | |||
|
|||
k.router[portID] = cbs |
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.
Can we delete this now?
x/ibc/05-port/keeper/keeper.go
Outdated
} | ||
// the module that owns the port, for now, is the first module that isn't "ibc" | ||
// that owns the module | ||
module := capOwners.Owners[1].Module |
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.
Are we sure that the ordering will always be this way? Isn't it dependent on the module name?
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.
ditto test case
x/ibc/05-port/types/module.go
Outdated
connectionHops []string, | ||
portID, | ||
channelID string, | ||
portCap capability.Capability, |
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.
We shouldn't need to pass the port capability to the module
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.
(we do need to pass the channel capability)
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.
whoops misnamed
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.
good work @AdityaSripal! Thanks for tackling this
x/ibc/04-channel/keeper/keeper.go
Outdated
} | ||
// the module that owns the channel, for now, is the first module that isn't "ibc" | ||
// that owns the module | ||
module := capOwners.Owners[1].Module |
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.
yeah this needs a test case for sure
x/ibc/05-port/keeper/keeper.go
Outdated
} | ||
// the module that owns the port, for now, is the first module that isn't "ibc" | ||
// that owns the module | ||
module := capOwners.Owners[1].Module |
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.
ditto test case
x/ibc/handler.go
Outdated
// Lookup module by port capability | ||
module, portCap, ok := k.PortKeeper.LookupModuleByPort(ctx, msg.PortID) | ||
if !ok { | ||
return nil, sdkerrors.Wrap(porttypes.ErrInvalidPort, "could not retrieve module from portID") |
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.
return nil, sdkerrors.Wrap(porttypes.ErrInvalidPort, "could not retrieve module from portID") | |
return nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "could not retrieve module from portID %s" msg.PortID) |
x/ibc/handler.go
Outdated
// Lookup module by port capability | ||
module, portCap, ok := k.PortKeeper.LookupModuleByPort(ctx, msg.PortID) | ||
if !ok { | ||
return nil, sdkerrors.Wrap(porttypes.ErrInvalidPort, "could not retrieve module from portID") |
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.
return nil, sdkerrors.Wrap(porttypes.ErrInvalidPort, "could not retrieve module from portID") | |
return nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "could not retrieve module from portID %s" msg.PortID) |
This pull request fixes 2 alerts when merging 8086e42 into 06e7b80 - view on LGTM.com fixed alerts:
|
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.
LGTM. minor suggestion
x/ibc/04-channel/keeper/keeper.go
Outdated
capOwners, ok := k.scopedKeeper.GetOwners(ctx, ibctypes.ChannelCapabilityPath(portID, channelID)) | ||
if !ok { | ||
return "", nil, false | ||
} | ||
if len(capOwners.Owners) < 2 { | ||
panic("more than one module has bound to this port; currently not supported") | ||
} | ||
// the module that owns the channel, for now, is the only module owner that isn't "ibc" | ||
var module string | ||
if capOwners.Owners[0].Module == "ibc" { | ||
module = capOwners.Owners[1].Module | ||
} else { | ||
module = capOwners.Owners[0].Module | ||
} | ||
|
||
cap, found := k.scopedKeeper.GetCapability(ctx, ibctypes.ChannelCapabilityPath(portID, channelID)) | ||
if !found { | ||
return "", nil, false | ||
} | ||
return module, cap, true |
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.
nit, you can avoid duplication by abstracting this part and passing the path:
func (k ScopedKeeper) LookupModule(ctx sdk.Context, path string) (string, capability.Capability, bool) {
capOwners, ok := k.GetOwners(ctx, path)
if !ok {
return "", nil, false
}
// For now, we enforce that only IBC and the module bound to port can own the capability
// while future implementations may allow multiple modules to bind to a port, currently we
// only allow one module to be bound to a port at any given time
if len(capOwners.Owners) != 2 {
panic("more than one module has bound to this port; currently not supported")
}
// the module that owns the port, for now, is the only module owner that isn't "ibc"
var module string
if capOwners.Owners[0].Module == "ibc" {
module = capOwners.Owners[1].Module
} else {
module = capOwners.Owners[0].Module
}
cap, found := k.GetCapability(ctx, path)
if !found {
return "", nil, false
}
return module, cap, true
}
and then:
// LookupModuleByChannel will return the IBCModule along with the capability associated with a given channel defined by its portID and channelID
func (k Keeper) LookupModuleByChannel(ctx sdk.Context, portID, channelID string) (string, capability.Capability, bool) {
return k.scopedKeeper.LookupModule(ctx, ibctypes.ChannelCapabilityPath(portID, channelID))
}
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.
Hmm yea doesn't make sense to have the ibc special case in the scopedKeeper code, but I can just return string of modules from scoped keeper and then have a util function in ibc to enforce length-2 and pick non-ibc module
This pull request fixes 3 alerts when merging b282f99 into 06e7b80 - view on LGTM.com fixed alerts:
|
crypto/keyring/keyring_test.go
Outdated
algo := Secp256k1 | ||
|
||
n1, n2, n3 := "some dude", "a dudette", "dude-ish" | ||
p1, p2, p3 := "1234", "foobar", "foobar" |
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.
string foobar
has 3 occurrences, but such constant foobar
already exists (from goconst
)
crypto/keyring/keyring_test.go
Outdated
|
||
algo := Secp256k1 | ||
n1, n2, n3 := "personal", "business", "other" | ||
p1, p2 := "1234", "really-secure!@#$" |
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.
string 1234
has 5 occurrences, but such constant nums
already exists (from goconst
)
x/ibc/03-connection/keeper/keeper.go
Outdated
|
||
if cb(connection) { | ||
if cb(types.IdentifiedConnectionEnd{connection, identifier}) { |
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.
composites: github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types.IdentifiedConnectionEnd
composite literal uses unkeyed fields (from govet
)
This pull request fixes 3 alerts when merging 9b1aeda into 06e7b80 - view on LGTM.com fixed alerts:
|
@@ -0,0 +1 @@ | |||
MANIFEST-000004 |
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.
Seems like some test files got committed accidentally. Is there a command to clean these files up or should i manually delete them?
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.
errr, not sure how this happened. These files exist on master
due to @alessio's changes to the keybase. But I branched off of ibc-alpha
, so not sure how these got in here. They also were not in my PR diff.
@@ -0,0 +1 @@ | |||
MANIFEST-000004 |
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.
Seems like some test files got committed accidentally. Is there a command to clean these files up or should i manually delete them?
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 above.
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.
Logic looks solid, seems like a few test-cases can be uncommented, a few nits.
x/ibc/05-port/types/router.go
Outdated
// GetRoute returns a IBCModule for a given module. | ||
func (rtr *Router) GetRoute(module string) IBCModule { | ||
if !rtr.HasRoute(module) { | ||
panic(fmt.Sprintf("route does not exist for module %s", module)) |
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.
This seems potentially dangerous? What if a packet has a port/channel that isn't routed?
return sdkerrors.Wrap(channel.ErrChannelCapabilityNotFound, err.Error()) | ||
} | ||
|
||
// TODO: escrow |
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.
Here is where we want to create the escrow address
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.
Looks like the current implementation just calculates the escrow address from portID, and channelID and uses the bank keeper to send and receive money. This seems like it should work?
Should I make these escrow addresses module accounts instead?
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.
This is OK for now, I suppose, maybe they should be module accounts but that can be a follow-up.
return sdkerrors.Wrap(channel.ErrChannelCapabilityNotFound, err.Error()) | ||
} | ||
|
||
// TODO: escrow |
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.
Here is where we want to create the escrow address
Co-Authored-By: Christopher Goes <cwgoes@pluranimity.org>
…-sdk into aditya/ibc-routing
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.
utACK modulo question on ClaimCapability
safety / permissions
NOTE: Capability is no longer an interface but a concrete type |
// Lookup module by port capability | ||
module, portCap, ok := k.PortKeeper.LookupModuleByPort(ctx, msg.PortID) | ||
if !ok { | ||
return nil, sdkerrors.Wrap(port.ErrInvalidPort, "could not retrieve module from portID") | ||
} | ||
res, cap, err := channel.HandleMsgChannelOpenInit(ctx, k.ChannelKeeper, portCap, msg) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// Retrieve callbacks from router | ||
cbs, ok := k.Router.GetRoute(module) | ||
if !ok { | ||
return nil, sdkerrors.Wrapf(port.ErrInvalidRoute, "route not found to module: %s", module) | ||
} | ||
err = cbs.OnChanOpenInit(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortID, msg.ChannelID, cap, msg.Channel.Counterparty, msg.Channel.Version) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return res, nil |
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.
nit, can we move each of the cases to a private function to avoid bloating the switch
and for ease of reading?
Closes: #5542
Description
Start routing implementation in new PR since it may require changes to design. Currently router from portID => module callbacks is in portkeeper
All routing gets handled within the IBC handler itself
For contributor use:
docs/
) or specification (x/<module>/spec/
)godoc
comments.Unreleased
section inCHANGELOG.md
Files changed
in the Github PR explorerFor admin use:
WIP
,R4R
,docs
, etc)