Skip to content

Commit

Permalink
feat: implement the Go side of dynamic IBC
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Apr 1, 2020
1 parent b004f11 commit cf2d894
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 5 deletions.
9 changes: 6 additions & 3 deletions packages/cosmic-swingset/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
55 changes: 55 additions & 0 deletions packages/cosmic-swingset/x/swingset/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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)
Expand All @@ -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 {
Expand Down
146 changes: 146 additions & 0 deletions packages/cosmic-swingset/x/swingset/ibc.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
50 changes: 48 additions & 2 deletions packages/cosmic-swingset/x/swingset/internal/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,35 @@ 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.

sendToController func(needReply bool, str string) (string, error)
}

// 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,
}
}
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}

0 comments on commit cf2d894

Please sign in to comment.