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

add slash throttling related queries #563

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ require (
github.com/oxyno-zeta/gomock-extra-matcher v1.1.0
github.com/regen-network/cosmos-proto v0.3.1
golang.org/x/exp v0.0.0-20221025133541-111beb427cde
github.com/spf13/pflag v1.0.5
)

require (
Expand Down Expand Up @@ -116,6 +115,7 @@ require (
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.13.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
Expand Down
56 changes: 56 additions & 0 deletions proto/interchain_security/ccv/provider/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ option go_package = "github.com/cosmos/interchain-security/x/ccv/provider/types"

import "google/api/annotations.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
import "interchain_security/ccv/v1/ccv.proto";
import "interchain_security/ccv/consumer/v1/genesis.proto";
import "interchain_security/ccv/provider/v1/provider.proto";

Expand Down Expand Up @@ -53,6 +55,20 @@ service Query {
returns (QueryValidatorProviderAddrResponse) {
option (google.api.http).get = "/interchain_security/ccv/provider/validator_provider_addr";
}

// QueryPendingSlashPackets returns the current state of the slash meter
// and a list of pending slash packets (chainID, slashPacket)
rpc QueryPendingSlashPackets(QueryPendingSlashPacketsRequest)
returns (QueryPendingSlashPacketsResponse) {
option (google.api.http).get = "/interchain_security/ccv/provider/pending_slash_requests";
}

// QueryPendingConsumerPackets returns a list of pending packets (slash packet and vsc matured)
// for a consumer chain
rpc QueryPendingConsumerPackets(QueryPendingConsumerPacketsRequest)
returns (QueryPendingConsumerPacketsResponse) {
option (google.api.http).get = "/interchain_security/ccv/provider/pending_consumer_packets";
}
}

message QueryConsumerGenesisRequest { string chain_id = 1; }
Expand Down Expand Up @@ -112,3 +128,43 @@ message QueryValidatorProviderAddrResponse {
// The address of the validator on the provider chain
string provider_address = 1;
}

message QueryPendingSlashPacketsRequest {}

message QueryPendingSlashPacketsResponse {
// current slash_meter state
int64 slash_meter = 1;
// allowance of voting power units (int) that the slash meter is given per replenish period
// this also serves as the max value for the meter.
int64 slash_meter_allowance = 2;
google.protobuf.Timestamp last_replenish = 3
[(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
repeated PendingSlashPacket packets = 4;
}

message QueryPendingConsumerPacketsRequest {
string chain_id = 1;
}

message QueryPendingConsumerPacketsResponse {
string chain_id = 1;
uint64 size = 2;
repeated PendingPacketWrapper packets = 3
[(gogoproto.nullable) = false];
}

message PendingSlashPacket {
string chain_id = 1;
google.protobuf.Timestamp received_at = 2
[(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
interchain_security.ccv.v1.SlashPacketData data = 3
[(gogoproto.nullable) = false];
}

// PendingPacketWrapper data contains either SlashPacketData or VSCMaturedPacketData
message PendingPacketWrapper {
oneof data {
interchain_security.ccv.v1.SlashPacketData slash_packet = 1;
interchain_security.ccv.v1.VSCMaturedPacketData vsc_matured_packet = 2;
}
}
74 changes: 74 additions & 0 deletions x/ccv/provider/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ func NewQueryCmd() *cobra.Command {
cmd.AddCommand(CmdConsumerStopProposals())
cmd.AddCommand(CmdConsumerValidatorKeyAssignment())
cmd.AddCommand(CmdProviderValidatorKey())
cmd.AddCommand(CmdPendingSlashPackets())
cmd.AddCommand(CmdPendingConsumerPackets())

return cmd
}
Expand Down Expand Up @@ -246,3 +248,75 @@ $ %s query provider validator-provider-key foochain %s1gghjut3ccd8ay0zduzj64hwre

return cmd
}

func CmdPendingSlashPackets() *cobra.Command {
cmd := &cobra.Command{
Use: "pending-slash-packets",
Short: "Query pending slash packet queue on the provider chain",
Long: strings.TrimSpace(
fmt.Sprintf(`Returns current pending slash packet queue state on the provider chain.
Queue is ordered by time of arrival.
Example:
$ %s query provider pending-slash-packets
`,
version.AppName,
),
),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) (err error) {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

req := &types.QueryPendingSlashPacketsRequest{}
res, err := queryClient.QueryPendingSlashPackets(cmd.Context(), req)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func CmdPendingConsumerPackets() *cobra.Command {
cmd := &cobra.Command{
Use: "pending-consumer-packets [chainid]",
Short: "Query pending VSCMatured and slash packets for chainId",
Long: strings.TrimSpace(
fmt.Sprintf(`Returns the current pending VSCMatured and slash packets for chainId.
Queue is ordered by time of arrival.
Example:
$ %s query provider pending-consumer-packets foochain
`,
version.AppName,
),
),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

req := &types.QueryPendingConsumerPacketsRequest{ChainId: args[0]}
res, err := queryClient.QueryPendingConsumerPackets(cmd.Context(), req)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
80 changes: 80 additions & 0 deletions x/ccv/provider/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/interchain-security/x/ccv/provider/types"
ccvtypes "github.com/cosmos/interchain-security/x/ccv/types"
"github.com/cosmos/interchain-security/x/ccv/utils"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -116,3 +117,82 @@ func (k Keeper) QueryValidatorProviderAddr(goCtx context.Context, req *types.Que
ProviderAddress: providerAddr.String(),
}, nil
}

func (k Keeper) QueryPendingSlashPackets(goCtx context.Context, req *types.QueryPendingSlashPacketsRequest) (*types.QueryPendingSlashPacketsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

ctx := sdk.UnwrapSDKContext(goCtx)

meter := k.GetSlashMeter(ctx)
allowance := k.GetSlashMeterAllowance(ctx)
lastTs := k.GetLastSlashMeterReplenishTime(ctx) // always UTC
packets := []*types.PendingSlashPacket{}

// Iterate through ordered (by received time) slash packet entries from any consumer chain
k.IteratePendingSlashPacketEntries(ctx, func(entry types.SlashPacketEntry) (stop bool) {
slashPacket, found := k.GetPendingSlashPacketData(ctx, entry.ConsumerChainID, entry.IbcSeqNum)
if !found {
// TODO: maybe error if package was not found?
// I don't want to panic on provider in case of incomplete response data
// don't stop on error
return false
}

packets = append(packets, &types.PendingSlashPacket{
ChainId: entry.ConsumerChainID,
ReceivedAt: entry.RecvTime,
Data: slashPacket,
})
return false
})

return &types.QueryPendingSlashPacketsResponse{
SlashMeter: meter.Int64(),
SlashMeterAllowance: allowance.Int64(),
LastReplenish: lastTs,
Packets: packets,
}, nil
}

func (k Keeper) QueryPendingConsumerPackets(goCtx context.Context, req *types.QueryPendingConsumerPacketsRequest) (*types.QueryPendingConsumerPacketsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

if req.ChainId == "" {
return nil, status.Error(codes.InvalidArgument, "invalid chain-id")
}

ctx := sdk.UnwrapSDKContext(goCtx)
if _, found := k.GetChainToChannel(ctx, req.ChainId); !found {
return nil, status.Error(codes.InvalidArgument, "invalid chain-id")
}

// TODO: maybe just dump it as JSON bytes?
packets := []types.PendingPacketWrapper{}
k.IteratePendingPacketData(ctx, req.ChainId, func(ibcSeqNum uint64, data interface{}) (stop bool) {
switch data := data.(type) {
case ccvtypes.SlashPacketData:
packets = append(packets, types.PendingPacketWrapper{
Data: &types.PendingPacketWrapper_SlashPacket{SlashPacket: &data},
})
case ccvtypes.VSCMaturedPacketData:
packets = append(packets, types.PendingPacketWrapper{
Data: &types.PendingPacketWrapper_VscMaturedPacket{VscMaturedPacket: &data},
})
default:
// silently skip over invalid data
return false

}
return false
})

return &types.QueryPendingConsumerPacketsResponse{
ChainId: req.ChainId,
Size_: k.GetPendingPacketDataSize(ctx, req.ChainId),
Packets: packets,
}, nil
}
1 change: 1 addition & 0 deletions x/ccv/provider/keeper/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d
k.QueuePendingSlashPacketEntry(ctx, providertypes.NewSlashPacketEntry(
ctx.BlockTime(), // recv time
chainID, // consumer chain id that sent the packet
packet.Sequence, // IBC sequence number of the packet
data.Validator.Address))

// Queue slash packet data in the same (consumer chain specific) queue as vsc matured packet data,
Expand Down
61 changes: 57 additions & 4 deletions x/ccv/provider/keeper/throttle.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@ func (k Keeper) GetSlashMeterAllowance(ctx sdktypes.Context) sdktypes.Int {
// related to jailing/tombstoning over time. This "parent" queue is used to coordinate the order of slash packet handling
// between chains, whereas the chain specific queue is used to coordinate the order of slash and vsc matured packets
// relevant to each chain.
func (k Keeper) QueuePendingSlashPacketEntry(ctx sdktypes.Context, entry providertypes.SlashPacketEntry) {
func (k Keeper) QueuePendingSlashPacketEntry(ctx sdktypes.Context,
entry providertypes.SlashPacketEntry) {
store := ctx.KVStore(k.storeKey)
key := providertypes.PendingSlashPacketEntryKey(entry)
// Note: Val address is stored as value to assist in debugging. This could be removed for efficiency.
store.Set(key, entry.ValAddr)
}

Expand All @@ -197,9 +197,9 @@ func (k Keeper) IteratePendingSlashPacketEntries(ctx sdktypes.Context,
iterator := sdktypes.KVStorePrefixIterator(store, []byte{providertypes.PendingSlashPacketEntryBytePrefix})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
recvTime, chainID := providertypes.ParsePendingSlashPacketEntryKey(iterator.Key())
recvTime, chainID, ibcSeqNum := providertypes.ParsePendingSlashPacketEntryKey(iterator.Key())
valAddr := iterator.Value()
entry := providertypes.NewSlashPacketEntry(recvTime, chainID, valAddr)
entry := providertypes.NewSlashPacketEntry(recvTime, chainID, ibcSeqNum, valAddr)
stop := cb(entry)
if stop {
break
Expand Down Expand Up @@ -295,6 +295,59 @@ func (k Keeper) QueuePendingVSCMaturedPacketData(
k.IncrementPendingPacketDataSize(ctx, consumerChainID)
}

// GetPendingPacketData fetches packet data from the store using consumerChainId and ibcSeqNum
// Since multiple types can be stored, it is up to the caller to determine the type after fetching
func (k Keeper) GetPendingPacketData(ctx sdktypes.Context, consumerChainID string, ibcSeqNum uint64) interface{} {
store := ctx.KVStore(k.storeKey)
bz := store.Get(providertypes.PendingPacketDataKey(consumerChainID, ibcSeqNum))

var packetData interface{}
var err error
switch bz[0] {
case slashPacketData:
spd := ccvtypes.SlashPacketData{}
err = spd.Unmarshal(bz[1:])
packetData = spd
case vscMaturedPacketData:
vpd := ccvtypes.VSCMaturedPacketData{}
err = vpd.Unmarshal(bz[1:])
packetData = vpd
default:
panic("invalid packet data type")
}

if err != nil {
panic(fmt.Sprintf("failed to unmarshal pending packet data: %v", err))
}

return packetData
}

// GetPendingSlashPacketData fetches a slash packet data from the store using consumerChainId and ibcSeqNum
// If the packets is not SlashPacketData, it is considered as not found.
// TODO: discuss with others if we should panic here?
func (k Keeper) GetPendingSlashPacketData(ctx sdktypes.Context, consumerChainID string, ibcSeqNum uint64) (ccvtypes.SlashPacketData, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(providertypes.PendingPacketDataKey(consumerChainID, ibcSeqNum))
if len(bz) == 0 {
return ccvtypes.SlashPacketData{}, false
}

// TODO: discuss - maybe panic?
if bz[0] != slashPacketData {
return ccvtypes.SlashPacketData{}, false
}

packet := ccvtypes.SlashPacketData{}
err := packet.Unmarshal(bz[1:])

if err != nil {
panic(fmt.Sprintf("failed to unmarshal pending packet data: %v", err))
}

return packet, true
}

// IteratePendingPacketData iterates over the pending packet data queue for a specific consumer chain
// (ordered by ibc seq number) and calls the provided callback
func (k Keeper) IteratePendingPacketData(ctx sdktypes.Context, consumerChainID string, cb func(uint64, interface{}) (stop bool)) {
Expand Down
Loading