Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zoneconcierge: heartbeat IBC packet to keep relayer awake #195

Merged
merged 13 commits into from
Nov 10, 2022
4 changes: 4 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ type BabylonApp struct {
// IBC-related modules
IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly
ZoneConciergeKeeper zckeeper.Keeper // for cross-chain fungible token transfers

// make scoped keepers public for test purposes
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
ScopedZoneConciergeKeeper capabilitykeeper.ScopedKeeper
Expand Down Expand Up @@ -661,6 +662,9 @@ func NewBabylonApp(
}
}

app.ScopedIBCKeeper = scopedIBCKeeper
app.ScopedZoneConciergeKeeper = scopedZoneConciergeKeeper

return app
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ require (
github.com/99designs/keyring v1.2.1 // indirect
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
github.com/Workiva/go-datastructures v1.0.53 // indirect
github.com/armon/go-metrics v0.4.0 // indirect
github.com/armon/go-metrics v0.4.0
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
Expand Down
8 changes: 3 additions & 5 deletions proto/babylon/zoneconcierge/packet.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ option go_package = "github.com/babylonchain/babylon/x/zoneconcierge/types";

message ZoneconciergePacketData {
oneof packet {
NoData noData = 1;
// this line is used by starport scaffolding # ibc/packet/proto/field
Heartbeat heartbeart = 1;
}
}

message NoData {
message Heartbeat {
string msg = 1;
}

// this line is used by starport scaffolding # ibc/packet/proto/message
4 changes: 4 additions & 0 deletions testutil/keeper/zoneconcierge.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func (zoneconciergeChannelKeeper) ChanCloseInit(ctx sdk.Context, portID, channel
return nil
}

func (zoneconciergeChannelKeeper) GetAllChannels(ctx sdk.Context) []channeltypes.IdentifiedChannel {
return nil
}

// zoneconciergeportKeeper is a stub of PortKeeper
type zoneconciergePortKeeper struct{}

Expand Down
33 changes: 33 additions & 0 deletions x/zoneconcierge/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package zoneconcierge

import (
"time"

"github.com/babylonchain/babylon/x/zoneconcierge/keeper"
"github.com/babylonchain/babylon/x/zoneconcierge/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
abci "github.com/tendermint/tendermint/abci/types"
)

// BeginBlocker sends a pending packet for every channel upon each new block,
// so that the relayer is kept awake to relay headers
func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

}

func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)

for _, channel := range k.GetAllChannels(ctx) {
if channel.State == channeltypes.OPEN {
if err := k.SendHeartbeatIBCPacket(ctx, channel); err != nil {
panic(err)
}
}
}

return []abci.ValidatorUpdate{}
}
78 changes: 78 additions & 0 deletions x/zoneconcierge/keeper/ibc_heartbeat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package keeper

import (
sdkerrors "cosmossdk.io/errors"
metrics "github.com/armon/go-metrics"
"github.com/babylonchain/babylon/x/zoneconcierge/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
host "github.com/cosmos/ibc-go/v5/modules/core/24-host"
coretypes "github.com/cosmos/ibc-go/v5/modules/core/types"
)

// SendHeartbeatIBCPacket sends an empty IBC packet to a channel
// Doing this periodically keeps the relayer awake to relay headers
// (adapted from https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/transfer/keeper/relay.go)
func (k Keeper) SendHeartbeatIBCPacket(ctx sdk.Context, channel channeltypes.IdentifiedChannel) error {
// get src/dst ports and channels
sourcePort := channel.PortId
sourceChannel := channel.ChannelId
destinationPort := channel.Counterparty.GetPortID()
destinationChannel := channel.Counterparty.GetChannelID()

// find the next sequence number
sequence, found := k.channelKeeper.GetNextSequenceSend(ctx, sourcePort, sourceChannel)
if !found {
return sdkerrors.Wrapf(
channeltypes.ErrSequenceSendNotFound,
"source port: %s, source channel: %s", sourcePort, sourceChannel,
)
}

// begin createOutgoingPacket logic
// See spec for this logic: https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay
channelCap, ok := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel))
if !ok {
return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability")
}

// timeout
curHeight := clienttypes.GetSelfHeight(ctx)
curHeight.RevisionHeight += 100 // TODO: parameterise timeout

// construct packet
// note that the data is not allowed to be empty
packetData := &types.Heartbeat{Msg: "hello"} // TODO: what to send for heartbeat packet?
packet := channeltypes.NewPacket(
k.cdc.MustMarshal(packetData),
sequence,
sourcePort,
sourceChannel,
destinationPort,
destinationChannel,
curHeight, // if the packet is not relayed after thit height, then the packet will be timeout
uint64(0), // no need to set timeout timestamp if timeout height is set
)

// send packet
if err := k.ics4Wrapper.SendPacket(ctx, channelCap, packet); err != nil {
return err
}

// metrics stuff
labels := []metrics.Label{
telemetry.NewLabel(coretypes.LabelDestinationPort, destinationPort),
telemetry.NewLabel(coretypes.LabelDestinationChannel, destinationChannel),
}
defer func() {
telemetry.IncrCounterWithLabels(
[]string{"ibc", types.ModuleName, "send"},
1,
labels,
)
}()

return nil
}
5 changes: 5 additions & 0 deletions x/zoneconcierge/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
host "github.com/cosmos/ibc-go/v5/modules/core/24-host"
"github.com/tendermint/tendermint/libs/log"
)
Expand Down Expand Up @@ -98,3 +99,7 @@ func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Cap
func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error {
return k.scopedKeeper.ClaimCapability(ctx, cap, name)
}

func (k Keeper) GetAllChannels(ctx sdk.Context) []channeltypes.IdentifiedChannel {
return k.channelKeeper.GetAllChannels(ctx)
}
9 changes: 6 additions & 3 deletions x/zoneconcierge/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"

// this line is used by starport scaffolding # 1

"github.com/grpc-ecosystem/grpc-gateway/runtime"
Expand Down Expand Up @@ -153,9 +154,11 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw
func (AppModule) ConsensusVersion() uint64 { return 1 }

// BeginBlock contains the logic that is automatically triggered at the beginning of each block
func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
BeginBlocker(ctx, am.keeper, req)
}

// EndBlock contains the logic that is automatically triggered at the end of each block
func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return EndBlocker(ctx, am.keeper)
}
64 changes: 21 additions & 43 deletions x/zoneconcierge/module_ibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v5/modules/core/05-port/types"
host "github.com/cosmos/ibc-go/v5/modules/core/24-host"
ibcexported "github.com/cosmos/ibc-go/v5/modules/core/exported"
)
Expand All @@ -35,16 +34,6 @@ func (im IBCModule) OnChanOpenInit(
counterparty channeltypes.Counterparty,
version string,
) (string, error) {
// Require portID is the portID module is bound to
boundPort := im.keeper.GetPort(ctx)
if boundPort != portID {
return "", sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort)
}

if version != types.Version {
return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version)
}

// Claim channel capability passed back by IBC module
if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil {
return "", err
Expand All @@ -64,17 +53,6 @@ func (im IBCModule) OnChanOpenTry(
counterparty channeltypes.Counterparty,
counterpartyVersion string,
) (string, error) {

// Require portID is the portID module is bound to
boundPort := im.keeper.GetPort(ctx)
if boundPort != portID {
return "", sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort)
}

if counterpartyVersion != types.Version {
return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version)
}

// Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos
// (ie chainA and chainB both call ChanOpenInit before one of them calls ChanOpenTry)
// If module can already authenticate the capability then module already owns it so we don't need to claim
Expand Down Expand Up @@ -146,13 +124,13 @@ func (im IBCModule) OnRecvPacket(
return channeltypes.NewErrorAcknowledgement(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal packet data: %s", err.Error()))
}

// Dispatch packet
switch packet := modulePacketData.Packet.(type) {
// this line is used by starport scaffolding # ibc/packet/module/recv
default:
err := fmt.Errorf("unrecognized %s packet type: %T", types.ModuleName, packet)
return channeltypes.NewErrorAcknowledgement(err)
}
// // TODO (Babylon): Dispatch and process packet
// switch packet := modulePacketData.Packet.(type) {
// // this line is used by starport scaffolding # ibc/packet/module/recv
// default:
// err := fmt.Errorf("unrecognized %s packet type: %T", types.ModuleName, packet)
// return channeltypes.NewErrorAcknowledgement(err)
// }

// NOTE: acknowledgement will be written synchronously during IBC handler execution.
return ack
Expand All @@ -179,13 +157,13 @@ func (im IBCModule) OnAcknowledgementPacket(

var eventType string

// Dispatch packet
switch packet := modulePacketData.Packet.(type) {
// this line is used by starport scaffolding # ibc/packet/module/ack
default:
errMsg := fmt.Sprintf("unrecognized %s packet type: %T", types.ModuleName, packet)
return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
}
// // TODO (Babylon): Dispatch and process packet
// switch packet := modulePacketData.Packet.(type) {
// // this line is used by starport scaffolding # ibc/packet/module/ack
// default:
// errMsg := fmt.Sprintf("unrecognized %s packet type: %T", types.ModuleName, packet)
// return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
// }

ctx.EventManager().EmitEvent(
sdk.NewEvent(
Expand Down Expand Up @@ -226,13 +204,13 @@ func (im IBCModule) OnTimeoutPacket(
return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal packet data: %s", err.Error())
}

// Dispatch packet
switch packet := modulePacketData.Packet.(type) {
// this line is used by starport scaffolding # ibc/packet/module/timeout
default:
errMsg := fmt.Sprintf("unrecognized %s packet type: %T", types.ModuleName, packet)
return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
}
// // TODO (Babylon): Dispatch and process packet
// switch packet := modulePacketData.Packet.(type) {
// // this line is used by starport scaffolding # ibc/packet/module/timeout
// default:
// errMsg := fmt.Sprintf("unrecognized %s packet type: %T", types.ModuleName, packet)
// return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
// }

return nil
}
Loading