diff --git a/packages/cosmic-swingset/app.go b/packages/cosmic-swingset/app.go index c8b80998587..3cc3635b5f8 100644 --- a/packages/cosmic-swingset/app.go +++ b/packages/cosmic-swingset/app.go @@ -197,17 +197,20 @@ func NewSwingSetApp( app.slashingKeeper.Hooks()), ) + app.ibcKeeper = ibc.NewKeeper(app.cdc, keys[ibc.StoreKey], stakingKeeper) + // The SwingSetKeeper is the Keeper from the Agoric module - // It handles interactions with the kvstore + // It handles interactions with the kvstore and IBC. + swingsetCapKey := app.ibcKeeper.PortKeeper.BindPort(swingset.ModuleName) app.ssKeeper = swingset.NewKeeper( app.cdc, keys[swingset.StoreKey], + swingsetCapKey, + app.ibcKeeper.ChannelKeeper, app.bankKeeper, sendToController, ) - app.ibcKeeper = ibc.NewKeeper(app.cdc, keys[ibc.StoreKey], stakingKeeper) - app.mm = module.NewManager( genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), auth.NewAppModule(app.accountKeeper, app.supplyKeeper), diff --git a/packages/cosmic-swingset/x/swingset/handler.go b/packages/cosmic-swingset/x/swingset/handler.go index 7de3474957b..1fcac2209fd 100644 --- a/packages/cosmic-swingset/x/swingset/handler.go +++ b/packages/cosmic-swingset/x/swingset/handler.go @@ -10,8 +10,20 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" ) +type ibcPacketAction struct { + Type string `json:"type"` + Packet channeltypes.Packet `json:"packet"` + ChannelPort int `json:"channelPort"` + StoragePort int `json:"storagePort"` + BlockHeight int64 `json:"blockHeight"` + BlockTime int64 `json:"blockTime"` +} + type deliverInboundAction struct { Type string `json:"type"` Peer string `json:"peer"` @@ -26,8 +38,18 @@ type deliverInboundAction struct { func NewHandler(keeper Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { switch msg := msg.(type) { + // IBC channel support. + case channeltypes.MsgPacket: + return handleIBCPacket(ctx, keeper, "IBC_PACKET", msg.Packet) + + case channeltypes.MsgTimeout: + return handleIBCPacket(ctx, keeper, "IBC_TIMEOUT", msg.Packet) + + // Legacy deliver inbound. + // TODO: Sometime merge with IBC? case MsgDeliverInbound: return handleMsgDeliverInbound(ctx, keeper, msg) + default: errMsg := fmt.Sprintf("Unrecognized swingset Msg type: %v", msg.Type()) return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) @@ -43,6 +65,39 @@ func mailboxPeer(key string) (string, error) { return path[1], nil } +func handleIBCPacket(ctx sdk.Context, keeper Keeper, actionType string, packet channelexported.PacketI) (*sdk.Result, error) { + // Create a "storagePort" that the controller can use to communicate with the + // storageHandler + storagePort := RegisterPortHandler(NewUnlimitedStorageHandler(ctx, keeper)) + defer UnregisterPortHandler(storagePort) + + // The channel lifetime is longer than just one message. + pkt := packet.(channeltypes.Packet) + channelPort := GetIBCChannelPortHandler(ctx, keeper, pkt) + + action := &ibcPacketAction{ + Type: actionType, + Packet: pkt, + StoragePort: storagePort, + ChannelPort: channelPort, + BlockHeight: ctx.BlockHeight(), + BlockTime: ctx.BlockTime().Unix(), + } + + // fmt.Fprintf(os.Stderr, "Context is %+v\n", ctx) + b, err := json.Marshal(action) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + + _, err = keeper.CallToController(string(b)) + // fmt.Fprintln(os.Stderr, "Returned from SwingSet", out, err) + if err != nil { + return nil, err + } + return &sdk.Result{}, nil +} + func handleMsgDeliverInbound(ctx sdk.Context, keeper Keeper, msg MsgDeliverInbound) (*sdk.Result, error) { messages := make([][]interface{}, len(msg.Messages)) for i, message := range msg.Messages { diff --git a/packages/cosmic-swingset/x/swingset/ibc.go b/packages/cosmic-swingset/x/swingset/ibc.go new file mode 100644 index 00000000000..8b511858237 --- /dev/null +++ b/packages/cosmic-swingset/x/swingset/ibc.go @@ -0,0 +1,146 @@ +package swingset + +import ( + "encoding/base64" + "encoding/json" + "fmt" + + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// FIXME: How to tell the caller when this is a new channel? + +type channelEndpoint struct { + Port string `json:"port"` + Channel string `json:"channel"` +} + +type channelTuple struct { + Destination channelEndpoint + Source channelEndpoint +} + +type channelHandler struct { + Keeper Keeper + Context sdk.Context + Destination channelEndpoint + CurrentPacket *channeltypes.Packet + ChannelPort int + Tuple channelTuple +} + +type channelMessage struct { + Method string `json:"method"` + Data64 string `json:"data64"` +} + +func (cm channelMessage) GetData() []byte { + data, err := base64.StdEncoding.DecodeString(cm.Data64) + if err != nil { + fmt.Println("Could not decode base64 of", cm.Data64, ":", err) + return nil + } + return data +} + +var channelHandlers map[channelTuple]*channelHandler + +func init() { + channelHandlers = make(map[channelTuple]*channelHandler) +} + +func GetIBCChannelPortHandler(ctx sdk.Context, keeper Keeper, packet channeltypes.Packet) int { + tuple := channelTuple { + Destination: channelEndpoint{ + Port: packet.DestinationPort, + Channel: packet.DestinationChannel, + }, + Source: channelEndpoint{ + Port: packet.SourcePort, + Channel: packet.SourceChannel, + }, + }; + + // lookup existing channel based on the connection tuple + if ch, ok := channelHandlers[tuple]; ok { + ch.CurrentPacket = &packet + return ch.ChannelPort + } + + ch := NewChannelHandler(ctx, keeper, tuple) + ch.CurrentPacket = &packet + ch.ChannelPort = RegisterPortHandler(ch) + channelHandlers[tuple] = ch + return ch.ChannelPort +} + +func NewChannelHandler(ctx sdk.Context, keeper Keeper, tuple channelTuple) *channelHandler { + return &channelHandler{ + Context: ctx, + Keeper: keeper, + Tuple: tuple, + } +} + +func (ch *channelHandler) Receive(str string) (ret string, err error) { + fmt.Println("channel handler received", str) + + msg := new(channelMessage) + err = json.Unmarshal([]byte(str), &msg) + if err != nil { + return "", err + } + + switch msg.Method { + case "ack": + if ch.CurrentPacket == nil { + return "", fmt.Errorf("current packet is already acknowledged") + } + err = ch.Keeper.PacketExecuted(ch.Context, ch.CurrentPacket, msg.GetData()) + ch.CurrentPacket = nil + if err != nil { + return "", err + } + return "true", nil + + case "close": + // Make sure our port goes away. + defer func() { + UnregisterPortHandler(ch.ChannelPort) + delete(channelHandlers, ch.Tuple) + }() + if err = ch.Keeper.ChanCloseInit(ch.Context, ch.Tuple.Destination.Port, ch.Tuple.Destination.Channel); err != nil { + return "", err + } + return "true", nil + + case "send": + seq, ok := ch.Keeper.GetNextSequenceSend( + ch.Context, + ch.Tuple.Destination.Channel, + ch.Tuple.Destination.Port, + ) + if !ok { + return "", fmt.Errorf("unknown sequence number") + } + + // FIXME + blockTimeout := int64(100) + + packet := channeltypes.NewPacket( + msg.GetData(), seq, + ch.Tuple.Source.Port, ch.Tuple.Source.Channel, + ch.Tuple.Destination.Port, ch.Tuple.Destination.Channel, + uint64(ch.Context.BlockHeight() + blockTimeout), + ) + if err := ch.Keeper.SendPacket(ch.Context, packet); err != nil{ + return "", err + } + return "true", nil + + default: + return "", fmt.Errorf("unrecognized method %s", msg.Method) + } +} diff --git a/packages/cosmic-swingset/x/swingset/internal/keeper/keeper.go b/packages/cosmic-swingset/x/swingset/internal/keeper/keeper.go index 56e71587983..39a90a4564d 100644 --- a/packages/cosmic-swingset/x/swingset/internal/keeper/keeper.go +++ b/packages/cosmic-swingset/x/swingset/internal/keeper/keeper.go @@ -8,13 +8,18 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" + + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" ) // Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine type Keeper struct { - CoinKeeper types.BankKeeper + ChannelKeeper types.ChannelKeeper + CoinKeeper types.BankKeeper storeKey sdk.StoreKey // Unexposed key to access store from sdk.Context + capKey sdk.CapabilityKey cdc *codec.Codec // The wire codec for binary encoding/decoding. @@ -22,12 +27,16 @@ type Keeper struct { } // NewKeeper creates new instances of the swingset Keeper -func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, coinKeeper types.BankKeeper, +func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, + capKey sdk.CapabilityKey, channelKeeper types.ChannelKeeper, + coinKeeper types.BankKeeper, sendToController func(needReply bool, str string) (string, error)) Keeper { return Keeper{ + ChannelKeeper: channelKeeper, CoinKeeper: coinKeeper, storeKey: storeKey, cdc: cdc, + capKey: capKey, sendToController: sendToController, } } @@ -138,3 +147,40 @@ func (k Keeper) GetPeersIterator(ctx sdk.Context) sdk.Iterator { store := ctx.KVStore(k.storeKey) return sdk.KVStorePrefixIterator(store, nil) } + +// PacketExecuted defines a wrapper function for the channel Keeper's function +// in order to expose it to the SwingSet IBC handler. +func (k Keeper) PacketExecuted(ctx sdk.Context, packet channelexported.PacketI, acknowledgement []byte) error { + return k.ChannelKeeper.PacketExecuted(ctx, packet, acknowledgement) +} + +// ChanCloseInit defines a wrapper function for the channel Keeper's function +// in order to expose it to the SwingSet IBC handler. +func (k Keeper) ChanCloseInit(ctx sdk.Context, portID, channelID string) error { + return k.ChannelKeeper.ChanCloseInit(ctx, portID, channelID) +} + +// TimeoutExecuted defines a wrapper function for the channel Keeper's function +// in order to expose it to the SwingSet IBC handler. +func (k Keeper) TimeoutExecuted(ctx sdk.Context, packet channelexported.PacketI) error { + return k.ChannelKeeper.TimeoutExecuted(ctx, packet) +} + +// GetChannel defines a wrapper function for the channel Keeper's function +// in order to expose it to the SwingSet IBC handler. +func (k Keeper) GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channel.Channel, found bool) { + return k.ChannelKeeper.GetChannel(ctx, srcPort, srcChan) +} + +// GetNextSequenceSend defines a wrapper function for the channel Keeper's function +// in order to expose it to the SwingSet IBC handler. +func (k Keeper) GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) { + seq, ok := k.ChannelKeeper.GetNextSequenceSend(ctx, portID, channelID) + return seq, ok +} + +// SendPacket defines a wrapper function for the channel Keeper's function +// in order to expose it to the SwingSet IBC handler. +func (k Keeper) SendPacket(ctx sdk.Context, packet channelexported.PacketI) error { + return k.ChannelKeeper.SendPacket(ctx, packet) +} diff --git a/packages/cosmic-swingset/x/swingset/internal/types/expected_keepers.go b/packages/cosmic-swingset/x/swingset/internal/types/expected_keepers.go index de83417e9e2..19fb32a25bc 100644 --- a/packages/cosmic-swingset/x/swingset/internal/types/expected_keepers.go +++ b/packages/cosmic-swingset/x/swingset/internal/types/expected_keepers.go @@ -2,6 +2,10 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" + clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" ) // When a module wishes to interact with an other module it is good practice to define what it will use @@ -10,3 +14,23 @@ type BankKeeper interface { SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error } + +// ChannelKeeper defines the expected IBC channel keeper +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channel.Channel, found bool) + GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) + SendPacket(ctx sdk.Context, packet channelexported.PacketI) error + PacketExecuted(ctx sdk.Context, packet channelexported.PacketI, acknowledgement []byte) error + ChanCloseInit(ctx sdk.Context, portID, channelID string) error + TimeoutExecuted(ctx sdk.Context, packet channelexported.PacketI) error +} + +// ClientKeeper defines the expected IBC client keeper +type ClientKeeper interface { + GetClientConsensusState(ctx sdk.Context, clientID string) (connection clientexported.ConsensusState, found bool) +} + +// ConnectionKeeper defines the expected IBC connection keeper +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connection connection.ConnectionEnd, found bool) +}