Skip to content

Commit 2395b6f

Browse files
fix: limit vsc matured packets handled per endblocker (backport #1004) (#1015)
fix: limit vsc matured packets handled per endblocker (#1004) * initial implementation, still need tests * UTs * integration test * linter * Update CHANGELOG.md * make vsc matured handled this block a var * comment (cherry picked from commit 8c2fc56) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com>
1 parent 42f916e commit 2395b6f

File tree

8 files changed

+140
-14
lines changed

8 files changed

+140
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state m
1919

2020
## Notable PRs included in v2.0.0
2121

22+
* (feat/fix) limit vsc matured packets handled per endblocker [#1004](https://github.com/cosmos/interchain-security/pull/1004)
2223
* (fix) cosumer key prefix order to avoid complex migrations [#963](https://github.com/cosmos/interchain-security/pull/963) and [#991](https://github.com/cosmos/interchain-security/pull/991). The latter PR is the proper fix.
2324
* (feat) v1->v2 migrations to accommodate a bugfix having to do with store keys, introduce new params, and deal with consumer genesis state schema changes [#975](https://github.com/cosmos/interchain-security/pull/975) and [#997](https://github.com/cosmos/interchain-security/pull/997)
2425
* (deps) Bump github.com/cosmos/ibc-go/v4 from 4.4.0 to 4.4.2 [#982](https://github.com/cosmos/interchain-security/pull/982)

tests/integration/throttle.go

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
tmtypes "github.com/tendermint/tendermint/types"
1313
)
1414

15+
const fullSlashMeterString = "1.0"
16+
1517
// TestBasicSlashPacketThrottling tests slash packet throttling with a single consumer,
1618
// two slash packets, and no VSC matured packets. The most basic scenario.
1719
func (s *CCVTestSuite) TestBasicSlashPacketThrottling() {
@@ -651,7 +653,7 @@ func (s *CCVTestSuite) TestSlashSameValidator() {
651653

652654
// Set replenish fraction to 1.0 so that all sent packets should handled immediately (no throttling)
653655
params := providerKeeper.GetParams(s.providerCtx())
654-
params.SlashMeterReplenishFraction = "1.0"
656+
params.SlashMeterReplenishFraction = fullSlashMeterString // needs to be const for linter
655657
providerKeeper.SetParams(s.providerCtx(), params)
656658
providerKeeper.InitializeSlashMeter(s.providerCtx())
657659

@@ -706,7 +708,7 @@ func (s CCVTestSuite) TestSlashAllValidators() { //nolint:govet // this is a tes
706708

707709
// Set replenish fraction to 1.0 so that all sent packets should be handled immediately (no throttling)
708710
params := providerKeeper.GetParams(s.providerCtx())
709-
params.SlashMeterReplenishFraction = "1.0"
711+
params.SlashMeterReplenishFraction = fullSlashMeterString // needs to be const for linter
710712
providerKeeper.SetParams(s.providerCtx(), params)
711713
providerKeeper.InitializeSlashMeter(s.providerCtx())
712714

@@ -779,7 +781,7 @@ func (s *CCVTestSuite) TestLeadingVSCMaturedAreDequeued() {
779781

780782
// Queue up 50 slash packets for each consumer
781783
for _, bundle := range s.consumerBundles {
782-
for i := 0; i < 50; i++ {
784+
for i := 50; i < 100; i++ {
783785
ibcSeqNum := uint64(i)
784786
packet := s.constructSlashPacketFromConsumer(*bundle,
785787
*s.providerChain.Vals.Validators[0], stakingtypes.Downtime, ibcSeqNum)
@@ -792,7 +794,7 @@ func (s *CCVTestSuite) TestLeadingVSCMaturedAreDequeued() {
792794

793795
// Queue up another 50 vsc matured packets for each consumer
794796
for _, bundle := range s.consumerBundles {
795-
for i := 0; i < 50; i++ {
797+
for i := 100; i < 150; i++ {
796798
ibcSeqNum := uint64(i)
797799
packet := s.constructVSCMaturedPacketFromConsumer(*bundle, ibcSeqNum)
798800
packetData := ccvtypes.ConsumerPacketData{}
@@ -818,6 +820,10 @@ func (s *CCVTestSuite) TestLeadingVSCMaturedAreDequeued() {
818820
providerKeeper.SetSlashMeterReplenishTimeCandidate(s.providerCtx())
819821

820822
// Execute end blocker to dequeue only the leading vsc matured packets.
823+
// Note we must call the end blocker three times, since only 100 vsc matured packets can be handled
824+
// each block, and we have 5*50=250 total.
825+
s.providerChain.NextBlock()
826+
s.providerChain.NextBlock()
821827
s.providerChain.NextBlock()
822828

823829
// Confirm queue size is 100 for each consumer-specific queue (50 leading vsc matured are dequeued).
@@ -827,9 +833,80 @@ func (s *CCVTestSuite) TestLeadingVSCMaturedAreDequeued() {
827833
}
828834

829835
// No slash packets handled, global slash queue is same size as last block.
836+
globalEntries = providerKeeper.GetAllGlobalSlashEntries(s.providerCtx())
837+
s.Require().Equal(len(globalEntries), 50*5)
838+
839+
// No slash packets handled even if we call end blocker a couple more times.
840+
s.providerChain.NextBlock()
841+
s.providerChain.NextBlock()
842+
globalEntries = providerKeeper.GetAllGlobalSlashEntries(s.providerCtx())
830843
s.Require().Equal(len(globalEntries), 50*5)
831844
}
832845

846+
// TestVscMaturedHandledPerBlockLimit tests that only 100 vsc matured packets are handled per block,
847+
// specifically from HandleThrottleQueues().
848+
//
849+
// Note the vsc matured per block limit is also tested in, TestLeadingVSCMaturedAreDequeued,
850+
// specifically in the context of HandleLeadingVSCMaturedPackets().
851+
func (s *CCVTestSuite) TestVscMaturedHandledPerBlockLimit() {
852+
s.SetupAllCCVChannels()
853+
providerKeeper := s.providerApp.GetProviderKeeper()
854+
855+
// Set replenish fraction to 1.0 so that all sent packets should be handled immediately (no jail throttling)
856+
params := providerKeeper.GetParams(s.providerCtx())
857+
params.SlashMeterReplenishFraction = fullSlashMeterString // needs to be const for linter
858+
providerKeeper.SetParams(s.providerCtx(), params)
859+
providerKeeper.InitializeSlashMeter(s.providerCtx())
860+
861+
// Queue up 100 vsc matured packets for each consumer
862+
for _, bundle := range s.consumerBundles {
863+
for i := 0; i < 100; i++ {
864+
ibcSeqNum := uint64(i)
865+
packet := s.constructVSCMaturedPacketFromConsumer(*bundle, ibcSeqNum)
866+
packetData := ccvtypes.ConsumerPacketData{}
867+
ccvtypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &packetData)
868+
providerKeeper.OnRecvVSCMaturedPacket(s.providerCtx(),
869+
packet, *packetData.GetVscMaturedPacketData())
870+
}
871+
}
872+
873+
// Queue up 50 slash packets for each consumer, with new IBC sequence numbers
874+
for _, bundle := range s.consumerBundles {
875+
for i := 100; i < 150; i++ {
876+
ibcSeqNum := uint64(i)
877+
packet := s.constructSlashPacketFromConsumer(*bundle,
878+
*s.providerChain.Vals.Validators[0], stakingtypes.Downtime, ibcSeqNum)
879+
packetData := ccvtypes.ConsumerPacketData{}
880+
ccvtypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &packetData)
881+
providerKeeper.OnRecvSlashPacket(s.providerCtx(),
882+
packet, *packetData.GetSlashPacketData())
883+
}
884+
}
885+
886+
// Confirm queue size is 150 for each consumer-specific queue.
887+
for _, bundle := range s.consumerBundles {
888+
s.Require().Equal(uint64(150),
889+
providerKeeper.GetThrottledPacketDataSize(s.providerCtx(), bundle.Chain.ChainID))
890+
}
891+
// Confirm global queue size is 50 * 5 (50 slash packets for each of 5 consumers)
892+
globalEntries := providerKeeper.GetAllGlobalSlashEntries(s.providerCtx())
893+
s.Require().Equal(len(globalEntries), 50*5)
894+
895+
// Note even though there is no jail throttling active, slash packets will not be handled until
896+
// all of the leading vsc matured packets are handled first. This should take 5 blocks.
897+
for i := 0; i < 5; i++ {
898+
s.providerChain.NextBlock()
899+
s.Require().Len(providerKeeper.GetAllGlobalSlashEntries(s.providerCtx()), 250) // global entries remain same size
900+
}
901+
902+
// Set signing info for val to be jailed, preventing panic
903+
s.setDefaultValSigningInfo(*s.providerChain.Vals.Validators[0])
904+
905+
// Now we execute one more block and all 250 of the slash packets should be handled.
906+
s.providerChain.NextBlock()
907+
s.Require().Empty(providerKeeper.GetAllGlobalSlashEntries(s.providerCtx())) // empty global entries = all slash packets handled
908+
}
909+
833910
func (s *CCVTestSuite) confirmValidatorJailed(tmVal tmtypes.Validator, checkPower bool) {
834911
sdkVal, found := s.providerApp.GetTestStakingKeeper().GetValidator(
835912
s.providerCtx(), sdk.ValAddress(tmVal.Address))

testutil/integration/debug_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ func TestLeadingVSCMaturedAreDequeued(t *testing.T) {
205205
runCCVTestByName(t, "TestLeadingVSCMaturedAreDequeued")
206206
}
207207

208+
func TestVscMaturedHandledPerBlockLimit(t *testing.T) {
209+
runCCVTestByName(t, "TestVscMaturedHandledPerBlockLimit")
210+
}
211+
208212
//
209213
// Unbonding tests
210214
//

x/ccv/provider/keeper/relay.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,25 @@ func (k Keeper) OnRecvVSCMaturedPacket(
5353
// the "VSC Maturity and Slashing Order" CCV property. If VSC matured packet data DOES NOT
5454
// trail slash packet data for that consumer, it will be handled in this method,
5555
// bypassing HandleThrottleQueues.
56-
func (k Keeper) HandleLeadingVSCMaturedPackets(ctx sdk.Context) {
56+
func (k Keeper) HandleLeadingVSCMaturedPackets(ctx sdk.Context) (vscMaturedHandledThisBlock int) {
57+
vscMaturedHandledThisBlock = 0
5758
for _, chain := range k.GetAllConsumerChains(ctx) {
59+
// Note: it's assumed the order of the vsc matured slice matches the order of the ibc seq nums slice,
60+
// in that a vsc matured packet data at index i is associated with the ibc seq num at index i.
5861
leadingVscMatured, ibcSeqNums := k.GetLeadingVSCMaturedData(ctx, chain.ChainId)
59-
for _, data := range leadingVscMatured {
62+
ibcSeqNumsHandled := []uint64{}
63+
for idx, data := range leadingVscMatured {
64+
if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit {
65+
// Break from inner for-loop, DeleteThrottledPacketData will still be called for this consumer
66+
break
67+
}
6068
k.HandleVSCMaturedPacket(ctx, chain.ChainId, data)
69+
vscMaturedHandledThisBlock++
70+
ibcSeqNumsHandled = append(ibcSeqNumsHandled, ibcSeqNums[idx])
6171
}
62-
k.DeleteThrottledPacketData(ctx, chain.ChainId, ibcSeqNums...)
72+
k.DeleteThrottledPacketData(ctx, chain.ChainId, ibcSeqNumsHandled...)
6373
}
74+
return vscMaturedHandledThisBlock
6475
}
6576

6677
// HandleVSCMaturedPacket handles a VSCMatured packet.
@@ -267,13 +278,14 @@ func (k Keeper) EndBlockCIS(ctx sdk.Context) {
267278
// - Marshaling and/or store corruption errors.
268279
// - Setting invalid slash meter values (see SetSlashMeter).
269280
k.CheckForSlashMeterReplenishment(ctx)
281+
270282
// Handle leading vsc matured packets before throttling logic.
271283
//
272284
// Note: HandleLeadingVSCMaturedPackets contains panics for the following scenarios, any of which should never occur
273285
// if the protocol is correct and external data is properly validated:
274286
//
275287
// - Marshaling and/or store corruption errors.
276-
k.HandleLeadingVSCMaturedPackets(ctx)
288+
vscMaturedHandledThisBlock := k.HandleLeadingVSCMaturedPackets(ctx)
277289
// Handle queue entries considering throttling logic.
278290
//
279291
// Note: HandleThrottleQueues contains panics for the following scenarios, any of which should never occur
@@ -282,7 +294,7 @@ func (k Keeper) EndBlockCIS(ctx sdk.Context) {
282294
// - SlashMeter has not been set (which should be set in InitGenesis, see InitializeSlashMeter).
283295
// - Marshaling and/or store corruption errors.
284296
// - Setting invalid slash meter values (see SetSlashMeter).
285-
k.HandleThrottleQueues(ctx)
297+
k.HandleThrottleQueues(ctx, vscMaturedHandledThisBlock)
286298
}
287299

288300
// OnRecvSlashPacket delivers a received slash packet, validates it and

x/ccv/provider/keeper/throttle.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,25 @@ import (
1212

1313
// This file contains functionality relevant to the throttling of slash and vsc matured packets, aka circuit breaker logic.
1414

15+
const vscMaturedHandledPerBlockLimit = 100
16+
1517
// HandleThrottleQueues iterates over the global slash entry queue, and
1618
// handles all or some portion of throttled (slash and/or VSC matured) packet data received from
1719
// consumer chains. The slash meter is decremented appropriately in this method.
18-
func (k Keeper) HandleThrottleQueues(ctx sdktypes.Context) {
20+
func (k Keeper) HandleThrottleQueues(ctx sdktypes.Context, vscMaturedHandledThisBlock int) {
1921
meter := k.GetSlashMeter(ctx)
2022
// Return if meter is negative in value
2123
if meter.IsNegative() {
2224
return
2325
}
2426

27+
// Return if vsc matured handle limit was already reached this block, during HandleLeadingVSCMaturedPackets.
28+
// It makes no practical difference for throttling logic to execute next block.
29+
// By doing this, we assure that all leading vsc matured packets were handled before any slash packets.
30+
if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit {
31+
return
32+
}
33+
2534
// Obtain all global slash entries, where only some of them may be handled in this method,
2635
// depending on the value of the slash meter.
2736
allEntries := k.GetAllGlobalSlashEntries(ctx)
@@ -35,7 +44,7 @@ func (k Keeper) HandleThrottleQueues(ctx sdktypes.Context) {
3544
// Handle one slash and any trailing vsc matured packet data instances by passing in
3645
// chainID and appropriate callbacks, relevant packet data is deleted in this method.
3746

38-
k.HandlePacketDataForChain(ctx, globalEntry.ConsumerChainID, k.HandleSlashPacket, k.HandleVSCMaturedPacket)
47+
k.HandlePacketDataForChain(ctx, globalEntry.ConsumerChainID, k.HandleSlashPacket, k.HandleVSCMaturedPacket, vscMaturedHandledThisBlock)
3948
handledEntries = append(handledEntries, globalEntry)
4049

4150
// don't handle any more global entries if meter becomes negative in value
@@ -82,18 +91,31 @@ func (k Keeper) GetEffectiveValPower(ctx sdktypes.Context,
8291
func (k Keeper) HandlePacketDataForChain(ctx sdktypes.Context, consumerChainID string,
8392
slashPacketHandler func(sdktypes.Context, string, ccvtypes.SlashPacketData),
8493
vscMaturedPacketHandler func(sdktypes.Context, string, ccvtypes.VSCMaturedPacketData),
94+
vscMaturedHandledThisBlock int,
8595
) {
8696
// Get slash packet data and trailing vsc matured packet data, handle it all.
8797
slashFound, slashData, vscMaturedData, seqNums := k.GetSlashAndTrailingData(ctx, consumerChainID)
98+
seqNumsHandled := []uint64{}
8899
if slashFound {
89100
slashPacketHandler(ctx, consumerChainID, slashData)
101+
// Due to HandleLeadingVSCMaturedPackets() running before HandleThrottleQueues(), and the fact that
102+
// HandleThrottleQueues() will return until all leading vsc matured have been handled, a slash packet
103+
// should always be the first packet in the queue. So we can safely append the first seqNum here.
104+
seqNumsHandled = append(seqNumsHandled, seqNums[0])
90105
}
91-
for _, vscMData := range vscMaturedData {
106+
for idx, vscMData := range vscMaturedData {
107+
if vscMaturedHandledThisBlock >= vscMaturedHandledPerBlockLimit {
108+
// Break from for-loop, DeleteThrottledPacketData will still be called for this consumer
109+
break
110+
}
92111
vscMaturedPacketHandler(ctx, consumerChainID, vscMData)
112+
vscMaturedHandledThisBlock++
113+
// Append seq num for this vsc matured packet
114+
seqNumsHandled = append(seqNumsHandled, seqNums[idx+1]) // Note idx+1, since slash packet is at index 0
93115
}
94116

95117
// Delete handled data after it has all been handled.
96-
k.DeleteThrottledPacketData(ctx, consumerChainID, seqNums...)
118+
k.DeleteThrottledPacketData(ctx, consumerChainID, seqNumsHandled...)
97119
}
98120

99121
// InitializeSlashMeter initializes the slash meter to it's max value (also its allowance),

x/ccv/provider/keeper/throttle_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func TestHandlePacketDataForChain(t *testing.T) {
134134
handledData = append(handledData, data)
135135
}
136136

137-
providerKeeper.HandlePacketDataForChain(ctx, tc.chainID, slashHandleCounter, vscMaturedHandleCounter)
137+
providerKeeper.HandlePacketDataForChain(ctx, tc.chainID, slashHandleCounter, vscMaturedHandleCounter, 0)
138138

139139
// Assert number of handled data instances matches expected number
140140
require.Equal(t, len(tc.expectedHandledIndexes), len(handledData))

x/ccv/provider/types/keys.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ const (
134134
// ConsumerRewardDenomsBytePrefix is the byte prefix that will store a list of consumer reward denoms
135135
ConsumerRewardDenomsBytePrefix
136136

137+
// VSCMaturedHandledThisBlockBytePrefix is the byte prefix storing the number of vsc matured packets
138+
// handled in the current block
139+
VSCMaturedHandledThisBlockBytePrefix
140+
137141
// NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go
138142
)
139143

@@ -476,6 +480,10 @@ func ParseChainIdAndConsAddrKey(prefix byte, bz []byte) (string, sdk.ConsAddress
476480
return chainID, addr, nil
477481
}
478482

483+
func VSCMaturedHandledThisBlockKey() []byte {
484+
return []byte{VSCMaturedHandledThisBlockBytePrefix}
485+
}
486+
479487
//
480488
// End of generic helpers section
481489
//

x/ccv/provider/types/keys_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func getAllKeyPrefixes() []byte {
5151
providertypes.KeyAssignmentReplacementsBytePrefix,
5252
providertypes.ConsumerAddrsToPruneBytePrefix,
5353
providertypes.SlashLogBytePrefix,
54+
providertypes.VSCMaturedHandledThisBlockBytePrefix,
5455
}
5556
}
5657

@@ -94,6 +95,7 @@ func getAllFullyDefinedKeys() [][]byte {
9495
providertypes.KeyAssignmentReplacementsKey("chainID", providertypes.NewProviderConsAddress([]byte{0x05})),
9596
providertypes.ConsumerAddrsToPruneKey("chainID", 88),
9697
providertypes.SlashLogKey(providertypes.NewProviderConsAddress([]byte{0x05})),
98+
providertypes.VSCMaturedHandledThisBlockKey(),
9799
}
98100
}
99101

0 commit comments

Comments
 (0)