Skip to content

Commit

Permalink
zoneconcierge: heartbeat IBC packet to keep relayer awake (#195)
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianElvis authored Nov 10, 2022
1 parent d88b0f8 commit 00d2b9c
Show file tree
Hide file tree
Showing 12 changed files with 354 additions and 102 deletions.
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

0 comments on commit 00d2b9c

Please sign in to comment.