From 7691bf5cb62f9827a47e7371471cf818dc28ef9d Mon Sep 17 00:00:00 2001 From: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:06:28 -0700 Subject: [PATCH] wip, tests not fixed yet --- x/ccv/provider/keeper/relay.go | 112 +++---- x/ccv/provider/keeper/relay_test.go | 90 ------ x/ccv/provider/keeper/throttle.go | 439 ---------------------------- x/ccv/provider/module.go | 2 + 4 files changed, 38 insertions(+), 605 deletions(-) diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index d63594dad1..d0910178e9 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -17,7 +17,7 @@ import ( ccv "github.com/cosmos/interchain-security/v3/x/ccv/types" ) -// OnRecvVSCMaturedPacket handles a VSCMatured packet +// OnRecvVSCMaturedPacket handles a VSCMatured packet and returns a no-op result ack. func (k Keeper) OnRecvVSCMaturedPacket( ctx sdk.Context, packet channeltypes.Packet, @@ -34,12 +34,9 @@ func (k Keeper) OnRecvVSCMaturedPacket( panic(fmt.Errorf("VSCMaturedPacket received on unknown channel %s", packet.DestinationChannel)) } - if err := k.QueueThrottledVSCMaturedPacketData(ctx, chainID, packet.Sequence, data); err != nil { - return ccv.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf( - "failed to queue VSCMatured packet data: %s", err.Error())) - } + k.HandleVSCMaturedPacket(ctx, chainID, data) - k.Logger(ctx).Info("VSCMaturedPacket received and enqueued", + k.Logger(ctx).Info("VSCMaturedPacket handled", "chainID", chainID, "vscID", data.ValsetUpdateId, ) @@ -48,35 +45,6 @@ func (k Keeper) OnRecvVSCMaturedPacket( return ack } -// HandleLeadingVSCMaturedPackets handles all VSCMatured packet data that has been queued this block, -// but does not need to be throttled. The handled data is then removed from the queue. -// -// Note: VSC matured packet data which is queued behind slash packet data CANNOT be -// handled until the leading slash packet data has been handled. This is to maintain -// the "VSC Maturity and Slashing Order" CCV property. If VSC matured packet data DOES NOT -// trail slash packet data for that consumer, it will be handled in this method, -// bypassing HandleThrottleQueues. -func (k Keeper) HandleLeadingVSCMaturedPackets(ctx sdk.Context) (vscMaturedHandledThisBlock int) { - vscMaturedHandledThisBlock = 0 - for _, chain := range k.GetAllConsumerChains(ctx) { - // Note: it's assumed the order of the vsc matured slice matches the order of the ibc seq nums slice, - // in that a vsc matured packet data at index i is associated with the ibc seq num at index i. - leadingVscMatured, ibcSeqNums := k.GetLeadingVSCMaturedData(ctx, chain.ChainId) - ibcSeqNumsHandled := []uint64{} - for idx, data := range leadingVscMatured { - if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit { - // Break from inner for-loop, DeleteThrottledPacketData will still be called for this consumer - break - } - k.HandleVSCMaturedPacket(ctx, chain.ChainId, data) - vscMaturedHandledThisBlock++ - ibcSeqNumsHandled = append(ibcSeqNumsHandled, ibcSeqNums[idx]) - } - k.DeleteThrottledPacketData(ctx, chain.ChainId, ibcSeqNumsHandled...) - } - return vscMaturedHandledThisBlock -} - // HandleVSCMaturedPacket handles a VSCMatured packet. // // Note: This method should only panic for a system critical error like a @@ -270,18 +238,10 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { k.IncrementValidatorSetUpdateId(ctx) } -// EndBlockCIS contains the EndBlock logic needed for -// the Consumer Initiated Slashing sub-protocol -func (k Keeper) EndBlockCIS(ctx sdk.Context) { - // set the ValsetUpdateBlockHeight - blockHeight := uint64(ctx.BlockHeight()) + 1 - valUpdateID := k.GetValidatorSetUpdateId(ctx) - k.SetValsetUpdateBlockHeight(ctx, valUpdateID, blockHeight) - k.Logger(ctx).Debug("vscID was mapped to block height", "vscID", valUpdateID, "height", blockHeight) - - // Replenish slash meter if necessary, BEFORE executing slash packet throttling logic. - // This ensures the meter value is replenished, and not greater than the allowance (max value) - // for the block, before the throttling logic is executed. +// BeginBlockCIS contains the BeginBlock logic needed for the Consumer Initiated Slashing sub-protocol. +func (k Keeper) BeginBlockCIS(ctx sdk.Context) { + // Replenish slash meter if necessary. This ensures the meter value is replenished before handling any slash packets, + // and ensures the meter value is not greater than the allowance (max value) for the block. // // Note: CheckForSlashMeterReplenishment contains panics for the following scenarios, any of which should never occur // if the protocol is correct and external data is properly validated: @@ -291,23 +251,16 @@ func (k Keeper) EndBlockCIS(ctx sdk.Context) { // - Marshaling and/or store corruption errors. // - Setting invalid slash meter values (see SetSlashMeter). k.CheckForSlashMeterReplenishment(ctx) +} - // Handle leading vsc matured packets before throttling logic. - // - // Note: HandleLeadingVSCMaturedPackets contains panics for the following scenarios, any of which should never occur - // if the protocol is correct and external data is properly validated: - // - // - Marshaling and/or store corruption errors. - vscMaturedHandledThisBlock := k.HandleLeadingVSCMaturedPackets(ctx) - // Handle queue entries considering throttling logic. - // - // Note: HandleThrottleQueues contains panics for the following scenarios, any of which should never occur - // if the protocol is correct and external data is properly validated: - // - // - SlashMeter has not been set (which should be set in InitGenesis, see InitializeSlashMeter). - // - Marshaling and/or store corruption errors. - // - Setting invalid slash meter values (see SetSlashMeter). - k.HandleThrottleQueues(ctx, vscMaturedHandledThisBlock) +// EndBlockCIS contains the EndBlock logic needed for +// the Consumer Initiated Slashing sub-protocol +func (k Keeper) EndBlockCIS(ctx sdk.Context) { + // set the ValsetUpdateBlockHeight + blockHeight := uint64(ctx.BlockHeight()) + 1 + valUpdateID := k.GetValidatorSetUpdateId(ctx) + k.SetValsetUpdateBlockHeight(ctx, valUpdateID, blockHeight) + k.Logger(ctx).Debug("vscID was mapped to block height", "vscID", valUpdateID, "height", blockHeight) } // OnRecvSlashPacket delivers a received slash packet, validates it and @@ -358,20 +311,26 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d return channeltypes.NewResultAcknowledgement(ccv.V1Result) } - // Queue a slash entry to the global queue, which will be seen by the throttling logic - k.QueueGlobalSlashEntry(ctx, providertypes.NewGlobalSlashEntry( - ctx.BlockTime(), // recv time - chainID, // consumer chain id that sent the packet - packet.Sequence, // IBC sequence number of the packet - providerConsAddr)) // Provider consensus address of val to be slashed - - // Queue slash packet data in the same (consumer chain specific) queue as vsc matured packet data, - // to enforce order of handling between the two packet data types. - if err := k.QueueThrottledSlashPacketData(ctx, chainID, packet.Sequence, data); err != nil { - return ccv.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("failed to queue slash packet data: %s", err.Error())) + meter := k.GetSlashMeter(ctx) + // Return bounce ack if meter is negative in value + if meter.IsNegative() { + k.Logger(ctx).Info("SlashPacket received, but meter is negative. Packet will be bounced", + "chainID", chainID, + "consumer cons addr", consumerConsAddr.String(), + "provider cons addr", providerConsAddr.String(), + "vscID", data.ValsetUpdateId, + "infractionType", data.Infraction, + ) + return channeltypes.NewResultAcknowledgement(ccv.SlashPacketBouncedResult) } - k.Logger(ctx).Info("slash packet received and enqueued", + k.HandleSlashPacket(ctx, chainID, data) + + // Subtract voting power that will be jailed/tombstoned from the slash meter + meter = meter.Sub(k.GetEffectiveValPower(ctx, providerConsAddr)) + k.SetSlashMeter(ctx, meter) + + k.Logger(ctx).Info("slash packet received and handled", "chainID", chainID, "consumer cons addr", consumerConsAddr.String(), "provider cons addr", providerConsAddr.String(), @@ -379,7 +338,8 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d "infractionType", data.Infraction, ) - return channeltypes.NewResultAcknowledgement(ccv.V1Result) + // Return result ack that the packet was handled successfully + return channeltypes.NewResultAcknowledgement(ccv.SlashPacketHandledResult) } // ValidateSlashPacket validates a recv slash packet before it is diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index b266211a42..596fdfe0e7 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -139,96 +139,6 @@ func TestOnRecvVSCMaturedPacket(t *testing.T) { require.Equal(t, uint64(0), providerKeeper.GetThrottledPacketDataSize(ctx, "chain-2")) } -func TestHandleLeadingVSCMaturedPackets(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - providerKeeper.SetParams(ctx, providertypes.DefaultParams()) - - vscData := getTenSampleVSCMaturedPacketData() - - // Set channel to chain, and chain to client mappings - // (faking multiple established consumer channels) - providerKeeper.SetChannelToChain(ctx, "channel-1", "chain-1") - providerKeeper.SetConsumerClientId(ctx, "chain-1", "client-1") - providerKeeper.SetChannelToChain(ctx, "channel-2", "chain-2") - providerKeeper.SetConsumerClientId(ctx, "chain-2", "client-2") - - // Queue some leading vsc matured packet data for chain-1 - err := providerKeeper.QueueThrottledVSCMaturedPacketData(ctx, "chain-1", 1, vscData[0]) - require.NoError(t, err) - err = providerKeeper.QueueThrottledVSCMaturedPacketData(ctx, "chain-1", 2, vscData[1]) - require.NoError(t, err) - err = providerKeeper.QueueThrottledVSCMaturedPacketData(ctx, "chain-1", 3, vscData[2]) - require.NoError(t, err) - - // Queue some trailing slash packet data (and a couple more vsc matured) - err = providerKeeper.QueueThrottledSlashPacketData(ctx, "chain-1", 4, testkeeper.GetNewSlashPacketData()) - require.NoError(t, err) - err = providerKeeper.QueueThrottledSlashPacketData(ctx, "chain-1", 5, testkeeper.GetNewSlashPacketData()) - require.NoError(t, err) - err = providerKeeper.QueueThrottledVSCMaturedPacketData(ctx, "chain-1", 6, vscData[3]) - require.NoError(t, err) - err = providerKeeper.QueueThrottledVSCMaturedPacketData(ctx, "chain-1", 7, vscData[4]) - require.NoError(t, err) - - // Queue some leading vsc matured packet data for chain-2 - err = providerKeeper.QueueThrottledVSCMaturedPacketData(ctx, "chain-2", 1, vscData[5]) - require.NoError(t, err) - err = providerKeeper.QueueThrottledVSCMaturedPacketData(ctx, "chain-2", 2, vscData[6]) - require.NoError(t, err) - - // And trailing slash packet data for chain-2 - err = providerKeeper.QueueThrottledSlashPacketData(ctx, "chain-2", 3, testkeeper.GetNewSlashPacketData()) - require.NoError(t, err) - err = providerKeeper.QueueThrottledSlashPacketData(ctx, "chain-2", 4, testkeeper.GetNewSlashPacketData()) - require.NoError(t, err) - - // And one more trailing vsc matured packet for chain-2 - err = providerKeeper.QueueThrottledVSCMaturedPacketData(ctx, "chain-2", 5, vscData[7]) - require.NoError(t, err) - - // Set VSC Send timestamps for each recv vsc matured packet - providerKeeper.SetVscSendTimestamp(ctx, "chain-1", vscData[0].ValsetUpdateId, time.Now()) - providerKeeper.SetVscSendTimestamp(ctx, "chain-1", vscData[1].ValsetUpdateId, time.Now()) - providerKeeper.SetVscSendTimestamp(ctx, "chain-1", vscData[2].ValsetUpdateId, time.Now()) - providerKeeper.SetVscSendTimestamp(ctx, "chain-1", vscData[3].ValsetUpdateId, time.Now()) - providerKeeper.SetVscSendTimestamp(ctx, "chain-1", vscData[4].ValsetUpdateId, time.Now()) - providerKeeper.SetVscSendTimestamp(ctx, "chain-2", vscData[5].ValsetUpdateId, time.Now()) - providerKeeper.SetVscSendTimestamp(ctx, "chain-2", vscData[6].ValsetUpdateId, time.Now()) - providerKeeper.SetVscSendTimestamp(ctx, "chain-2", vscData[7].ValsetUpdateId, time.Now()) - - // Confirm each chain-specific queue has the expected number of packet data instances - require.Equal(t, uint64(7), providerKeeper.GetThrottledPacketDataSize(ctx, "chain-1")) - require.Equal(t, uint64(5), providerKeeper.GetThrottledPacketDataSize(ctx, "chain-2")) - - // Handle leading vsc matured packets and confirm queue sizes change for both chains - providerKeeper.HandleLeadingVSCMaturedPackets(ctx) - require.Equal(t, uint64(4), providerKeeper.GetThrottledPacketDataSize(ctx, "chain-1")) - require.Equal(t, uint64(3), providerKeeper.GetThrottledPacketDataSize(ctx, "chain-2")) - - // Confirm the leading vsc matured packet data was handled for both chains, - // but not the vsc matured packet data that trails slash data in the queue. - // This assertion is made by checking that VSC Send timestamps were deleted for - // handled vsc matured packet data. - _, found := providerKeeper.GetVscSendTimestamp(ctx, "chain-1", vscData[0].ValsetUpdateId) - require.False(t, found) - _, found = providerKeeper.GetVscSendTimestamp(ctx, "chain-1", vscData[1].ValsetUpdateId) - require.False(t, found) - _, found = providerKeeper.GetVscSendTimestamp(ctx, "chain-1", vscData[2].ValsetUpdateId) - require.False(t, found) - _, found = providerKeeper.GetVscSendTimestamp(ctx, "chain-1", vscData[3].ValsetUpdateId) - require.True(t, found) - _, found = providerKeeper.GetVscSendTimestamp(ctx, "chain-1", vscData[4].ValsetUpdateId) - require.True(t, found) - - _, found = providerKeeper.GetVscSendTimestamp(ctx, "chain-2", vscData[5].ValsetUpdateId) - require.False(t, found) - _, found = providerKeeper.GetVscSendTimestamp(ctx, "chain-2", vscData[6].ValsetUpdateId) - require.False(t, found) - _, found = providerKeeper.GetVscSendTimestamp(ctx, "chain-2", vscData[7].ValsetUpdateId) - require.True(t, found) -} - // TestOnRecvSlashPacket tests the OnRecvSlashPacket method specifically for double-sign slash packets. func TestOnRecvDoubleSignSlashPacket(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) diff --git a/x/ccv/provider/keeper/throttle.go b/x/ccv/provider/keeper/throttle.go index d8be629ee7..31acde4b77 100644 --- a/x/ccv/provider/keeper/throttle.go +++ b/x/ccv/provider/keeper/throttle.go @@ -9,66 +9,9 @@ import ( sdktypes "github.com/cosmos/cosmos-sdk/types" tmtypes "github.com/cometbft/cometbft/types" - providertypes "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" - ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) -// This file contains functionality relevant to the throttling of slash and vsc matured packets, aka circuit breaker logic. - -const vscMaturedHandledPerBlockLimit = 100 - -// HandleThrottleQueues iterates over the global slash entry queue, and -// handles all or some portion of throttled (slash and/or VSC matured) packet data received from -// consumer chains. The slash meter is decremented appropriately in this method. -func (k Keeper) HandleThrottleQueues(ctx sdktypes.Context, vscMaturedHandledThisBlock int) { - meter := k.GetSlashMeter(ctx) - // Return if meter is negative in value - if meter.IsNegative() { - return - } - - // Return if vsc matured handle limit was already reached this block, during HandleLeadingVSCMaturedPackets. - // It makes no practical difference for throttling logic to execute next block. - // By doing this, we assure that all leading vsc matured packets were handled before any slash packets. - if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit { - return - } - - // Obtain all global slash entries, where only some of them may be handled in this method, - // depending on the value of the slash meter. - allEntries := k.GetAllGlobalSlashEntries(ctx) - handledEntries := []providertypes.GlobalSlashEntry{} - - for _, globalEntry := range allEntries { - // Subtract voting power that will be jailed/tombstoned from the slash meter - providerAddr := providertypes.NewProviderConsAddress(globalEntry.ProviderValConsAddr) - meter = meter.Sub(k.GetEffectiveValPower(ctx, providerAddr)) - - // Handle one slash and any trailing vsc matured packet data instances by passing in - // chainID and appropriate callbacks, relevant packet data is deleted in this method. - - k.HandlePacketDataForChain(ctx, globalEntry.ConsumerChainID, k.HandleSlashPacket, k.HandleVSCMaturedPacket, vscMaturedHandledThisBlock) - handledEntries = append(handledEntries, globalEntry) - - // don't handle any more global entries if meter becomes negative in value - if meter.IsNegative() { - k.Logger(ctx).Info("negative slash meter value, no more slash packets will be handled", "meter", meter.Int64()) - break - } - } - - // Handled global entries are deleted after iteration is completed - k.DeleteGlobalSlashEntries(ctx, handledEntries...) - - // Persist current value for slash meter - k.SetSlashMeter(ctx, meter) - - if len(handledEntries) > 0 { - k.Logger(ctx).Info("handled global slash entries", "count", len(handledEntries), "meter", meter.Int64()) - } -} - // Obtains the effective validator power relevant to a validator consensus address. func (k Keeper) GetEffectiveValPower(ctx sdktypes.Context, valConsAddr providertypes.ProviderConsAddress, @@ -87,41 +30,6 @@ func (k Keeper) GetEffectiveValPower(ctx sdktypes.Context, } } -// HandlePacketDataForChain handles only the first queued slash packet relevant to the passed consumer chainID, -// and then handles any trailing vsc matured packets in that (consumer chain specific) throttled packet data queue. -// The handled data is then deleted from the queue. -// -// Note: Any packet data which is handled in this method is also deleted from the (consumer chain specific) queue. -func (k Keeper) HandlePacketDataForChain(ctx sdktypes.Context, consumerChainID string, - slashPacketHandler func(sdktypes.Context, string, ccvtypes.SlashPacketData), - vscMaturedPacketHandler func(sdktypes.Context, string, ccvtypes.VSCMaturedPacketData), - vscMaturedHandledThisBlock int, -) { - // Get slash packet data and trailing vsc matured packet data, handle it all. - slashFound, slashData, vscMaturedData, seqNums := k.GetSlashAndTrailingData(ctx, consumerChainID) - seqNumsHandled := []uint64{} - if slashFound { - slashPacketHandler(ctx, consumerChainID, slashData) - // Due to HandleLeadingVSCMaturedPackets() running before HandleThrottleQueues(), and the fact that - // HandleThrottleQueues() will return until all leading vsc matured have been handled, a slash packet - // should always be the first packet in the queue. So we can safely append the first seqNum here. - seqNumsHandled = append(seqNumsHandled, seqNums[0]) - } - for idx, vscMData := range vscMaturedData { - if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit { - // Break from for-loop, DeleteThrottledPacketData will still be called for this consumer - break - } - vscMaturedPacketHandler(ctx, consumerChainID, vscMData) - vscMaturedHandledThisBlock++ - // Append seq num for this vsc matured packet - seqNumsHandled = append(seqNumsHandled, seqNums[idx+1]) // Note idx+1, since slash packet is at index 0 - } - - // Delete handled data after it has all been handled. - k.DeleteThrottledPacketData(ctx, consumerChainID, seqNumsHandled...) -} - // InitializeSlashMeter initializes the slash meter to it's max value (also its allowance), // and sets the replenish time candidate to one replenish period from current block time. func (k Keeper) InitializeSlashMeter(ctx sdktypes.Context) { @@ -206,353 +114,6 @@ func (k Keeper) GetSlashMeterAllowance(ctx sdktypes.Context) math.Int { return roundedInt } -// -// CRUD section -// - -// QueueGlobalSlashEntry queues an entry to the "global" slash packet queue, used for throttling val power changes -// related to jailing/tombstoning over time. This "global" 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) QueueGlobalSlashEntry(ctx sdktypes.Context, entry providertypes.GlobalSlashEntry) { - store := ctx.KVStore(k.storeKey) - key := providertypes.GlobalSlashEntryKey(entry) - bz := entry.ProviderValConsAddr - store.Set(key, bz) -} - -// DeleteGlobalSlashEntriesForConsumer deletes all pending slash packet entries in the global queue, -// only relevant to a single consumer. -func (k Keeper) DeleteGlobalSlashEntriesForConsumer(ctx sdktypes.Context, consumerChainID string) { - allEntries := k.GetAllGlobalSlashEntries(ctx) - entriesToDel := []providertypes.GlobalSlashEntry{} - - for _, entry := range allEntries { - if entry.ConsumerChainID == consumerChainID { - entriesToDel = append(entriesToDel, entry) - } - } - k.DeleteGlobalSlashEntries(ctx, entriesToDel...) -} - -// GetAllGlobalSlashEntries returns all global slash entries from the queue. -// -// Note global slash entries are stored under keys with the following format: -// GlobalSlashEntryBytePrefix | uint64 recv time | ibc seq num | consumer chain id -// Thus, the returned array is ordered by recv time, then ibc seq num. -func (k Keeper) GetAllGlobalSlashEntries(ctx sdktypes.Context) []providertypes.GlobalSlashEntry { - store := ctx.KVStore(k.storeKey) - iterator := sdktypes.KVStorePrefixIterator(store, []byte{providertypes.GlobalSlashEntryBytePrefix}) - defer iterator.Close() - - entries := []providertypes.GlobalSlashEntry{} - - for ; iterator.Valid(); iterator.Next() { - // MustParseGlobalSlashEntryKey should not panic, since we should be iterating over keys that're - // assumed to be correctly serialized in QueueGlobalSlashEntry. - recvTime, chainID, ibcSeqNum := providertypes.MustParseGlobalSlashEntryKey(iterator.Key()) - valAddr := providertypes.NewProviderConsAddress(iterator.Value()) - entry := providertypes.NewGlobalSlashEntry(recvTime, chainID, ibcSeqNum, valAddr) - entries = append(entries, entry) - } - return entries -} - -// DeleteGlobalSlashEntries deletes the given global entries from the global slash queue -func (k Keeper) DeleteGlobalSlashEntries(ctx sdktypes.Context, entries ...providertypes.GlobalSlashEntry) { - store := ctx.KVStore(k.storeKey) - for _, entry := range entries { - store.Delete(providertypes.GlobalSlashEntryKey(entry)) - } -} - -// Pending packet data type enum, used to encode the type of packet data stored at each entry in the mutual queue. -const ( - slashPacketData byte = iota - vscMaturedPacketData -) - -// GetThrottledPacketDataSize returns the size of the throttled packet data queue for the given consumer chain -func (k Keeper) GetThrottledPacketDataSize(ctx sdktypes.Context, consumerChainID string) uint64 { - store := ctx.KVStore(k.storeKey) - key := providertypes.ThrottledPacketDataSizeKey(consumerChainID) - var size uint64 - bz := store.Get(key) - if bz == nil { - size = 0 - } else { - size = sdktypes.BigEndianToUint64(bz) - } - return size -} - -// SetThrottledPacketDataSize sets the size of the throttled packet data queue for the given consumer chain -func (k Keeper) SetThrottledPacketDataSize(ctx sdktypes.Context, consumerChainID string, size uint64) { - // Sanity check to ensure that the chain-specific throttled packet data queue does not grow too - // large for a single consumer chain. This check ensures that binaries would panic deterministically - // if the queue does grow too large. MaxThrottledPackets should be set accordingly (quite large). - if size >= uint64(k.GetMaxThrottledPackets(ctx)) { - panic(fmt.Sprintf("throttled packet data queue for chain %s is too large: %d", consumerChainID, size)) - } - - store := ctx.KVStore(k.storeKey) - key := providertypes.ThrottledPacketDataSizeKey(consumerChainID) - bz := sdktypes.Uint64ToBigEndian(size) - store.Set(key, bz) -} - -// IncrementThrottledPacketDataSize increments the size of the throttled packet data -// queue for the given consumer chain. -func (k Keeper) IncrementThrottledPacketDataSize(ctx sdktypes.Context, consumerChainID string) { - size := k.GetThrottledPacketDataSize(ctx, consumerChainID) - k.SetThrottledPacketDataSize(ctx, consumerChainID, size+1) - k.Logger(ctx).Debug("incremented throttled packets size", - "chainID", consumerChainID, - "size", size+1, - ) -} - -// QueueThrottledSlashPacketData queues the slash packet data for a chain-specific throttled packet data queue. -// -// Note: This queue is shared between pending slash packet data and pending vsc matured packet data. -func (k Keeper) QueueThrottledSlashPacketData( - ctx sdktypes.Context, consumerChainID string, ibcSeqNum uint64, data ccvtypes.SlashPacketData, -) error { - return k.QueueThrottledPacketData(ctx, consumerChainID, ibcSeqNum, data) -} - -// QueueThrottledVSCMaturedPacketData queues the vsc matured packet data for a chain-specific throttled packet data queue. -// -// Note: This queue is shared between pending slash packet data and pending vsc matured packet data. -func (k Keeper) QueueThrottledVSCMaturedPacketData( - ctx sdktypes.Context, consumerChainID string, ibcSeqNum uint64, data ccvtypes.VSCMaturedPacketData, -) error { - return k.QueueThrottledPacketData(ctx, consumerChainID, ibcSeqNum, data) -} - -// QueueThrottledPacketData queues a slash packet data or vsc matured packet data instance -// for the given consumer chain's queue. -// -// Note: This method returns an error because it is called from -// OnRecvSlashPacket and OnRecvVSCMaturedPacket, meaning we can return an ibc err ack to the -// counter party chain on error, instead of panicking this chain. -func (k Keeper) QueueThrottledPacketData( - ctx sdktypes.Context, consumerChainID string, ibcSeqNum uint64, packetData interface{}, -) error { - store := ctx.KVStore(k.storeKey) - - var bz []byte - var err error - switch data := packetData.(type) { - case ccvtypes.SlashPacketData: - bz, err = data.Marshal() - if err != nil { - return fmt.Errorf("failed to marshal slash packet data: %v", err) - } - bz = append([]byte{slashPacketData}, bz...) - case ccvtypes.VSCMaturedPacketData: - bz, err = data.Marshal() - if err != nil { - return fmt.Errorf("failed to marshal vsc matured packet data: %v", err) - } - bz = append([]byte{vscMaturedPacketData}, bz...) - default: - // Indicates a developer error, this method should only be called - // by tests, QueueThrottledSlashPacketData, or QueueThrottledVSCMaturedPacketData. - panic(fmt.Sprintf("unexpected packet data type: %T", data)) - } - - store.Set(providertypes.ThrottledPacketDataKey(consumerChainID, ibcSeqNum), bz) - k.IncrementThrottledPacketDataSize(ctx, consumerChainID) - return nil -} - -// GetLeadingVSCMaturedData returns the leading vsc matured packet data instances -// for a chain-specific throttled packet data queue. Ie the vsc matured packet data instances -// that do not have any slash packet data instances preceding them in the queue for consumerChainID. -func (k Keeper) GetLeadingVSCMaturedData(ctx sdktypes.Context, consumerChainID string) ( - vscMaturedData []ccvtypes.VSCMaturedPacketData, ibcSeqNums []uint64, -) { - store := ctx.KVStore(k.storeKey) - iteratorPrefix := providertypes.ChainIdWithLenKey(providertypes.ThrottledPacketDataBytePrefix, consumerChainID) - iterator := sdktypes.KVStorePrefixIterator(store, iteratorPrefix) - defer iterator.Close() - - // Iterate over the throttled packet data queue, - // and return vsc matured packet data instances until we encounter a slash packet data instance. - vscMaturedData = []ccvtypes.VSCMaturedPacketData{} - ibcSeqNums = []uint64{} - for ; iterator.Valid(); iterator.Next() { - - bz := iterator.Value() - if bz[0] == slashPacketData { - break - } else if bz[0] != vscMaturedPacketData { - // This case would indicate a developer error or store corruption, - // since QueueThrottledPacketData should only queue slash packet data or vsc matured packet data. - panic(fmt.Sprintf("unexpected packet data type: %d", bz[0])) - } - - var data ccvtypes.VSCMaturedPacketData - err := data.Unmarshal(bz[1:]) - if err != nil { - // An error here would indicate something is very wrong, - // vsc matured packet data is assumed to be correctly serialized in QueueThrottledPacketData. - panic(fmt.Sprintf("failed to unmarshal vsc matured packet data: %v", err)) - } - - vscMaturedData = append(vscMaturedData, data) - // The below func should not panic, since we should be iterating over keys that're - // assumed to be correctly serialized in QueueThrottledPacketData. - _, ibcSeqNum := providertypes.MustParseThrottledPacketDataKey(iterator.Key()) - ibcSeqNums = append(ibcSeqNums, ibcSeqNum) - } - return vscMaturedData, ibcSeqNums -} - -// GetSlashAndTrailingData returns the first slash packet data instance and any -// trailing vsc matured packet data instances in the chain-specific throttled packet data queue. -// -// Note that throttled packet data is stored under keys with the following format: -// ThrottledPacketDataBytePrefix | len(chainID) | chainID | ibcSeqNum -// Thus, the returned array is in ascending order of ibc seq numbers. -func (k Keeper) GetSlashAndTrailingData(ctx sdktypes.Context, consumerChainID string) ( - slashFound bool, slashData ccvtypes.SlashPacketData, vscMaturedData []ccvtypes.VSCMaturedPacketData, - // Note: this slice contains the IBC sequence numbers of the slash packet data - // and trailing vsc matured packet data instances. This is used by caller to delete the - // data after it has been handled. - ibcSeqNums []uint64, -) { - store := ctx.KVStore(k.storeKey) - iteratorPrefix := providertypes.ChainIdWithLenKey(providertypes.ThrottledPacketDataBytePrefix, consumerChainID) - iterator := sdktypes.KVStorePrefixIterator(store, iteratorPrefix) - defer iterator.Close() - - slashFound = false - slashData = ccvtypes.SlashPacketData{} - vscMaturedData = []ccvtypes.VSCMaturedPacketData{} - ibcSeqNums = []uint64{} - - for ; iterator.Valid(); iterator.Next() { - - bz := iterator.Value() - if bz[0] == slashPacketData { - if slashFound { - // Break for-loop, we've already found first slash packet data instance. - break - } else { - if err := slashData.Unmarshal(bz[1:]); err != nil { - // An error here would indicate something is very wrong, - // slash packet data is assumed to be correctly serialized in QueueThrottledPacketData. - panic(fmt.Sprintf("failed to unmarshal slash packet data: %v", err)) - } - slashFound = true - } - } else if bz[0] == vscMaturedPacketData { - vscMData := ccvtypes.VSCMaturedPacketData{} - if err := vscMData.Unmarshal(bz[1:]); err != nil { - // An error here would indicate something is very wrong, - // vsc matured packet data is assumed to be correctly serialized in QueueThrottledPacketData. - panic(fmt.Sprintf("failed to unmarshal vsc matured packet data: %v", err)) - } - vscMaturedData = append(vscMaturedData, vscMData) - } else { - // This case would indicate a developer error or store corruption, - // since QueueThrottledPacketData should only queue slash packet data or vsc matured packet data. - panic("invalid packet data type") - } - // The below func should not panic, since we should be iterating over keys that're - // assumed to be correctly serialized in QueueThrottledPacketData. - _, ibcSeqNum := providertypes.MustParseThrottledPacketDataKey(iterator.Key()) - ibcSeqNums = append(ibcSeqNums, ibcSeqNum) - } - return slashFound, slashData, vscMaturedData, ibcSeqNums -} - -// GetAllThrottledPacketData returns all throttled packet data for a specific consumer chain. -// -// Note: This method is only used by tests and queries, hence why it returns redundant data as different types. -// Since this method executes on query, no panics are explicitly included. -func (k Keeper) GetAllThrottledPacketData(ctx sdktypes.Context, consumerChainID string) ( - slashData []ccvtypes.SlashPacketData, vscMaturedData []ccvtypes.VSCMaturedPacketData, - rawOrderedData []interface{}, ibcSeqNums []uint64, -) { - slashData = []ccvtypes.SlashPacketData{} - vscMaturedData = []ccvtypes.VSCMaturedPacketData{} - rawOrderedData = []interface{}{} - ibcSeqNums = []uint64{} - - store := ctx.KVStore(k.storeKey) - iteratorPrefix := providertypes.ChainIdWithLenKey(providertypes.ThrottledPacketDataBytePrefix, consumerChainID) - iterator := sdktypes.KVStorePrefixIterator(store, iteratorPrefix) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - switch bz[0] { - case slashPacketData: - d := ccvtypes.SlashPacketData{} - if err := d.Unmarshal(bz[1:]); err != nil { - k.Logger(ctx).Error(fmt.Sprintf("failed to unmarshal slash packet data: %v", err)) - continue - } - slashData = append(slashData, d) - rawOrderedData = append(rawOrderedData, d) - case vscMaturedPacketData: - d := ccvtypes.VSCMaturedPacketData{} - if err := d.Unmarshal(bz[1:]); err != nil { - k.Logger(ctx).Error(fmt.Sprintf("failed to unmarshal vsc matured packet data: %v", err)) - continue - } - vscMaturedData = append(vscMaturedData, d) - rawOrderedData = append(rawOrderedData, d) - default: - k.Logger(ctx).Error(fmt.Sprintf("invalid packet data type: %v", bz[0])) - continue - } - _, ibcSeqNum, err := providertypes.ParseThrottledPacketDataKey(iterator.Key()) - if err != nil { - k.Logger(ctx).Error(fmt.Sprintf("failed to parse throttled packet data key: %v", err)) - continue - } - ibcSeqNums = append(ibcSeqNums, ibcSeqNum) - } - - return slashData, vscMaturedData, rawOrderedData, ibcSeqNums -} - -// DeleteAllPacketDataForConsumer deletes all queued packet data for the given consumer chain. -func (k Keeper) DeleteThrottledPacketDataForConsumer(ctx sdktypes.Context, consumerChainID string) { - store := ctx.KVStore(k.storeKey) - iteratorPrefix := providertypes.ChainIdWithLenKey(providertypes.ThrottledPacketDataBytePrefix, consumerChainID) - iterator := sdktypes.KVStorePrefixIterator(store, iteratorPrefix) - defer iterator.Close() - - keysToDel := [][]byte{} - for ; iterator.Valid(); iterator.Next() { - keysToDel = append(keysToDel, iterator.Key()) - } - // Delete data for this consumer - for _, key := range keysToDel { - store.Delete(key) - } - - // Delete size of data queue for this consumer - store.Delete(providertypes.ThrottledPacketDataSizeKey(consumerChainID)) -} - -// DeleteThrottledPacketData deletes the given throttled packet data instances -// (specified by their ibc seq number) from the chain-specific throttled packet data queue. -func (k Keeper) DeleteThrottledPacketData(ctx sdktypes.Context, consumerChainID string, ibcSeqNumbers ...uint64) { - store := ctx.KVStore(k.storeKey) - for _, ibcSeqNum := range ibcSeqNumbers { - store.Delete(providertypes.ThrottledPacketDataKey(consumerChainID, ibcSeqNum)) - } - // Decrement the size of the pending packet data queue - sizeBeforeDeletion := k.GetThrottledPacketDataSize(ctx, consumerChainID) - k.SetThrottledPacketDataSize(ctx, consumerChainID, sizeBeforeDeletion-uint64(len(ibcSeqNumbers))) -} - // GetSlashMeter returns a meter (persisted as a signed int) which stores an amount of voting power, corresponding // to an allowance of validators that can be jailed/tombstoned over time. // diff --git a/x/ccv/provider/module.go b/x/ccv/provider/module.go index 82891c27c7..5aa743ec2d 100644 --- a/x/ccv/provider/module.go +++ b/x/ccv/provider/module.go @@ -137,6 +137,8 @@ func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { am.keeper.BeginBlockInit(ctx) // Stop and remove state for any consumer chains that are due to be stopped via pending consumer removal proposals am.keeper.BeginBlockCCR(ctx) + // Check for replenishing slash meter before any slash packets are processed for this block + am.keeper.BeginBlockCIS(ctx) } // EndBlock implements the AppModule interface