-
Notifications
You must be signed in to change notification settings - Fork 122
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
feat: pause unbondings during equivocation proposal voting period #791
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
package e2e | ||
|
||
import ( | ||
"time" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" | ||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" | ||
providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" | ||
) | ||
|
||
func (s *CCVTestSuite) TestPauseUnbondingOnEquivocationProposal() { | ||
tests := []struct { | ||
name string | ||
setup func() govtypes.Content | ||
expectedWithoutProp unbondingsOnHold | ||
expectedDuringProp unbondingsOnHold | ||
}{ | ||
{ | ||
// Assert a text proposal doesn't pause any existing unbondings | ||
name: "text proposal and unbonding delegation", | ||
setup: func() govtypes.Content { | ||
var ( | ||
bondAmt = sdk.NewInt(10000000) | ||
delAddr = s.providerChain.SenderAccount.GetAddress() | ||
) | ||
// delegate bondAmt and undelegate it | ||
delegateAndUndelegate(s, delAddr, bondAmt, 1) | ||
|
||
return govtypes.NewTextProposal("title", "desc") | ||
}, | ||
expectedWithoutProp: unbondingsOnHold{ | ||
unbondingDelegationRefCount: 1, | ||
}, | ||
expectedDuringProp: unbondingsOnHold{ | ||
unbondingDelegationRefCount: 1, | ||
}, | ||
}, | ||
{ | ||
// Assert an equivocation proposal pauses nothing if there's no existing | ||
// unbondings. | ||
name: "equivocation proposal and no unbonding", | ||
setup: func() govtypes.Content { | ||
var ( | ||
val, _ = s.getValByIdx(0) | ||
consAddr, _ = val.GetConsAddr() | ||
) | ||
return providertypes.NewEquivocationProposal("title", "desc", | ||
[]*evidencetypes.Equivocation{{ | ||
Height: 1, | ||
Power: 1, | ||
Time: time.Now(), | ||
ConsensusAddress: consAddr.String(), | ||
}}, | ||
) | ||
}, | ||
expectedWithoutProp: unbondingsOnHold{}, | ||
expectedDuringProp: unbondingsOnHold{}, | ||
}, | ||
{ | ||
// Assert an equivocation proposal pauses unbonding delegations | ||
name: "equivocation proposal and unbonding delegation", | ||
setup: func() govtypes.Content { | ||
// create an unbonding entry of type UnbondingDelegation | ||
var ( | ||
bondAmt = sdk.NewInt(10000000) | ||
delAddr = s.providerChain.SenderAccount.GetAddress() | ||
val, _ = s.getValByIdx(0) | ||
consAddr, _ = val.GetConsAddr() | ||
) | ||
// delegate bondAmt and undelegate it | ||
delegateAndUndelegate(s, delAddr, bondAmt, 1) | ||
|
||
return providertypes.NewEquivocationProposal("title", "desc", | ||
[]*evidencetypes.Equivocation{{ | ||
Height: 1, | ||
Power: 1, | ||
Time: time.Now(), | ||
ConsensusAddress: consAddr.String(), | ||
}}, | ||
) | ||
}, | ||
expectedWithoutProp: unbondingsOnHold{ | ||
unbondingDelegationRefCount: 1, | ||
}, | ||
expectedDuringProp: unbondingsOnHold{ | ||
unbondingDelegationRefCount: 2, | ||
}, | ||
}, | ||
{ | ||
// Assert an equivocation proposal pauses redelegations | ||
name: "equivocation proposal and redelegate", | ||
setup: func() govtypes.Content { | ||
// create an unbonding entry of type UnbondingDelegation | ||
var ( | ||
bondAmt = sdk.NewInt(10000000) | ||
delAddr = s.providerChain.SenderAccount.GetAddress() | ||
val, valSrcAddr = s.getValByIdx(0) | ||
_, valDstAddr = s.getValByIdx(1) | ||
consAddr, _ = val.GetConsAddr() | ||
) | ||
// delegate bondAmt and redelegate it | ||
delegateAndRedelegate(s, delAddr, valSrcAddr, valDstAddr, bondAmt) | ||
|
||
return providertypes.NewEquivocationProposal("title", "desc", | ||
[]*evidencetypes.Equivocation{{ | ||
Height: 1, | ||
Power: 1, | ||
Time: time.Now(), | ||
ConsensusAddress: consAddr.String(), | ||
}}, | ||
) | ||
}, | ||
expectedWithoutProp: unbondingsOnHold{ | ||
redelegationRefCount: 1, | ||
}, | ||
expectedDuringProp: unbondingsOnHold{ | ||
redelegationRefCount: 2, | ||
}, | ||
}, | ||
{ | ||
// Assert an equivocation proposal pauses validator unbonding | ||
name: "equivocation proposal and validator unbonding", | ||
setup: func() govtypes.Content { | ||
var ( | ||
delAddr = s.providerChain.SenderAccount.GetAddress() | ||
val, valAddr = s.getValByIdx(0) | ||
consAddr, _ = val.GetConsAddr() | ||
) | ||
// unbond validator by undelegate all his delegation (test setup only | ||
// delegates from delAddr, there's no validator self delegation) | ||
undelegate(s, delAddr, valAddr, sdk.NewDec(1)) | ||
|
||
return providertypes.NewEquivocationProposal("title", "desc", | ||
[]*evidencetypes.Equivocation{{ | ||
Height: 1, | ||
Power: 1, | ||
Time: time.Now(), | ||
ConsensusAddress: consAddr.String(), | ||
}}, | ||
) | ||
}, | ||
expectedWithoutProp: unbondingsOnHold{ | ||
unbondingDelegationRefCount: 1, | ||
validatorUnbondingRefCount: 1, | ||
}, | ||
expectedDuringProp: unbondingsOnHold{ | ||
unbondingDelegationRefCount: 2, | ||
validatorUnbondingRefCount: 2, | ||
}, | ||
}, | ||
} | ||
submitProp := func(s *CCVTestSuite, content govtypes.Content) uint64 { | ||
proposal, err := s.providerApp.GetE2eGovKeeper().SubmitProposal(s.providerCtx(), content) | ||
s.Require().NoError(err) | ||
return proposal.ProposalId | ||
} | ||
addDepositProp := func(s *CCVTestSuite, proposalID uint64, depositAmt sdk.Coins) { | ||
depositorAddr := s.providerChain.SenderAccount.GetAddress() | ||
_, err := s.providerApp.GetE2eGovKeeper().AddDeposit( | ||
s.providerCtx(), proposalID, depositorAddr, depositAmt, | ||
) | ||
s.Require().NoError(err) | ||
} | ||
for i, tt := range tests { | ||
s.Run(tt.name, func() { | ||
if i+1 < len(tests) { | ||
// reset suite to reset provider client | ||
defer s.SetupTest() | ||
} | ||
|
||
//----------------------------------------- | ||
// Setup | ||
|
||
var ( | ||
govContent = tt.setup() | ||
val, valAddr = s.getValByIdx(0) | ||
consAddr, _ = val.GetConsAddr() | ||
govKeeper = s.providerApp.GetE2eGovKeeper() | ||
dustAmt = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1))) | ||
minDepositAmt = govKeeper.GetDepositParams(s.providerCtx()).MinDeposit | ||
) | ||
// Equivocation proposal requires validator signing info and a slash log | ||
s.setDefaultValSigningInfo(*s.providerChain.Vals.Validators[0]) | ||
s.providerApp.GetProviderKeeper().SetSlashLog(s.providerCtx(), | ||
providertypes.NewProviderConsAddress(consAddr)) | ||
// Reduce voting period | ||
votingParams := govKeeper.GetVotingParams(s.providerCtx()) | ||
votingParams.VotingPeriod = 3 * time.Second | ||
govKeeper.SetVotingParams(s.providerCtx(), votingParams) | ||
// Reduce deposit period | ||
depositParams := govKeeper.GetDepositParams(s.providerCtx()) | ||
depositParams.MaxDepositPeriod = 3 * time.Second | ||
govKeeper.SetDepositParams(s.providerCtx(), depositParams) | ||
// must move one block forward because unbonding validators are detected | ||
// during EndBlock. | ||
s.providerChain.NextBlock() | ||
checkStakingUnbondingOnHoldRefCount(s, valAddr, tt.expectedWithoutProp) | ||
|
||
//----------------------------------------- | ||
// #1 Create a proposal, activate it and wait for voting period | ||
|
||
proposalID := submitProp(s, govContent) | ||
checkStakingUnbondingOnHoldRefCount(s, valAddr, tt.expectedWithoutProp) | ||
// assert that until voting period starts, there's no pause triggered | ||
addDepositProp(s, proposalID, dustAmt) | ||
checkStakingUnbondingOnHoldRefCount(s, valAddr, tt.expectedWithoutProp) | ||
// activate prop | ||
addDepositProp(s, proposalID, minDepositAmt) | ||
checkStakingUnbondingOnHoldRefCount(s, valAddr, tt.expectedDuringProp) | ||
// assert that an additionnal deposit done after the activation doesn't | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth a quick check here to validate the voting period has not been reset? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How can that happen ? I see no message in the gov module that could reset the voting period. |
||
// trigger additionnal pauses | ||
addDepositProp(s, proposalID, dustAmt) | ||
checkStakingUnbondingOnHoldRefCount(s, valAddr, tt.expectedDuringProp) | ||
// ends the voting period | ||
incrementTime(s, votingParams.VotingPeriod) | ||
checkStakingUnbondingOnHoldRefCount(s, valAddr, tt.expectedWithoutProp) | ||
s.Assert().False( | ||
s.providerApp.GetProviderKeeper().HasEquivocationProposal(s.providerCtx(), proposalID), | ||
"proposalID should be removed from store after voting period", | ||
) | ||
|
||
//----------------------------------------- | ||
// #2 Create an other proposal but let it expire (not enough deposit) | ||
|
||
proposalID = submitProp(s, govContent) | ||
checkStakingUnbondingOnHoldRefCount(s, valAddr, tt.expectedWithoutProp) | ||
incrementTime(s, depositParams.MaxDepositPeriod) | ||
checkStakingUnbondingOnHoldRefCount(s, valAddr, tt.expectedWithoutProp) | ||
s.Assert().False( | ||
s.providerApp.GetProviderKeeper().HasEquivocationProposal(s.providerCtx(), proposalID), | ||
"proposalID shouldn't be registered if proposal didn't reach the voting period", | ||
) | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell this is not very conventional, but on the other hand, adding the
govKeeper
to theproviderKeeper
is even more ugly, because thegovKeeper
needs theproviderKeeper
for the proposal routes. So we would have to write something like (pseudo code):There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not against the current design per say, but imo adding a
govKeeper
pointer ref toproviderKeeper
, while usingSetGovKeeper
is the easiest to understand. No need to add a parameter forgovKeeper
inproviderKeeper.NewKeeper()
. You could just auto setgovKeeper
to nil, until it is explicitly set after the constructorThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see one benefit of having an additional parameter in
Hooks()
: it breaks compilation after upgrade. Typically when gaia will upgrade its interchain-security dependency, the fix will be obvious, while on the other hand, having to callSetGovKeeper
can be easily forgotten.Is there any validation we could setup to ensure
SetGovKeeper
is actually called ? A little bit likekeeper.mustValidateField
but for setters.