Skip to content

Commit

Permalink
feat: Add Eureka receive packet entrypoint call
Browse files Browse the repository at this point in the history
  • Loading branch information
kulikthebird committed Feb 21, 2025
1 parent 2b4efde commit 3fe0582
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 5 deletions.
13 changes: 13 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
ibcclienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
ibcconnectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types"
porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types"
ibcapi "github.com/cosmos/ibc-go/v10/modules/core/api"
ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported"
ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper"
ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint"
Expand Down Expand Up @@ -129,6 +130,7 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/CosmWasm/wasmd/x/wasm"
"github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
)
Expand Down Expand Up @@ -657,6 +659,17 @@ func NewWasmApp(
AddRoute(icahosttypes.SubModuleName, icaHostStack)
app.IBCKeeper.SetRouter(ibcRouter)

// Create fee enabled wasm ibc Stack
wasmEurekaModule := keeper.NewEurekaHandler(app.WasmKeeper)

ibcRouterV2 := ibcapi.NewRouter().
AddRoute(wasmtypes.ModuleName, wasmEurekaModule)
// TODO tkulik: Add other routers ?
// AddRoute(ibctransfertypes.ModuleName, transferStack).
// AddRoute(icacontrollertypes.SubModuleName, icaControllerStack).
// AddRoute(icahosttypes.SubModuleName, icaHostStack)
app.IBCKeeper.SetRouterV2(ibcRouterV2)

clientKeeper := app.IBCKeeper.ClientKeeper
storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider()

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/CosmWasm/wasmd
go 1.23.6

require (
github.com/CosmWasm/wasmvm/v2 v2.2.1
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250213132354-b95e700963e8
github.com/cosmos/cosmos-proto v1.0.0-beta.5
github.com/cosmos/cosmos-sdk v0.50.11
github.com/cosmos/gogogateway v1.2.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CosmWasm/wasmvm/v2 v2.2.1 h1:cmOnM+TDfUl2VRugeo1eJBw4U/Lw0WLviuQHKSo9DVQ=
github.com/CosmWasm/wasmvm/v2 v2.2.1/go.mod h1:bMhLQL4Yp9CzJi9A83aR7VO9wockOsSlZbT4ztOl6bg=
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250213132354-b95e700963e8 h1:6M+5Sc+w5KaHPfw1EjSXCly58zzEH51/wm7MOPL4GSg=
github.com/CosmWasm/wasmvm/v2 v2.2.2-0.20250213132354-b95e700963e8/go.mod h1:bMhLQL4Yp9CzJi9A83aR7VO9wockOsSlZbT4ztOl6bg=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q=
github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
Expand Down
81 changes: 81 additions & 0 deletions tests/e2e/eureka_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package e2e_test

import (
"testing"

sdkmath "cosmossdk.io/math"
ibcfee "github.com/cosmos/ibc-go/v10/modules/apps/29-fee/types"
ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
ibctesting "github.com/cosmos/ibc-go/v10/testing"
"github.com/stretchr/testify/require"

"github.com/CosmWasm/wasmd/app"
"github.com/CosmWasm/wasmd/tests/e2e"
wasmibctesting "github.com/CosmWasm/wasmd/tests/wasmibctesting"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2"
)

func TestEurekaReceiveEntrypoint(t *testing.T) {
coord := wasmibctesting.NewCoordinator(t, 2)
chainA := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(1)))
chainB := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(2)))
contractAddr := e2e.InstantiateStargateReflectContract(t, chainA)
chainA.Fund(contractAddr, sdkmath.NewIntFromUint64(1_000_000_000))
marshaler := app.MakeEncodingConfig(t).Codec

contractCode := chainA.StoreCodeFile("./testdata/eureka.wasm").CodeID
contractAddrA := chainA.InstantiateContract(contractCode, []byte(`{}`))
contractPortA := wasmkeeper.PortIDForContract(contractAddrA)
contractPortB := "wasm.ChainBContractAddr"

require.NotEmpty(t, contractAddrA)

path := wasmibctesting.NewWasmPath(chainA, chainB)
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
PortID: contractPortA,
Version: string(marshaler.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.V1})),
Order: channeltypes.UNORDERED,
}
path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{
PortID: contractPortB,
Version: string(marshaler.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.V1})),
Order: channeltypes.UNORDERED,
}

// TODO tkulik: Is it necessary?
path.EndpointA.Counterparty.ChannelConfig = &ibctesting.ChannelConfig{
PortID: contractPortB,
Version: string(marshaler.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.V1})),
Order: channeltypes.UNORDERED,
}
path.EndpointB.Counterparty.ChannelConfig = &ibctesting.ChannelConfig{
PortID: contractPortA,
Version: string(marshaler.MustMarshalJSON(&ibcfee.Metadata{FeeVersion: ibcfee.Version, AppVersion: ibctransfertypes.V1})),
Order: channeltypes.UNORDERED,
}

path.Path.SetupV2()

timeoutTimestamp := chainA.GetTimeoutTimestampSecs()

// TODO tkulik: Not sure about if endpoint A or B should be source
_, err := path.EndpointA.MsgSendPacket(timeoutTimestamp, mockv2.NewMockPayload(contractPortA, contractPortB))
require.NoError(t, err)

// eurekaMsg := &wasmvmtypes.EurekaMsg{
// SendPacket: &wasmvmtypes.EurekaSendPacketMsg{
// Payloads: []wasmvmtypes.EurekaPayload{{
// DestinationPort: "port-1",
// Version: "v1",
// Encoding: icatypes.EncodingProto3JSON,
// Value: []byte{},
// }},
// ChannelID: "channel-1",
// Timeout: 100,
// },
// }

// _, err = chain.SendMsgs(&eurekaMsg)
}
Binary file added tests/e2e/testdata/eureka.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/integration/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func setupTest(t *testing.T) testData {

ctx, keepers := keeper.CreateTestInput(t, false, []string{
"iterator", "staking", "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3",
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1", "cosmwasm_2_2",
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1", "cosmwasm_2_2", "eureka",
})
encConf := keeper.MakeEncodingConfig(t)
queryRouter := baseapp.NewGRPCQueryRouter()
Expand Down
1 change: 1 addition & 0 deletions x/wasm/keeper/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ func BuiltInCapabilities() []string {
"cosmwasm_2_0",
"cosmwasm_2_1",
"cosmwasm_2_2",
"eureka",
}
}
169 changes: 169 additions & 0 deletions x/wasm/keeper/eureka.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package keeper

import (
"time"

wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported"

errorsmod "cosmossdk.io/errors"

"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/CosmWasm/wasmd/x/wasm/types"
channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"
ibcapi "github.com/cosmos/ibc-go/v10/modules/core/api"
)

var _ ibcapi.IBCModule = EurekaHandler{}

// internal interface that is implemented by ibc middleware
type appVersionGetter interface {
// GetAppVersion returns the application level version with all middleware data stripped out
GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool)
}

type EurekaHandler struct {
keeper types.EurekaContractKeeper
}

func NewEurekaHandler(keeper types.EurekaContractKeeper) EurekaHandler {
return EurekaHandler{
keeper: keeper,
}
}

func (module EurekaHandler) OnSendPacket(
ctx sdk.Context,
sourceClient string,
destinationClient string,
sequence uint64,
payload channeltypesv2.Payload,
signer sdk.AccAddress,
) error {
return nil
}

func (module EurekaHandler) OnRecvPacket(
ctx sdk.Context,
sourceClient string,
destinationClient string,
sequence uint64,
payload channeltypesv2.Payload,
relayer sdk.AccAddress,
) channeltypesv2.RecvPacketResult {
contractAddr, err := ContractFromPortID(payload.DestinationPort)
if err != nil {
// this must not happen as ports were registered before
panic(errorsmod.Wrapf(err, "contract port id"))
}

em := sdk.NewEventManager()
msg := wasmvmtypes.EurekaPacketReceiveMsg{Packet: newEurekaPacket(payload), Relayer: relayer.String()}

ack, err := module.keeper.OnRecvEurekaPacket(ctx.WithEventManager(em), contractAddr, msg)

if err != nil {
ack = CreateErrorAcknowledgement(err)
// the state gets reverted, so we drop all captured events
} else if ack == nil || ack.Success() {
// emit all contract and submessage events on success
// nil ack is a success case, see: https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/keeper/msg_server.go#L453
ctx.EventManager().EmitEvents(em.Events())
}
types.EmitAcknowledgementEvent(ctx, contractAddr, ack, err)
return ack
}

func (module EurekaHandler) OnTimeoutPacket(
ctx sdk.Context,
sourceClient string,
destinationClient string,
sequence uint64,
payload channeltypesv2.Payload,
relayer sdk.AccAddress,
) error {
return nil
}

func (module EurekaHandler) OnAcknowledgementPacket(
ctx sdk.Context,
sourceClient string,
destinationClient string,
sequence uint64,
acknowledgement []byte,
payload channeltypesv2.Payload,
relayer sdk.AccAddress,
) error {
return nil
}

// The method calls the contract to process the incoming Eureka packet. The contract fully owns the data processing and
// returns the acknowledgement data for the chain level. This allows custom applications and protocols on top
// of IBC Eureka.
func (k Keeper) OnRecvEurekaPacket(
ctx sdk.Context,
contractAddr sdk.AccAddress,
msg wasmvmtypes.EurekaPacketReceiveMsg,
) (ibcexported.Acknowledgement, error) {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-recv-packet")
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr)
if err != nil {
return nil, err
}

env := types.NewEnv(ctx, contractAddr)
querier := k.newQueryHandler(ctx, contractAddr)

gasLeft := k.runtimeGasForContract(ctx)
res, gasUsed, execErr := k.wasmVM.EUPacketReceive(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gasLeft, costJSONDeserialization)
k.consumeRuntimeGas(ctx, gasUsed)
if execErr != nil {
panic(execErr) // let the contract fully abort an IBC packet receive.
// Throwing a panic here instead of an error ack will revert
// all state downstream and not persist any data in ibc-go.
// This can be triggered by throwing a panic in the contract
}
if res == nil {
// If this gets executed, that's a bug in wasmvm
return nil, errorsmod.Wrap(types.ErrVMError, "internal wasmvm error")
}
if res.Err != "" {
// return error ACK with non-redacted contract message, state will be reverted
return channeltypes.Acknowledgement{
Response: &channeltypes.Acknowledgement_Error{Error: res.Err},
}, nil
}
// note submessage reply results can overwrite the `Acknowledgement` data
data, err := k.handleContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Acknowledgement, res.Ok.Events)
if err != nil {
// submessage errors result in error ACK with state reverted. Error message is redacted
return nil, err
}

// TODO tkulik: What about this? Should we support async?
// if data == nil {
// // Protocol might never write acknowledgement or contract
// // wants async acknowledgements, we don't know.
// // So store the packet for later.
// err = k.StoreAsyncAckPacket(ctx, convertPacket(msg.Packet))
// if err != nil {
// return nil, err
// }
// return nil, nil
// }

// success ACK, state will be committed
return ContractConfirmStateAck(data), nil
}

func newEurekaPacket(payload channeltypesv2.Payload) wasmvmtypes.EurekaPayload {
return wasmvmtypes.EurekaPayload{
DestinationPort: payload.DestinationPort,
Version: payload.Version,
Encoding: payload.Encoding,
Value: payload.Value,
}
}
2 changes: 1 addition & 1 deletion x/wasm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var replierWasm []byte

var AvailableCapabilities = []string{
"iterator", "staking", "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3",
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1", "cosmwasm_2_2",
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1", "cosmwasm_2_2", "eureka",
}

func TestNewKeeper(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions x/wasm/keeper/wasmtesting/mock_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type MockWasmEngine struct {
IBCPacketTimeoutFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketTimeoutMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResult, uint64, error)
IBCSourceCallbackFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCSourceCallbackMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResult, uint64, error)
IBCDestinationCallbackFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCDestinationCallbackMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResult, uint64, error)
EUPacketReceiveFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.EurekaPacketReceiveMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCReceiveResult, uint64, error)
PinFn func(checksum wasmvm.Checksum) error
UnpinFn func(checksum wasmvm.Checksum) error
GetMetricsFn func() (*wasmvmtypes.Metrics, error)
Expand Down Expand Up @@ -105,6 +106,13 @@ func (m MockWasmEngine) IBCDestinationCallback(codeID wasmvm.Checksum, env wasmv
return m.IBCDestinationCallbackFn(codeID, env, msg, store, goapi, querier, gasMeter, gasLimit, deserCost)
}

func (m *MockWasmEngine) EUPacketReceive(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.EurekaPacketReceiveMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCReceiveResult, uint64, error) {
if m.IBCPacketReceiveFn == nil {
panic("not supposed to be called!")
}
return m.EUPacketReceiveFn(codeID, env, msg, store, goapi, querier, gasMeter, gasLimit, deserCost)
}

func (m *MockWasmEngine) StoreCode(codeID wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error) {
if m.StoreCodeFn == nil {
panic("not supposed to be called!")
Expand Down
9 changes: 9 additions & 0 deletions x/wasm/types/exported_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,12 @@ type IBCContractKeeper interface {
// DeleteAsyncAckPacket deletes a previously stored packet. See StoreAsyncAckPacket for more details.
DeleteAsyncAckPacket(ctx context.Context, portID, channelID string, sequence uint64)
}

// EurekaContractKeeper Eureka lifecycle event handler
type EurekaContractKeeper interface {
OnRecvEurekaPacket(
ctx sdk.Context,
contractAddr sdk.AccAddress,
msg wasmvmtypes.EurekaPacketReceiveMsg,
) (ibcexported.Acknowledgement, error)
}
14 changes: 14 additions & 0 deletions x/wasm/types/wasmer_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,20 @@ type WasmEngine interface {
deserCost wasmvmtypes.UFraction,
) (*wasmvmtypes.IBCBasicResult, uint64, error)

// EUPacketReceive is available on Eureka-enabled contracts and is called when an incoming
// packet is received on a channel belonging to this contract
EUPacketReceive(
checksum wasmvm.Checksum,
env wasmvmtypes.Env,
packet wasmvmtypes.EurekaPacketReceiveMsg,
store wasmvm.KVStore,
goapi wasmvm.GoAPI,
querier wasmvm.Querier,
gasMeter wasmvm.GasMeter,
gasLimit uint64,
deserCost wasmvmtypes.UFraction,
) (*wasmvmtypes.IBCReceiveResult, uint64, error)

// Pin pins a code to an in-memory cache, such that is
// always loaded quickly when executed.
// Pin is idempotent.
Expand Down

0 comments on commit 3fe0582

Please sign in to comment.