diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go index 2fad1cc93ef..8b96c8fe76d 100644 --- a/golang/cosmos/app/app.go +++ b/golang/cosmos/app/app.go @@ -502,16 +502,13 @@ func NewAgoricApp( ) app.VibcKeeper = vibc.NewKeeper( - appCodec, keys[vibc.StoreKey], + appCodec, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, - app.BankKeeper, - scopedVibcKeeper, - app.SwingSetKeeper.PushAction, - ) + ).WithScope(keys[vibc.StoreKey], scopedVibcKeeper, app.SwingSetKeeper.PushAction) - vibcModule := vibc.NewAppModule(app.VibcKeeper) + vibcModule := vibc.NewAppModule(app.VibcKeeper, app.BankKeeper) vibcIBCModule := vibc.NewIBCModule(app.VibcKeeper) - app.vibcPort = vm.RegisterPortHandler("vibc", vibcIBCModule) + app.vibcPort = vm.RegisterPortHandler("vibc", vibc.NewReceiver(app.VibcKeeper)) app.VbankKeeper = vbank.NewKeeper( appCodec, keys[vbank.StoreKey], app.GetSubspace(vbank.ModuleName), diff --git a/golang/cosmos/go.mod b/golang/cosmos/go.mod index 663a9122195..5ef54f5f2d8 100644 --- a/golang/cosmos/go.mod +++ b/golang/cosmos/go.mod @@ -3,6 +3,7 @@ module github.com/Agoric/agoric-sdk/golang/cosmos go 1.20 require ( + cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.0-rc.0 github.com/armon/go-metrics v0.4.1 github.com/cosmos/cosmos-sdk v0.46.16 @@ -31,7 +32,6 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.1 // indirect cloud.google.com/go/storage v1.30.1 // indirect - cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect @@ -169,9 +169,6 @@ replace ( github.com/confio/ics23/go => github.com/agoric-labs/cosmos-sdk/ics23/go v0.8.0-alpha.agoric.1 - // We need a fork of cosmos-sdk until all of the differences are merged. - github.com/cosmos/cosmos-sdk => github.com/agoric-labs/cosmos-sdk v0.46.16-alpha.agoric.2 - // https://pkg.go.dev/vuln/GO-2023-2409 github.com/dvsekhvalnov/jose2go => github.com/dvsekhvalnov/jose2go v1.5.1-0.20231206184617-48ba0b76bc88 @@ -185,13 +182,23 @@ replace ( // replace broken goleveldb. github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 +) + +// Agoric-specific replacements: +replace ( + // We need a fork of cosmos-sdk until all of the differences are merged. + github.com/cosmos/cosmos-sdk => github.com/agoric-labs/cosmos-sdk v0.46.16-alpha.agoric.2 + + // Async version negotiation + github.com/cosmos/ibc-go/v6 => github.com/agoric-labs/ibc-go/v6 v6.2.1-alpha.agoric.3 // use cometbft // Use our fork at least until post-v0.34.14 is released with // https://github.com/tendermint/tendermint/issue/6899 resolved. github.com/tendermint/tendermint => github.com/agoric-labs/cometbft v0.34.30-alpha.agoric.1 -// For testing against a local cosmos-sdk or tendermint +// For testing against a local cosmos-sdk, ibc-go, or cometbft // github.com/cosmos/cosmos-sdk => ../../../forks/cosmos-sdk -// github.com/tendermint/tendermint => ../../../forks/tendermint +// github.com/cosmos/ibc-go/v6 => ../../../forks/ibc-go/v6 +// github.com/tendermint/tendermint => ../../../forks/cometbft ) diff --git a/golang/cosmos/go.sum b/golang/cosmos/go.sum index c6d0bd1aa86..7562b04991a 100644 --- a/golang/cosmos/go.sum +++ b/golang/cosmos/go.sum @@ -236,6 +236,8 @@ github.com/agoric-labs/cosmos-sdk v0.46.16-alpha.agoric.2 h1:iHHqpYC0JzMbH4UYnQr github.com/agoric-labs/cosmos-sdk v0.46.16-alpha.agoric.2/go.mod h1:zUe5lsg/X7SeSO1nGkzOh9EGKO295szfrxIxYmeLYic= github.com/agoric-labs/cosmos-sdk/ics23/go v0.8.0-alpha.agoric.1 h1:2jvHI/2d+psWAZy6FQ0vXJCHUtfU3ZbbW+pQFL04arQ= github.com/agoric-labs/cosmos-sdk/ics23/go v0.8.0-alpha.agoric.1/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/agoric-labs/ibc-go/v6 v6.2.1-alpha.agoric.3 h1:YqvVwK+Lg/ZsuwyVm9UbPs8K55fg00R3Y9KnmaTBdgc= +github.com/agoric-labs/ibc-go/v6 v6.2.1-alpha.agoric.3/go.mod h1:V9NOCRS9RPkSJNJQIPRAjZn/lo2mCAAKOSv3/83ISDY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -380,8 +382,6 @@ github.com/cosmos/iavl v0.19.6 h1:XY78yEeNPrEYyNCKlqr9chrwoeSDJ0bV2VjocTk//OU= github.com/cosmos/iavl v0.19.6/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v6 v6.1.1 h1:2geCtV4PoNPeRnVc0HMAcRcv+7W3Mvk2nmASkGkOdzE= github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v6 v6.1.1/go.mod h1:ovYRGX7P7Vq0D54JIVlIm/47STEKgWJfw9frvL0AWGQ= -github.com/cosmos/ibc-go/v6 v6.2.1 h1:NiaDXTRhKwf3n9kELD4VRIe5zby1yk1jBvaz9tXTQ6k= -github.com/cosmos/ibc-go/v6 v6.2.1/go.mod h1:XLsARy4Y7+GtAqzMcxNdlQf6lx+ti1e8KcMGv5NIK7A= github.com/cosmos/keyring v1.2.0 h1:8C1lBP9xhImmIabyXW4c3vFjjLiBdGCmfLUfeZlV1Yo= github.com/cosmos/keyring v1.2.0/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= github.com/cosmos/ledger-cosmos-go v0.12.4 h1:drvWt+GJP7Aiw550yeb3ON/zsrgW0jgh5saFCr7pDnw= diff --git a/golang/cosmos/x/swingset/abci.go b/golang/cosmos/x/swingset/abci.go index df6e360c472..2afb78457f5 100644 --- a/golang/cosmos/x/swingset/abci.go +++ b/golang/cosmos/x/swingset/abci.go @@ -15,27 +15,27 @@ import ( ) type beginBlockAction struct { - vm.ActionHeader `actionType:"BEGIN_BLOCK"` - ChainID string `json:"chainID"` - Params types.Params `json:"params"` + *vm.ActionHeader `actionType:"BEGIN_BLOCK"` + ChainID string `json:"chainID"` + Params types.Params `json:"params"` } type endBlockAction struct { - vm.ActionHeader `actionType:"END_BLOCK"` + *vm.ActionHeader `actionType:"END_BLOCK"` } type commitBlockAction struct { - vm.ActionHeader `actionType:"COMMIT_BLOCK"` + *vm.ActionHeader `actionType:"COMMIT_BLOCK"` } type afterCommitBlockAction struct { - vm.ActionHeader `actionType:"AFTER_COMMIT_BLOCK"` + *vm.ActionHeader `actionType:"AFTER_COMMIT_BLOCK"` } func BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock, keeper Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) - action := &beginBlockAction{ + action := beginBlockAction{ ChainID: ctx.ChainID(), Params: keeper.GetParams(ctx), } @@ -56,7 +56,7 @@ var endBlockTime int64 func EndBlock(ctx sdk.Context, req abci.RequestEndBlock, keeper Keeper) ([]abci.ValidatorUpdate, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - action := &endBlockAction{} + action := endBlockAction{} _, err := keeper.BlockingSend(ctx, action) // fmt.Fprintf(os.Stderr, "END_BLOCK Returned from SwingSet: %s, %v\n", out, err) @@ -83,7 +83,7 @@ func getEndBlockContext() sdk.Context { func CommitBlock(keeper Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), "commit_blocker") - action := &commitBlockAction{} + action := commitBlockAction{} _, err := keeper.BlockingSend(getEndBlockContext(), action) // fmt.Fprintf(os.Stderr, "COMMIT_BLOCK Returned from SwingSet: %s, %v\n", out, err) @@ -98,7 +98,7 @@ func CommitBlock(keeper Keeper) error { func AfterCommitBlock(keeper Keeper) error { // defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), "commit_blocker") - action := &afterCommitBlockAction{} + action := afterCommitBlockAction{} _, err := keeper.BlockingSend(getEndBlockContext(), action) // fmt.Fprintf(os.Stderr, "AFTER_COMMIT_BLOCK Returned from SwingSet: %s, %v\n", out, err) diff --git a/golang/cosmos/x/swingset/keeper/msg_server.go b/golang/cosmos/x/swingset/keeper/msg_server.go index 138a6d6803f..c40aa6a0baa 100644 --- a/golang/cosmos/x/swingset/keeper/msg_server.go +++ b/golang/cosmos/x/swingset/keeper/msg_server.go @@ -21,10 +21,10 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} type deliverInboundAction struct { - vm.ActionHeader `actionType:"DELIVER_INBOUND"` - Peer string `json:"peer"` - Messages [][]interface{} `json:"messages"` - Ack uint64 `json:"ack"` + *vm.ActionHeader `actionType:"DELIVER_INBOUND"` + Peer string `json:"peer"` + Messages [][]interface{} `json:"messages"` + Ack uint64 `json:"ack"` } func (keeper msgServer) routeAction(ctx sdk.Context, msg vm.ControllerAdmissionMsg, action vm.Action) error { @@ -48,7 +48,7 @@ func (keeper msgServer) DeliverInbound(goCtx context.Context, msg *types.MsgDeli for i, message := range msg.Messages { messages[i] = []interface{}{msg.Nums[i], message} } - action := &deliverInboundAction{ + action := deliverInboundAction{ Peer: msg.Submitter.String(), Messages: messages, Ack: msg.Ack, @@ -63,9 +63,9 @@ func (keeper msgServer) DeliverInbound(goCtx context.Context, msg *types.MsgDeli } type walletAction struct { - vm.ActionHeader `actionType:"WALLET_ACTION"` - Owner string `json:"owner"` - Action string `json:"action"` + *vm.ActionHeader `actionType:"WALLET_ACTION"` + Owner string `json:"owner"` + Action string `json:"action"` } func (keeper msgServer) WalletAction(goCtx context.Context, msg *types.MsgWalletAction) (*types.MsgWalletActionResponse, error) { @@ -76,7 +76,7 @@ func (keeper msgServer) WalletAction(goCtx context.Context, msg *types.MsgWallet return nil, err } - action := &walletAction{ + action := walletAction{ Owner: msg.Owner.String(), Action: msg.Action, } @@ -91,9 +91,9 @@ func (keeper msgServer) WalletAction(goCtx context.Context, msg *types.MsgWallet } type walletSpendAction struct { - vm.ActionHeader `actionType:"WALLET_SPEND_ACTION"` - Owner string `json:"owner"` - SpendAction string `json:"spendAction"` + *vm.ActionHeader `actionType:"WALLET_SPEND_ACTION"` + Owner string `json:"owner"` + SpendAction string `json:"spendAction"` } func (keeper msgServer) WalletSpendAction(goCtx context.Context, msg *types.MsgWalletSpendAction) (*types.MsgWalletSpendActionResponse, error) { @@ -104,7 +104,7 @@ func (keeper msgServer) WalletSpendAction(goCtx context.Context, msg *types.MsgW return nil, err } - action := &walletSpendAction{ + action := walletSpendAction{ Owner: msg.Owner.String(), SpendAction: msg.SpendAction, } @@ -117,7 +117,7 @@ func (keeper msgServer) WalletSpendAction(goCtx context.Context, msg *types.MsgW } type provisionAction struct { - vm.ActionHeader `actionType:"PLEASE_PROVISION"` + *vm.ActionHeader `actionType:"PLEASE_PROVISION"` *types.MsgProvision AutoProvision bool `json:"autoProvision"` } @@ -141,7 +141,7 @@ func (keeper msgServer) provisionIfNeeded(ctx sdk.Context, owner sdk.AccAddress) PowerFlags: []string{types.PowerFlagSmartWallet}, } - action := &provisionAction{ + action := provisionAction{ MsgProvision: msg, AutoProvision: true, } @@ -163,7 +163,7 @@ func (keeper msgServer) Provision(goCtx context.Context, msg *types.MsgProvision return nil, err } - action := &provisionAction{ + action := provisionAction{ MsgProvision: msg, } @@ -184,7 +184,7 @@ func (keeper msgServer) Provision(goCtx context.Context, msg *types.MsgProvision } type installBundleAction struct { - vm.ActionHeader `actionType:"INSTALL_BUNDLE"` + *vm.ActionHeader `actionType:"INSTALL_BUNDLE"` *types.MsgInstallBundle } @@ -195,7 +195,7 @@ func (keeper msgServer) InstallBundle(goCtx context.Context, msg *types.MsgInsta if err != nil { return nil, err } - action := &installBundleAction{ + action := installBundleAction{ MsgInstallBundle: msg, } diff --git a/golang/cosmos/x/swingset/keeper/proposal.go b/golang/cosmos/x/swingset/keeper/proposal.go index dd84fa0287a..079dfbe82e6 100644 --- a/golang/cosmos/x/swingset/keeper/proposal.go +++ b/golang/cosmos/x/swingset/keeper/proposal.go @@ -11,13 +11,13 @@ import ( ) type coreEvalAction struct { - vm.ActionHeader `actionType:"CORE_EVAL"` - Evals []types.CoreEval `json:"evals"` + *vm.ActionHeader `actionType:"CORE_EVAL"` + Evals []types.CoreEval `json:"evals"` } // CoreEvalProposal tells SwingSet to evaluate the given JS code. func (k Keeper) CoreEvalProposal(ctx sdk.Context, p *types.CoreEvalProposal) error { - action := &coreEvalAction{ + action := coreEvalAction{ Evals: p.Evals, } diff --git a/golang/cosmos/x/vibc/alias.go b/golang/cosmos/x/vibc/alias.go index bb31d675d40..f2241dbd16b 100644 --- a/golang/cosmos/x/vibc/alias.go +++ b/golang/cosmos/x/vibc/alias.go @@ -14,11 +14,14 @@ const ( var ( NewKeeper = keeper.NewKeeper NewMsgSendPacket = types.NewMsgSendPacket + NewReceiver = types.NewReceiver + NewIBCModule = types.NewIBCModule ModuleCdc = types.ModuleCdc RegisterCodec = types.RegisterCodec ) type ( Keeper = keeper.Keeper + ScopedKeeper = types.ScopedKeeper MsgSendPacket = types.MsgSendPacket ) diff --git a/golang/cosmos/x/vibc/handler.go b/golang/cosmos/x/vibc/handler.go index 524ba8cd5b9..60cfb1f602a 100644 --- a/golang/cosmos/x/vibc/handler.go +++ b/golang/cosmos/x/vibc/handler.go @@ -5,16 +5,17 @@ import ( sdkioerrors "cosmossdk.io/errors" "github.com/Agoric/agoric-sdk/golang/cosmos/vm" + "github.com/Agoric/agoric-sdk/golang/cosmos/x/vibc/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // NewHandler returns a handler for "vibc" type messages. -func NewHandler(keeper Keeper) sdk.Handler { +func NewHandler(keeper Keeper, bankKeeper types.BankKeeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { switch msg := msg.(type) { case *MsgSendPacket: - return handleMsgSendPacket(ctx, keeper, msg) + return handleMsgSendPacket(ctx, keeper, bankKeeper, msg) default: errMsg := fmt.Sprintf("Unrecognized vibc Msg type: %T", msg) @@ -24,14 +25,19 @@ func NewHandler(keeper Keeper) sdk.Handler { } type sendPacketAction struct { + *vm.ActionHeader `actionType:"IBC_EVENT"` + Event string `json:"event" default:"sendPacket"` *MsgSendPacket - vm.ActionHeader `actionType:"IBC_EVENT"` - Event string `json:"event" default:"sendPacket"` } -func handleMsgSendPacket(ctx sdk.Context, keeper Keeper, msg *MsgSendPacket) (*sdk.Result, error) { +func handleMsgSendPacket( + ctx sdk.Context, + keeper Keeper, + bankKeeper types.BankKeeper, + msg *MsgSendPacket, +) (*sdk.Result, error) { onePass := sdk.NewInt64Coin("sendpacketpass", 1) - balance := keeper.GetBalance(ctx, msg.Sender, onePass.Denom) + balance := bankKeeper.GetBalance(ctx, msg.Sender, onePass.Denom) if balance.IsLT(onePass) { return nil, sdkioerrors.Wrap( sdkerrors.ErrInsufficientFee, @@ -39,7 +45,7 @@ func handleMsgSendPacket(ctx sdk.Context, keeper Keeper, msg *MsgSendPacket) (*s ) } - action := &sendPacketAction{ + action := sendPacketAction{ MsgSendPacket: msg, } // fmt.Fprintf(os.Stderr, "Context is %+v\n", ctx) diff --git a/golang/cosmos/x/vibc/keeper/keeper.go b/golang/cosmos/x/vibc/keeper/keeper.go index 5b1d8c5696d..4d5587012ef 100644 --- a/golang/cosmos/x/vibc/keeper/keeper.go +++ b/golang/cosmos/x/vibc/keeper/keeper.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkioerrors "cosmossdk.io/errors" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" capability "github.com/cosmos/cosmos-sdk/x/capability/types" clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" @@ -16,47 +15,66 @@ import ( host "github.com/cosmos/ibc-go/v6/modules/core/24-host" ibcexported "github.com/cosmos/ibc-go/v6/modules/core/exported" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - - vm "github.com/Agoric/agoric-sdk/golang/cosmos/vm" + "github.com/Agoric/agoric-sdk/golang/cosmos/vm" "github.com/Agoric/agoric-sdk/golang/cosmos/x/vibc/types" ) +var ( + _ porttypes.ICS4Wrapper = Keeper{} + _ types.IBCModuleImpl = Keeper{} + _ types.ReceiverImpl = Keeper{} +) + // Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine type Keeper struct { - storeKey storetypes.StoreKey - cdc codec.Codec + cdc codec.Codec channelKeeper types.ChannelKeeper portKeeper types.PortKeeper - scopedKeeper capabilitykeeper.ScopedKeeper - bankKeeper bankkeeper.Keeper - PushAction vm.ActionPusher + // Filled out by `WithScope` + scopedKeeper types.ScopedKeeper + storeKey storetypes.StoreKey + pushAction vm.ActionPusher } -// NewKeeper creates a new dIBC Keeper instance +// NewKeeper creates a new vibc Keeper instance func NewKeeper( - cdc codec.Codec, key storetypes.StoreKey, - channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, - bankKeeper bankkeeper.Keeper, - scopedKeeper capabilitykeeper.ScopedKeeper, - pushAction vm.ActionPusher, + cdc codec.Codec, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, ) Keeper { return Keeper{ - storeKey: key, cdc: cdc, - bankKeeper: bankKeeper, channelKeeper: channelKeeper, portKeeper: portKeeper, - scopedKeeper: scopedKeeper, - PushAction: pushAction, } } -func (k Keeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { - return k.bankKeeper.GetBalance(ctx, addr, denom) +// WithScope returns a new Keeper copied from the receiver, but with the given +// store key, scoped keeper, and push action. +func (k Keeper) WithScope(storeKey storetypes.StoreKey, scopedKeeper types.ScopedKeeper, pushAction vm.ActionPusher) Keeper { + k.storeKey = storeKey + k.scopedKeeper = scopedKeeper + k.pushAction = pushAction + return k +} + +// PushAction sends a vm.Action to the VM controller. +func (k Keeper) PushAction(ctx sdk.Context, action vm.Action) error { + return k.pushAction(ctx, action) +} + +// GetICS4Wrapper returns the ICS4Wrapper interface for the keeper. +func (k Keeper) GetICS4Wrapper() porttypes.ICS4Wrapper { + return k +} + +// GetAppVersion defines a wrapper function for the channel Keeper's function +// in order to expose it to the vibc IBC handler. +func (k Keeper) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return k.channelKeeper.GetAppVersion(ctx, portID, channelID) } // GetChannel defines a wrapper function for the channel Keeper's function @@ -65,9 +83,8 @@ func (k Keeper) GetChannel(ctx sdk.Context, portID, channelID string) (channelty return k.channelKeeper.GetChannel(ctx, portID, channelID) } -// ChanOpenInit defines a wrapper function for the channel Keeper's function -// in order to expose it to the vibc IBC handler. -func (k Keeper) ChanOpenInit(ctx sdk.Context, order channeltypes.Order, connectionHops []string, +// ReceiveChanOpenInit wraps the keeper's ChanOpenInit function. +func (k Keeper) ReceiveChanOpenInit(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, rPortID, version string, ) error { capName := host.PortPath(portID) @@ -92,41 +109,41 @@ func (k Keeper) ChanOpenInit(ctx sdk.Context, order channeltypes.Order, connecti return nil } +// ReceiveSendPacket wraps the keeper's SendPacket function. +func (k Keeper) ReceiveSendPacket(ctx sdk.Context, packet ibcexported.PacketI) (uint64, error) { + sourcePort := packet.GetSourcePort() + sourceChannel := packet.GetSourceChannel() + timeoutHeight := packet.GetTimeoutHeight() + timeoutRevisionNumber := timeoutHeight.GetRevisionNumber() + timeoutRevisionHeight := timeoutHeight.GetRevisionHeight() + clientTimeoutHeight := clienttypes.NewHeight(timeoutRevisionNumber, timeoutRevisionHeight) + timeoutTimestamp := packet.GetTimeoutTimestamp() + data := packet.GetData() + + capName := host.ChannelCapabilityPath(sourcePort, sourceChannel) + chanCap, ok := k.GetCapability(ctx, capName) + if !ok { + return 0, sdkioerrors.Wrapf(channeltypes.ErrChannelCapabilityNotFound, "could not retrieve channel capability at: %s", capName) + } + return k.SendPacket(ctx, chanCap, sourcePort, sourceChannel, clientTimeoutHeight, timeoutTimestamp, data) +} + // SendPacket defines a wrapper function for the channel Keeper's function // in order to expose it to the vibc IBC handler. func (k Keeper) SendPacket( ctx sdk.Context, + chanCap *capability.Capability, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte, ) (uint64, error) { - capName := host.ChannelCapabilityPath(sourcePort, sourceChannel) - chanCap, ok := k.GetCapability(ctx, capName) - if !ok { - return 0, sdkioerrors.Wrapf(channeltypes.ErrChannelCapabilityNotFound, "could not retrieve channel capability at: %s", capName) - } return k.channelKeeper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) } -var _ ibcexported.Acknowledgement = (*rawAcknowledgement)(nil) - -type rawAcknowledgement struct { - data []byte -} - -func (r rawAcknowledgement) Acknowledgement() []byte { - return r.data -} - -func (r rawAcknowledgement) Success() bool { - return true -} - -// WriteAcknowledgement defines a wrapper function for the channel Keeper's function -// in order to expose it to the vibc IBC handler. -func (k Keeper) WriteAcknowledgement(ctx sdk.Context, packet ibcexported.PacketI, acknowledgement []byte) error { +// ReceiveWriteAcknowledgement wraps the keeper's WriteAcknowledgment function. +func (k Keeper) ReceiveWriteAcknowledgement(ctx sdk.Context, packet ibcexported.PacketI, ack ibcexported.Acknowledgement) error { portID := packet.GetDestPort() channelID := packet.GetDestChannel() capName := host.ChannelCapabilityPath(portID, channelID) @@ -134,15 +151,33 @@ func (k Keeper) WriteAcknowledgement(ctx sdk.Context, packet ibcexported.PacketI if !ok { return sdkioerrors.Wrapf(channeltypes.ErrChannelCapabilityNotFound, "could not retrieve channel capability at: %s", capName) } - ack := rawAcknowledgement{ - data: acknowledgement, - } + return k.WriteAcknowledgement(ctx, chanCap, packet, ack) +} + +// WriteAcknowledgement defines a wrapper function for the channel Keeper's function +// in order to expose it to the vibc IBC handler. +func (k Keeper) WriteAcknowledgement(ctx sdk.Context, chanCap *capability.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement) error { return k.channelKeeper.WriteAcknowledgement(ctx, chanCap, packet, ack) } -// ChanCloseInit defines a wrapper function for the channel Keeper's function +// ReceiveWriteOpenTryChannel wraps the keeper's WriteOpenTryChannel function. +func (k Keeper) ReceiveWriteOpenTryChannel(ctx sdk.Context, packet ibcexported.PacketI, order channeltypes.Order, connectionHops []string, version string) error { + portID := packet.GetDestPort() + channelID := packet.GetDestChannel() + counterparty := channeltypes.NewCounterparty(packet.GetSourcePort(), packet.GetSourceChannel()) + k.WriteOpenTryChannel(ctx, portID, channelID, order, connectionHops, counterparty, version) + return nil +} + +// WriteOpenTryChannel is a wrapper function for the channel Keeper's function +func (k Keeper) WriteOpenTryChannel(ctx sdk.Context, portID, channelID string, order channeltypes.Order, + connectionHops []string, counterparty channeltypes.Counterparty, version string) { + k.channelKeeper.WriteOpenTryChannel(ctx, portID, channelID, order, connectionHops, counterparty, version) +} + +// ReceiveChanCloseInit is a wrapper function for the channel Keeper's function // in order to expose it to the vibc IBC handler. -func (k Keeper) ChanCloseInit(ctx sdk.Context, portID, channelID string) error { +func (k Keeper) ReceiveChanCloseInit(ctx sdk.Context, portID, channelID string) error { capName := host.ChannelCapabilityPath(portID, channelID) chanCap, ok := k.GetCapability(ctx, capName) if !ok { @@ -155,20 +190,21 @@ func (k Keeper) ChanCloseInit(ctx sdk.Context, portID, channelID string) error { return nil } -// BindPort defines a wrapper function for the port Keeper's function in -// order to expose it to the vibc IBC handler. -func (k Keeper) BindPort(ctx sdk.Context, portID string) error { - _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) +// ReceiveBindPort is a wrapper function for the port Keeper's function in order +// to expose it to the vibc IBC handler. +func (k Keeper) ReceiveBindPort(ctx sdk.Context, portID string) error { + portPath := host.PortPath(portID) + _, ok := k.GetCapability(ctx, portPath) if ok { return fmt.Errorf("port %s is already bound", portID) } cap := k.portKeeper.BindPort(ctx, portID) - return k.ClaimCapability(ctx, cap, host.PortPath(portID)) + return k.ClaimCapability(ctx, cap, portPath) } -// TimeoutExecuted defines a wrapper function for the channel Keeper's function -// in order to expose it to the vibc IBC handler. -func (k Keeper) TimeoutExecuted(ctx sdk.Context, packet ibcexported.PacketI) error { +// ReceiveTimeoutExecuted is a wrapper function for the channel Keeper's +// function in order to expose it to the vibc IBC handler. +func (k Keeper) ReceiveTimeoutExecuted(ctx sdk.Context, packet ibcexported.PacketI) error { portID := packet.GetSourcePort() channelID := packet.GetSourceChannel() capName := host.ChannelCapabilityPath(portID, channelID) @@ -180,11 +216,12 @@ func (k Keeper) TimeoutExecuted(ctx sdk.Context, packet ibcexported.PacketI) err } // ClaimCapability allows the vibc module to claim a capability that IBC module -// passes to it +// passes to it. func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capability.Capability, name string) error { return k.scopedKeeper.ClaimCapability(ctx, cap, name) } +// GetCapability allows the vibc module to retrieve a capability. func (k Keeper) GetCapability(ctx sdk.Context, name string) (*capability.Capability, bool) { return k.scopedKeeper.GetCapability(ctx, name) } diff --git a/golang/cosmos/x/vibc/keeper/triggers.go b/golang/cosmos/x/vibc/keeper/triggers.go new file mode 100644 index 00000000000..10416d0cade --- /dev/null +++ b/golang/cosmos/x/vibc/keeper/triggers.go @@ -0,0 +1,101 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v6/modules/core/exported" + + "github.com/Agoric/agoric-sdk/golang/cosmos/vm" + "github.com/Agoric/agoric-sdk/golang/cosmos/x/vibc/types" +) + +func reifyPacket(packet ibcexported.PacketI) channeltypes.Packet { + height := packet.GetTimeoutHeight() + ctHeight := clienttypes.Height{ + RevisionHeight: height.GetRevisionHeight(), + RevisionNumber: height.GetRevisionNumber(), + } + return channeltypes.Packet{ + Sequence: packet.GetSequence(), + SourcePort: packet.GetSourcePort(), + SourceChannel: packet.GetSourceChannel(), + DestinationPort: packet.GetDestPort(), + DestinationChannel: packet.GetDestChannel(), + Data: packet.GetData(), + TimeoutHeight: ctHeight, + TimeoutTimestamp: packet.GetTimeoutTimestamp(), + } +} + +type WriteAcknowledgementEvent struct { + *vm.ActionHeader `actionType:"IBC_EVENT"` + Event string `json:"event" default:"writeAcknowledgement"` + Target string `json:"target"` + Packet channeltypes.Packet `json:"packet"` + Acknowledgement []byte `json:"acknowledgement"` + Relayer sdk.AccAddress `json:"relayer"` +} + +func (k Keeper) TriggerWriteAcknowledgement( + ctx sdk.Context, + target string, + packet ibcexported.PacketI, + acknowledgement ibcexported.Acknowledgement, +) error { + event := WriteAcknowledgementEvent{ + Target: target, + Packet: reifyPacket(packet), + Acknowledgement: acknowledgement.Acknowledgement(), + } + + err := k.PushAction(ctx, event) + if err != nil { + return err + } + + return nil +} + +func (k Keeper) TriggerOnAcknowledgementPacket( + ctx sdk.Context, + target string, + packet ibcexported.PacketI, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + event := types.AcknowledgementPacketEvent{ + Target: target, + Packet: reifyPacket(packet), + Acknowledgement: acknowledgement, + Relayer: relayer, + } + + err := k.PushAction(ctx, event) + if err != nil { + return err + } + + return nil +} + +func (k Keeper) TriggerOnTimeoutPacket( + ctx sdk.Context, + target string, + packet ibcexported.PacketI, + relayer sdk.AccAddress, +) error { + event := types.TimeoutPacketEvent{ + Target: target, + Packet: reifyPacket(packet), + Relayer: relayer, + } + + err := k.PushAction(ctx, event) + if err != nil { + return err + } + + return nil +} diff --git a/golang/cosmos/x/vibc/module.go b/golang/cosmos/x/vibc/module.go index e1027c7b213..715f8f68042 100644 --- a/golang/cosmos/x/vibc/module.go +++ b/golang/cosmos/x/vibc/module.go @@ -71,13 +71,15 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic keeper Keeper + bankKeeper types.BankKeeper } // NewAppModule creates a new AppModule Object -func NewAppModule(k Keeper) AppModule { +func NewAppModule(k Keeper, bankKeeper types.BankKeeper) AppModule { am := AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: k, + bankKeeper: bankKeeper, } return am } @@ -104,7 +106,7 @@ func (AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { // Route implements the AppModule interface func (am AppModule) Route() sdk.Route { - return sdk.NewRoute(RouterKey, NewHandler(am.keeper)) + return sdk.NewRoute(RouterKey, NewHandler(am.keeper, am.bankKeeper)) } // QuerierRoute implements the AppModule interface diff --git a/golang/cosmos/x/vibc/types/expected_keepers.go b/golang/cosmos/x/vibc/types/expected_keepers.go index b94c33c0445..dacf3d45947 100644 --- a/golang/cosmos/x/vibc/types/expected_keepers.go +++ b/golang/cosmos/x/vibc/types/expected_keepers.go @@ -9,8 +9,13 @@ import ( ibcexported "github.com/cosmos/ibc-go/v6/modules/core/exported" ) +type BankKeeper interface { + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin +} + // ChannelKeeper defines the expected IBC channel keeper type ChannelKeeper interface { + GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channel.Channel, found bool) SendPacket( ctx sdk.Context, @@ -26,6 +31,8 @@ type ChannelKeeper interface { portCap *capability.Capability, counterparty channel.Counterparty, version string) (string, *capability.Capability, error) WriteOpenInitChannel(ctx sdk.Context, portID, channelID string, order channel.Order, connectionHops []string, counterparty channel.Counterparty, version string) + WriteOpenTryChannel(ctx sdk.Context, portID, channelID string, order channel.Order, + connectionHops []string, counterparty channel.Counterparty, version string) ChanCloseInit(ctx sdk.Context, portID, channelID string, chanCap *capability.Capability) error TimeoutExecuted(ctx sdk.Context, channelCap *capability.Capability, packet ibcexported.PacketI) error } @@ -44,3 +51,9 @@ type ConnectionKeeper interface { type PortKeeper interface { BindPort(ctx sdk.Context, portID string) *capability.Capability } + +// ScopedKeeper defines the expected scoped capability keeper +type ScopedKeeper interface { + ClaimCapability(ctx sdk.Context, cap *capability.Capability, name string) error + GetCapability(ctx sdk.Context, name string) (*capability.Capability, bool) +} diff --git a/golang/cosmos/x/vibc/ibc.go b/golang/cosmos/x/vibc/types/ibc_module.go similarity index 54% rename from golang/cosmos/x/vibc/ibc.go rename to golang/cosmos/x/vibc/types/ibc_module.go index 1d0533247b9..dd7d1be4b2a 100644 --- a/golang/cosmos/x/vibc/ibc.go +++ b/golang/cosmos/x/vibc/types/ibc_module.go @@ -1,161 +1,58 @@ -package vibc +package types import ( - "context" - "encoding/json" - "fmt" - sdkioerrors "cosmossdk.io/errors" "github.com/Agoric/agoric-sdk/golang/cosmos/vm" capability "github.com/cosmos/cosmos-sdk/x/capability/types" channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" host "github.com/cosmos/ibc-go/v6/modules/core/24-host" + ibckeeper "github.com/cosmos/ibc-go/v6/modules/core/keeper" "github.com/cosmos/ibc-go/v6/modules/core/exported" sdk "github.com/cosmos/cosmos-sdk/types" ) -var ( - _ porttypes.IBCModule = IBCModule{} +const ( + // AsyncVersions is a flag that indicates whether the IBC module supports + // asynchronous versions. If it does, then the VM must supply an empty + // version string to indicate that the VM explicitly (possibly async) + // performs the Write* method. + AsyncVersions = ibckeeper.AsyncVersionNegotiation ) -type IBCModule struct { - keeper Keeper -} - -type portMessage struct { // comes from swingset's IBC handler - Type string `json:"type"` // IBC_METHOD - Method string `json:"method"` - Packet channeltypes.Packet `json:"packet"` - RelativeTimeoutNs uint64 `json:"relativeTimeoutNs,string"` - Order string `json:"order"` - Hops []string `json:"hops"` - Version string `json:"version"` - Ack []byte `json:"ack"` -} +var ( + _ porttypes.IBCModule = (*IBCModule)(nil) +) -func stringToOrder(order string) channeltypes.Order { - switch order { - case "ORDERED": - return channeltypes.ORDERED - case "UNORDERED": - return channeltypes.UNORDERED - default: - return channeltypes.NONE - } +type IBCModuleImpl interface { + ClaimCapability(ctx sdk.Context, channelCap *capability.Capability, path string) error + GetChannel(ctx sdk.Context, portID, channelID string) (channeltypes.Channel, bool) + PushAction(ctx sdk.Context, action vm.Action) error } -func orderToString(order channeltypes.Order) string { - switch order { - case channeltypes.ORDERED: - return "ORDERED" - case channeltypes.UNORDERED: - return "UNORDERED" - default: - return "NONE" - } +type IBCModule struct { + impl IBCModuleImpl } -func NewIBCModule(keeper Keeper) IBCModule { +func NewIBCModule(impl IBCModuleImpl) IBCModule { return IBCModule{ - keeper: keeper, - } -} - -func (ch IBCModule) Receive(cctx context.Context, str string) (ret string, err error) { - // fmt.Println("ibc.go downcall", str) - ctx := sdk.UnwrapSDKContext(cctx) - keeper := ch.keeper - - msg := new(portMessage) - err = json.Unmarshal([]byte(str), &msg) - if err != nil { - return ret, err - } - - if msg.Type != "IBC_METHOD" { - return "", fmt.Errorf(`channel handler only accepts messages of "type": "IBC_METHOD"`) + impl: impl, } - - switch msg.Method { - case "sendPacket": - timeoutTimestamp := msg.Packet.TimeoutTimestamp - if msg.Packet.TimeoutHeight.IsZero() && msg.Packet.TimeoutTimestamp == 0 { - // Use the relative timeout if no absolute timeout is specifiied. - timeoutTimestamp = uint64(ctx.BlockTime().UnixNano()) + msg.RelativeTimeoutNs - } - - seq, err := keeper.SendPacket( - ctx, - msg.Packet.SourcePort, - msg.Packet.SourceChannel, - msg.Packet.TimeoutHeight, - timeoutTimestamp, - msg.Packet.Data, - ) - if err == nil { - // synthesize the sent packet - packet := channeltypes.NewPacket( - msg.Packet.Data, seq, - msg.Packet.SourcePort, msg.Packet.SourceChannel, - msg.Packet.DestinationPort, msg.Packet.DestinationChannel, - msg.Packet.TimeoutHeight, timeoutTimestamp, - ) - bytes, err := json.Marshal(&packet) - if err == nil { - ret = string(bytes) - } - } - - case "receiveExecuted": - err = keeper.WriteAcknowledgement(ctx, msg.Packet, msg.Ack) - if err == nil { - ret = "true" - } - - case "startChannelOpenInit": - err = keeper.ChanOpenInit( - ctx, stringToOrder(msg.Order), msg.Hops, - msg.Packet.SourcePort, - msg.Packet.DestinationPort, - msg.Version, - ) - if err == nil { - ret = "true" - } - - case "startChannelCloseInit": - err = keeper.ChanCloseInit(ctx, msg.Packet.SourcePort, msg.Packet.SourceChannel) - if err == nil { - ret = "true" - } - - case "bindPort": - err = keeper.BindPort(ctx, msg.Packet.SourcePort) - if err == nil { - ret = "true" - } - - case "timeoutExecuted": - err = keeper.TimeoutExecuted(ctx, msg.Packet) - if err == nil { - ret = "true" - } - - default: - err = fmt.Errorf("unrecognized method %s", msg.Method) - } - - // fmt.Println("ibc.go downcall reply", ret, err) - return } -func (im IBCModule) PushAction(ctx sdk.Context, action vm.Action) error { - // fmt.Println("ibc.go upcall", send) - return im.keeper.PushAction(ctx, action) - // fmt.Println("ibc.go upcall reply", reply, err) +type ChannelOpenInitEvent struct { + *vm.ActionHeader `actionType:"IBC_EVENT"` + Event string `json:"event" default:"channelOpenInit"` + Target string `json:"target,omitempty"` + Order string `json:"order"` + ConnectionHops []string `json:"connectionHops"` + PortID string `json:"portID"` + ChannelID string `json:"channelID"` + Counterparty channeltypes.Counterparty `json:"counterparty"` + Version string `json:"version"` + AsyncVersions bool `json:"asyncVersions"` } // Implement IBCModule callbacks @@ -169,21 +66,45 @@ func (im IBCModule) OnChanOpenInit( counterparty channeltypes.Counterparty, version string, ) (string, error) { - return "", sdkioerrors.Wrap( - channeltypes.ErrChannelNotFound, - fmt.Sprintf("vibc does not allow synthetic channelOpenInit for port %s", portID), - ) + event := ChannelOpenInitEvent{ + Order: orderToString(order), + ConnectionHops: connectionHops, + PortID: portID, + ChannelID: channelID, + Counterparty: counterparty, + Version: version, + AsyncVersions: AsyncVersions, + } + + err := im.impl.PushAction(ctx, event) + if err != nil { + return "", err + } + + // Claim channel capability passed back by IBC module + if err := im.impl.ClaimCapability(ctx, channelCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + if !event.AsyncVersions { + // We have to supply a synchronous version, so just echo back the one they sent. + return event.Version, nil + } + + return "", nil } -type channelOpenTryEvent struct { +type ChannelOpenTryEvent struct { *vm.ActionHeader `actionType:"IBC_EVENT"` Event string `json:"event" default:"channelOpenTry"` + Target string `json:"target,omitempty"` Order string `json:"order"` ConnectionHops []string `json:"connectionHops"` PortID string `json:"portID"` ChannelID string `json:"channelID"` Counterparty channeltypes.Counterparty `json:"counterparty"` Version string `json:"version"` + AsyncVersions bool `json:"asyncVersions"` } func (im IBCModule) OnChanOpenTry( @@ -196,29 +117,37 @@ func (im IBCModule) OnChanOpenTry( counterparty channeltypes.Counterparty, counterpartyVersion string, ) (string, error) { - event := channelOpenTryEvent{ + event := ChannelOpenTryEvent{ Order: orderToString(order), ConnectionHops: connectionHops, PortID: portID, ChannelID: channelID, Counterparty: counterparty, - Version: counterpartyVersion, // TODO: don't just use the counterparty version + Version: counterpartyVersion, + AsyncVersions: AsyncVersions, } - err := im.PushAction(ctx, event) + err := im.impl.PushAction(ctx, event) if err != nil { return "", err } // Claim channel capability passed back by IBC module - if err = im.keeper.ClaimCapability(ctx, channelCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + if err = im.impl.ClaimCapability(ctx, channelCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { return "", sdkioerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, err.Error()) } - return event.Version, err + if !event.AsyncVersions { + // We have to supply a synchronous version, so just echo back the one they sent. + return event.Version, nil + } + + // Use an empty version string to indicate that the VM explicitly (possibly + // async) performs the WriteOpenTryChannel. + return "", nil } -type channelOpenAckEvent struct { +type ChannelOpenAckEvent struct { *vm.ActionHeader `actionType:"IBC_EVENT"` Event string `json:"event" default:"channelOpenAck"` PortID string `json:"portID"` @@ -237,10 +166,10 @@ func (im IBCModule) OnChanOpenAck( ) error { // We don't care if the channel was found. If it wasn't then GetChannel // returns an empty channel object that we can still use without crashing. - channel, _ := im.keeper.GetChannel(ctx, portID, channelID) + channel, _ := im.impl.GetChannel(ctx, portID, channelID) channel.Counterparty.ChannelId = counterpartyChannelID - event := channelOpenAckEvent{ + event := ChannelOpenAckEvent{ PortID: portID, ChannelID: channelID, CounterpartyVersion: counterpartyVersion, @@ -248,12 +177,13 @@ func (im IBCModule) OnChanOpenAck( ConnectionHops: channel.ConnectionHops, } - return im.PushAction(ctx, event) + return im.impl.PushAction(ctx, event) } -type channelOpenConfirmEvent struct { +type ChannelOpenConfirmEvent struct { *vm.ActionHeader `actionType:"IBC_EVENT"` Event string `json:"event" default:"channelOpenConfirm"` + Target string `json:"target,omitempty"` PortID string `json:"portID"` ChannelID string `json:"channelID"` } @@ -263,17 +193,18 @@ func (im IBCModule) OnChanOpenConfirm( portID, channelID string, ) error { - event := channelOpenConfirmEvent{ + event := ChannelOpenConfirmEvent{ PortID: portID, ChannelID: channelID, } - return im.PushAction(ctx, event) + return im.impl.PushAction(ctx, event) } -type channelCloseInitEvent struct { +type ChannelCloseInitEvent struct { *vm.ActionHeader `actionType:"IBC_EVENT"` Event string `json:"event" default:"channelCloseInit"` + Target string `json:"target,omitempty"` PortID string `json:"portID"` ChannelID string `json:"channelID"` } @@ -283,18 +214,19 @@ func (im IBCModule) OnChanCloseInit( portID, channelID string, ) error { - event := channelCloseInitEvent{ + event := ChannelCloseInitEvent{ PortID: portID, ChannelID: channelID, } - err := im.PushAction(ctx, event) + err := im.impl.PushAction(ctx, event) return err } -type channelCloseConfirmEvent struct { +type ChannelCloseConfirmEvent struct { *vm.ActionHeader `actionType:"IBC_EVENT"` Event string `json:"event" default:"channelCloseConfirm"` + Target string `json:"target,omitempty"` PortID string `json:"portID"` ChannelID string `json:"channelID"` } @@ -304,19 +236,21 @@ func (im IBCModule) OnChanCloseConfirm( portID, channelID string, ) error { - event := channelCloseConfirmEvent{ + event := ChannelCloseConfirmEvent{ PortID: portID, ChannelID: channelID, } - err := im.PushAction(ctx, event) + err := im.impl.PushAction(ctx, event) return err } -type receivePacketEvent struct { +type ReceivePacketEvent struct { *vm.ActionHeader `actionType:"IBC_EVENT"` Event string `json:"event" default:"receivePacket"` + Target string `json:"target,omitempty"` Packet channeltypes.Packet `json:"packet"` + Relayer sdk.AccAddress `json:"relayer"` } func (im IBCModule) OnRecvPacket( @@ -332,11 +266,12 @@ func (im IBCModule) OnRecvPacket( // and also "rly tx xfer"-- they both are trying to relay // the same packets. - event := receivePacketEvent{ - Packet: packet, + event := ReceivePacketEvent{ + Packet: packet, + Relayer: relayer, } - err := im.PushAction(ctx, event) + err := im.impl.PushAction(ctx, event) if err != nil { return channeltypes.NewErrorAcknowledgement(err) } @@ -344,11 +279,13 @@ func (im IBCModule) OnRecvPacket( return nil } -type acknowledgementPacketEvent struct { +type AcknowledgementPacketEvent struct { *vm.ActionHeader `actionType:"IBC_EVENT"` Event string `json:"event" default:"acknowledgementPacket"` + Target string `json:"target,omitempty"` Packet channeltypes.Packet `json:"packet"` Acknowledgement []byte `json:"acknowledgement"` + Relayer sdk.AccAddress `json:"relayer"` } func (im IBCModule) OnAcknowledgementPacket( @@ -357,12 +294,13 @@ func (im IBCModule) OnAcknowledgementPacket( acknowledgement []byte, relayer sdk.AccAddress, ) error { - event := acknowledgementPacketEvent{ + event := AcknowledgementPacketEvent{ Packet: packet, Acknowledgement: acknowledgement, + Relayer: relayer, } - err := im.PushAction(ctx, event) + err := im.impl.PushAction(ctx, event) if err != nil { return err } @@ -370,10 +308,12 @@ func (im IBCModule) OnAcknowledgementPacket( return nil } -type timeoutPacketEvent struct { +type TimeoutPacketEvent struct { *vm.ActionHeader `actionType:"IBC_EVENT"` Event string `json:"event" default:"timeoutPacket"` + Target string `json:"target,omitempty"` Packet channeltypes.Packet `json:"packet"` + Relayer sdk.AccAddress `json:"relayer"` } func (im IBCModule) OnTimeoutPacket( @@ -381,11 +321,12 @@ func (im IBCModule) OnTimeoutPacket( packet channeltypes.Packet, relayer sdk.AccAddress, ) error { - event := timeoutPacketEvent{ - Packet: packet, + event := TimeoutPacketEvent{ + Packet: packet, + Relayer: relayer, } - err := im.PushAction(ctx, event) + err := im.impl.PushAction(ctx, event) if err != nil { return err } diff --git a/golang/cosmos/x/vibc/types/receiver.go b/golang/cosmos/x/vibc/types/receiver.go new file mode 100644 index 00000000000..9dfd43b0957 --- /dev/null +++ b/golang/cosmos/x/vibc/types/receiver.go @@ -0,0 +1,160 @@ +package types + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/Agoric/agoric-sdk/golang/cosmos/vm" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" + + "github.com/cosmos/ibc-go/v6/modules/core/exported" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + _ vm.PortHandler = Receiver{} + _ exported.Acknowledgement = (*rawAcknowledgement)(nil) +) + +type ReceiverImpl interface { + ReceiveSendPacket(ctx sdk.Context, packet exported.PacketI) (uint64, error) + ReceiveWriteAcknowledgement(ctx sdk.Context, packet exported.PacketI, ack exported.Acknowledgement) error + ReceiveChanOpenInit(ctx sdk.Context, order channeltypes.Order, hops []string, sourcePort, destinationPort, version string) error + ReceiveWriteOpenTryChannel(ctx sdk.Context, packet exported.PacketI, order channeltypes.Order, connectionHops []string, version string) error + ReceiveChanCloseInit(ctx sdk.Context, sourcePort, sourceChannel string) error + ReceiveBindPort(ctx sdk.Context, sourcePort string) error + ReceiveTimeoutExecuted(ctx sdk.Context, packet exported.PacketI) error +} + +type Receiver struct { + impl ReceiverImpl +} + +func NewReceiver(impl ReceiverImpl) Receiver { + return Receiver{ + impl: impl, + } +} + +type portMessage struct { // comes from swingset's IBC handler + Type string `json:"type"` // IBC_METHOD + Method string `json:"method"` + Packet channeltypes.Packet `json:"packet"` + RelativeTimeoutNs uint64 `json:"relativeTimeoutNs,string"` + Order string `json:"order"` + Hops []string `json:"hops"` + Version string `json:"version"` + Ack []byte `json:"ack"` +} + +func stringToOrder(order string) channeltypes.Order { + switch order { + case "ORDERED": + return channeltypes.ORDERED + case "UNORDERED": + return channeltypes.UNORDERED + default: + return channeltypes.NONE + } +} + +func orderToString(order channeltypes.Order) string { + switch order { + case channeltypes.ORDERED: + return "ORDERED" + case channeltypes.UNORDERED: + return "UNORDERED" + default: + return "NONE" + } +} + +type rawAcknowledgement struct { + data []byte +} + +func (r rawAcknowledgement) Acknowledgement() []byte { + return r.data +} + +func (r rawAcknowledgement) Success() bool { + return true +} + +func (ir Receiver) Receive(cctx context.Context, str string) (ret string, err error) { + ctx := sdk.UnwrapSDKContext(cctx) + impl := ir.impl + + msg := new(portMessage) + err = json.Unmarshal([]byte(str), &msg) + if err != nil { + return "", err + } + + if msg.Type != "IBC_METHOD" { + return "", fmt.Errorf(`channel handler only accepts messages of "type": "IBC_METHOD"`) + } + + switch msg.Method { + case "sendPacket": + timeoutTimestamp := msg.Packet.TimeoutTimestamp + if msg.Packet.TimeoutHeight.IsZero() && timeoutTimestamp == 0 { + // Use the relative timeout if no absolute timeout is specifiied. + timeoutTimestamp = uint64(ctx.BlockTime().UnixNano()) + msg.RelativeTimeoutNs + } + + packet := channeltypes.NewPacket( + msg.Packet.Data, 0, + msg.Packet.SourcePort, msg.Packet.SourceChannel, + msg.Packet.DestinationPort, msg.Packet.DestinationChannel, + msg.Packet.TimeoutHeight, timeoutTimestamp, + ) + seq, err := impl.ReceiveSendPacket(ctx, packet) + if err == nil { + packet.Sequence = seq + bytes, err := json.Marshal(&packet) + if err == nil { + ret = string(bytes) + } + } + + case "tryOpenExecuted": + err = impl.ReceiveWriteOpenTryChannel( + ctx, msg.Packet, + stringToOrder(msg.Order), msg.Hops, msg.Version, + ) + + case "receiveExecuted": + ack := rawAcknowledgement{ + data: msg.Ack, + } + err = impl.ReceiveWriteAcknowledgement(ctx, msg.Packet, ack) + + case "startChannelOpenInit": + err = impl.ReceiveChanOpenInit( + ctx, stringToOrder(msg.Order), msg.Hops, + msg.Packet.SourcePort, + msg.Packet.DestinationPort, + msg.Version, + ) + + case "startChannelCloseInit": + err = impl.ReceiveChanCloseInit(ctx, msg.Packet.SourcePort, msg.Packet.SourceChannel) + + case "bindPort": + err = impl.ReceiveBindPort(ctx, msg.Packet.SourcePort) + + case "timeoutExecuted": + err = impl.ReceiveTimeoutExecuted(ctx, msg.Packet) + + default: + err = fmt.Errorf("unrecognized method %s", msg.Method) + } + + if ret == "" && err == nil { + ret = "true" + } + return +} diff --git a/packages/vats/src/ibc.js b/packages/vats/src/ibc.js index dcec6802d5f..c0097b39c92 100644 --- a/packages/vats/src/ibc.js +++ b/packages/vats/src/ibc.js @@ -345,7 +345,7 @@ export const prepareIBCProtocol = (zone, { makeVowKit, watch, when }) => { srcPortToOutbounds.init(portID, harden([ob])); } - // Initialise the channel, which automatic relayers should pick up. + // Initialise the channel, which a listening relayer should pick up. const packet = { source_port: portID, destination_port: rPortID, @@ -397,6 +397,37 @@ export const prepareIBCProtocol = (zone, { makeVowKit, watch, when }) => { console.info('IBC fromBridge', obj); await null; switch (obj.event) { + case 'channelOpenInit': { + const { + channelID, + portID, + counterparty: { port_id: rPortID, channel_id: rChannelID }, + connectionHops: hops, + order, + version, + asyncVersions, + } = obj; + if (!asyncVersions) { + // Synchronous versions have already been negotiated. + break; + } + + // We have async version negotiation, so we must call back before the + // channel can make progress. + // We just use the provided version without failing. + await util.downcall('initOpenExecuted', { + packet: { + source_port: portID, + source_channel: channelID, + destination_port: rPortID, + destination_channel: rChannelID, + }, + order, + version, + hops, + }); + break; + } case 'channelOpenTry': { // They're (more or less politely) asking if we are listening, so make an attempt. const { @@ -407,6 +438,7 @@ export const prepareIBCProtocol = (zone, { makeVowKit, watch, when }) => { order, version, counterpartyVersion: rVersion, + asyncVersions, } = obj; const localAddr = `/ibc-port/${portID}/${order.toLowerCase()}/${version}`; @@ -448,10 +480,23 @@ export const prepareIBCProtocol = (zone, { makeVowKit, watch, when }) => { channelKeyToInfo.init(channelKey, obj); try { - if (negotiatedVersion !== version) { - // Too late; the relayer gave us a version we didn't like. + if (asyncVersions) { + // We have async version negotiation, so we must call back now. + await util.downcall('tryOpenExecuted', { + packet: { + source_port: rPortID, + source_channel: rChannelID, + destination_port: portID, + destination_channel: channelID, + }, + order, + version: negotiatedVersion, + hops, + }); + } else if (negotiatedVersion !== version) { + // Too late; the other side gave us a version we didn't like. throw Error( - `${channelKey}: negotiated version was ${negotiatedVersion}; rejecting ${version}`, + `${channelKey}: async negotiated version was ${negotiatedVersion} but synchronous version was ${version}`, ); } } catch (e) {