-
Notifications
You must be signed in to change notification settings - Fork 138
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
Slashing related e2e test improvements #461
Changes from 2 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 |
---|---|---|
|
@@ -14,249 +14,170 @@ import ( | |
clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" | ||
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" | ||
|
||
"github.com/cosmos/interchain-security/x/ccv/types" | ||
|
||
abci "github.com/tendermint/tendermint/abci/types" | ||
"github.com/tendermint/tendermint/crypto/ed25519" | ||
) | ||
|
||
// TestSendDowntimePacket tests consumer initiated slashing | ||
func (s *CCVTestSuite) TestSendSlashPacketDowntime() { | ||
s.SetupCCVChannel() | ||
s.SetupTransferChannel() | ||
validatorsPerChain := len(s.consumerChain.Vals.Validators) | ||
|
||
providerStakingKeeper := s.providerApp.GetE2eStakingKeeper() | ||
providerSlashingKeeper := s.providerApp.GetE2eSlashingKeeper() | ||
consumerKeeper := s.consumerApp.GetConsumerKeeper() | ||
providerKeeper := s.providerApp.GetProviderKeeper() | ||
|
||
// get a cross-chain validator address, pubkey and balance | ||
tmVals := s.consumerChain.Vals.Validators | ||
tmVal := tmVals[0] | ||
|
||
val, err := tmVal.ToProto() | ||
s.Require().NoError(err) | ||
pubkey, err := cryptocodec.FromTmProtoPublicKey(val.GetPubKey()) | ||
s.Require().Nil(err) | ||
consAddr := sdk.GetConsAddress(pubkey) | ||
valData, found := providerStakingKeeper.GetValidatorByConsAddr(s.providerCtx(), consAddr) | ||
s.Require().True(found) | ||
valOldBalance := valData.Tokens | ||
|
||
// create the validator's signing info record to allow jailing | ||
valInfo := slashingtypes.NewValidatorSigningInfo(consAddr, s.providerCtx().BlockHeight(), | ||
s.providerCtx().BlockHeight()-1, time.Time{}.UTC(), false, int64(0)) | ||
providerSlashingKeeper.SetValidatorSigningInfo(s.providerCtx(), consAddr, valInfo) | ||
|
||
// get valseUpdateId for current block height | ||
valsetUpdateId := consumerKeeper.GetHeightValsetUpdateID(s.consumerCtx(), | ||
uint64(s.consumerCtx().BlockHeight())) | ||
|
||
// construct the downtime packet with the validator address and power along | ||
// with the slashing and jailing parameters | ||
validator := abci.Validator{ | ||
Address: tmVal.Address, | ||
Power: tmVal.VotingPower, | ||
} | ||
|
||
oldBlockTime := s.consumerCtx().BlockTime() | ||
slashFraction := int64(100) | ||
packetData := types.NewSlashPacketData(validator, valsetUpdateId, stakingtypes.Downtime) | ||
timeout := uint64(oldBlockTime.Add(ccv.DefaultCCVTimeoutPeriod).UnixNano()) | ||
packet := channeltypes.NewPacket(packetData.GetBytes(), 1, ccv.ConsumerPortID, | ||
s.path.EndpointA.ChannelID, ccv.ProviderPortID, s.path.EndpointB.ChannelID, | ||
clienttypes.Height{}, timeout) | ||
|
||
// Send the downtime packet through CCV | ||
err = s.path.EndpointA.SendPacket(packet) | ||
s.Require().NoError(err) | ||
|
||
// Set outstanding slashing flag | ||
consumerKeeper.SetOutstandingDowntime(s.consumerCtx(), consAddr) | ||
|
||
// save next VSC packet info | ||
oldBlockTime = s.providerCtx().BlockTime() | ||
timeout = uint64(oldBlockTime.Add(ccv.DefaultCCVTimeoutPeriod).UnixNano()) | ||
valsetUpdateID := providerKeeper.GetValidatorSetUpdateId(s.providerCtx()) | ||
|
||
// receive the downtime packet on the provider chain; | ||
// RecvPacket() calls the provider endblocker thus sends a VSC packet to the consumer | ||
err = s.path.EndpointB.RecvPacket(packet) | ||
s.Require().NoError(err) | ||
|
||
// check that the validator was removed from the provider validator set | ||
s.Require().Len(s.providerChain.Vals.Validators, validatorsPerChain-1) | ||
// check that the VSC ID is updated on the consumer chain | ||
const ( | ||
downtimeTestCase = iota | ||
doubleSignTestCase | ||
) | ||
|
||
// update consumer client on the VSC packet sent from provider | ||
err = s.path.EndpointA.UpdateClient() | ||
s.Require().NoError(err) | ||
func (s *CCVTestSuite) TestSendAndApplySlashPacket() { | ||
|
||
// reconstruct VSC packet | ||
valUpdates := []abci.ValidatorUpdate{ | ||
{ | ||
PubKey: val.GetPubKey(), | ||
Power: int64(0), | ||
}, | ||
testCases := []int{ | ||
downtimeTestCase, | ||
doubleSignTestCase, | ||
} | ||
packetData2 := ccv.NewValidatorSetChangePacketData(valUpdates, valsetUpdateID, []string{consAddr.String()}) | ||
packet2 := channeltypes.NewPacket(packetData2.GetBytes(), 1, ccv.ProviderPortID, s.path.EndpointB.ChannelID, | ||
ccv.ConsumerPortID, s.path.EndpointA.ChannelID, clienttypes.Height{}, timeout) | ||
|
||
// receive VSC packet about jailing on the consumer chain | ||
err = s.path.EndpointA.RecvPacket(packet2) | ||
s.Require().NoError(err) | ||
for _, tc := range testCases { | ||
|
||
// check that the consumer update its VSC ID for the subsequent block | ||
s.Require().Equal(consumerKeeper.GetHeightValsetUpdateID(s.consumerCtx(), uint64(s.consumerCtx().BlockHeight())+1), valsetUpdateID) | ||
s.SetupTest() | ||
s.SetupCCVChannel() | ||
s.SetupTransferChannel() | ||
validatorsPerChain := len(s.consumerChain.Vals.Validators) | ||
|
||
providerStakingKeeper := s.providerApp.GetE2eStakingKeeper() | ||
providerSlashingKeeper := s.providerApp.GetE2eSlashingKeeper() | ||
providerKeeper := s.providerApp.GetProviderKeeper() | ||
consumerKeeper := s.consumerApp.GetConsumerKeeper() | ||
|
||
// get a cross-chain validator address, pubkey and balance | ||
tmVals := s.consumerChain.Vals.Validators | ||
tmVal := tmVals[0] | ||
|
||
val, err := tmVal.ToProto() | ||
s.Require().NoError(err) | ||
pubkey, err := cryptocodec.FromTmProtoPublicKey(val.GetPubKey()) | ||
s.Require().Nil(err) | ||
consAddr := sdk.GetConsAddress(pubkey) | ||
valData, found := providerStakingKeeper.GetValidatorByConsAddr(s.providerCtx(), consAddr) | ||
s.Require().True(found) | ||
valOldBalance := valData.Tokens | ||
|
||
// create the validator's signing info record to allow jailing | ||
valInfo := slashingtypes.NewValidatorSigningInfo(consAddr, s.providerCtx().BlockHeight(), | ||
s.providerCtx().BlockHeight()-1, time.Time{}.UTC(), false, int64(0)) | ||
providerSlashingKeeper.SetValidatorSigningInfo(s.providerCtx(), consAddr, valInfo) | ||
|
||
// get valseUpdateId for current block height | ||
valsetUpdateId := consumerKeeper.GetHeightValsetUpdateID( | ||
s.consumerCtx(), uint64(s.consumerCtx().BlockHeight())) | ||
|
||
// construct the slash packet with the validator address and power | ||
validator := abci.Validator{ | ||
Address: tmVal.Address, | ||
Power: tmVal.VotingPower, | ||
} | ||
|
||
// check that the validator was removed from the consumer validator set | ||
s.Require().Len(s.consumerChain.Vals.Validators, validatorsPerChain-1) | ||
// Construct packet data depending on the test case | ||
var infractionType stakingtypes.InfractionType | ||
|
||
err = s.path.EndpointB.UpdateClient() | ||
s.Require().NoError(err) | ||
if tc == downtimeTestCase { | ||
infractionType = stakingtypes.Downtime | ||
} else if tc == doubleSignTestCase { | ||
infractionType = stakingtypes.DoubleSign | ||
} | ||
packetData := ccv.NewSlashPacketData(validator, valsetUpdateId, infractionType).GetBytes() | ||
|
||
// check that the validator is successfully jailed on provider | ||
oldBlockTime := s.consumerCtx().BlockTime() | ||
timeout := uint64(oldBlockTime.Add(ccv.DefaultCCVTimeoutPeriod).UnixNano()) | ||
packet := channeltypes.NewPacket(packetData, 1, ccv.ConsumerPortID, s.path.EndpointA.ChannelID, | ||
ccv.ProviderPortID, s.path.EndpointB.ChannelID, clienttypes.Height{}, timeout) | ||
|
||
validatorJailed, ok := s.providerApp.GetE2eStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), consAddr) | ||
s.Require().True(ok) | ||
s.Require().True(validatorJailed.Jailed) | ||
s.Require().Equal(validatorJailed.Status, stakingtypes.Unbonding) | ||
// Send the slash packet through CCV | ||
err = s.path.EndpointA.SendPacket(packet) | ||
s.Require().NoError(err) | ||
|
||
// check that the validator's token was slashed | ||
slashedAmout := sdk.NewDec(1).QuoInt64(slashFraction).Mul(valOldBalance.ToDec()) | ||
resultingTokens := valOldBalance.Sub(slashedAmout.TruncateInt()) | ||
s.Require().Equal(resultingTokens, validatorJailed.GetTokens()) | ||
if tc == downtimeTestCase { | ||
// Set outstanding slashing flag if testing a downtime slash packet | ||
consumerKeeper.SetOutstandingDowntime(s.consumerCtx(), consAddr) | ||
} | ||
|
||
// check that the validator's unjailing time is updated | ||
valSignInfo, found := providerSlashingKeeper.GetValidatorSigningInfo(s.providerCtx(), consAddr) | ||
s.Require().True(found) | ||
s.Require().True(valSignInfo.JailedUntil.After(s.providerCtx().BlockHeader().Time)) | ||
// Note: RecvPacket advances two blocks. Let's say the provider is currently at height n. | ||
// The received slash packet will be handled during n, and the staking module will then | ||
// register a validator update from that packet during the endblocker of n. Then the ccv | ||
// module sends a VSC packet during the endblocker of n. The new validator set will be | ||
// committed to in block n+1, and will be in effect for block n+2. | ||
|
||
// check that the outstanding slashing flag is reset on the consumer | ||
pFlag := consumerKeeper.OutstandingDowntime(s.consumerCtx(), consAddr) | ||
s.Require().False(pFlag) | ||
valsetUpdateN := providerKeeper.GetValidatorSetUpdateId(s.providerCtx()) | ||
|
||
// check that slashing packet gets acknowledged | ||
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) | ||
err = s.path.EndpointA.AcknowledgePacket(packet, ack.Acknowledgement()) | ||
s.Require().NoError(err) | ||
} | ||
// receive the downtime packet on the provider chain. RecvPacket() calls the provider endblocker twice | ||
err = s.path.EndpointB.RecvPacket(packet) | ||
s.Require().NoError(err) | ||
|
||
func (s *CCVTestSuite) TestSendSlashPacketDoubleSign() { | ||
s.SetupCCVChannel() | ||
s.SetupTransferChannel() | ||
validatorsPerChain := len(s.consumerChain.Vals.Validators) | ||
// We've now advanced two blocks. | ||
|
||
providerStakingKeeper := s.providerApp.GetE2eStakingKeeper() | ||
providerSlashingKeeper := s.providerApp.GetE2eSlashingKeeper() | ||
providerKeeper := s.providerApp.GetProviderKeeper() | ||
consumerKeeper := s.consumerApp.GetConsumerKeeper() | ||
// One VSC packet should have been sent during block n | ||
expectedSentValsetUpdateId := valsetUpdateN | ||
_, found = providerKeeper.GetVscSendTimestamp(s.providerCtx(), | ||
s.consumerChain.ChainID, expectedSentValsetUpdateId) | ||
s.Require().True(found) | ||
|
||
// get a cross-chain validator address, pubkey and balance | ||
tmVals := s.consumerChain.Vals.Validators | ||
tmVal := tmVals[0] | ||
// Confirm the valset update Id was incremented twice. | ||
valsetUpdateNPlus2 := providerKeeper.GetValidatorSetUpdateId(s.providerCtx()) | ||
s.Require().Equal(valsetUpdateN+2, valsetUpdateNPlus2) | ||
|
||
val, err := tmVal.ToProto() | ||
s.Require().NoError(err) | ||
pubkey, err := cryptocodec.FromTmProtoPublicKey(val.GetPubKey()) | ||
s.Require().Nil(err) | ||
consAddr := sdk.GetConsAddress(pubkey) | ||
valData, found := providerStakingKeeper.GetValidatorByConsAddr(s.providerCtx(), consAddr) | ||
s.Require().True(found) | ||
valOldBalance := valData.Tokens | ||
|
||
// create the validator's signing info record to allow jailing | ||
valInfo := slashingtypes.NewValidatorSigningInfo(consAddr, s.providerCtx().BlockHeight(), | ||
s.providerCtx().BlockHeight()-1, time.Time{}.UTC(), false, int64(0)) | ||
providerSlashingKeeper.SetValidatorSigningInfo(s.providerCtx(), consAddr, valInfo) | ||
|
||
// get valseUpdateId for current block height | ||
valsetUpdateId := consumerKeeper.GetHeightValsetUpdateID(s.consumerCtx(), uint64(s.consumerCtx().BlockHeight())) | ||
|
||
// construct the downtime packet with the validator address and power along | ||
// with the slashing and jailing parameters | ||
validator := abci.Validator{ | ||
Address: tmVal.Address, | ||
Power: tmVal.VotingPower, | ||
} | ||
// check that the validator was removed from the provider validator set | ||
s.Require().Len(s.providerChain.Vals.Validators, validatorsPerChain-1) | ||
|
||
oldBlockTime := s.consumerCtx().BlockTime() | ||
packetData := types.NewSlashPacketData(validator, valsetUpdateId, stakingtypes.DoubleSign) | ||
// Relay the VSC packet to the consumer | ||
relayAllCommittedPackets(s, s.providerChain, s.path, ccv.ProviderPortID, s.path.EndpointB.ChannelID, 1) | ||
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. This is the new way that we relay the VSC packet from provider to consumer. Note that this method is commonly used elsewhere, and is a more effective test compared to manual packet construction 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. Why 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. Great question, after looking through the slashing file a bit, this method is specific to the relaying and provider handling for slash packets. There are methods further in the file that test the actual logic around sending downtime and double signing slash packets from a consumer. If we implemented that logic into this test, we'd be able to use We could consolidate all those tests into a single tests, but prob out of the scope for this PR 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. I've also updated the comment for |
||
|
||
timeout := uint64(oldBlockTime.Add(ccv.DefaultCCVTimeoutPeriod).UnixNano()) | ||
packet := channeltypes.NewPacket(packetData.GetBytes(), 1, ccv.ConsumerPortID, s.path.EndpointA.ChannelID, | ||
ccv.ProviderPortID, s.path.EndpointB.ChannelID, clienttypes.Height{}, timeout) | ||
// check that the consumer updated its VSC ID for the subsequent block | ||
actualValsetUpdateID := consumerKeeper.GetHeightValsetUpdateID( | ||
s.consumerCtx(), uint64(s.consumerCtx().BlockHeight())+1) | ||
s.Require().Equal(expectedSentValsetUpdateId, actualValsetUpdateID) | ||
|
||
// Send the downtime packet through CCV | ||
err = s.path.EndpointA.SendPacket(packet) | ||
s.Require().NoError(err) | ||
// check that the validator was removed from the consumer validator set | ||
s.Require().Len(s.consumerChain.Vals.Validators, validatorsPerChain-1) | ||
|
||
// save next VSC packet info | ||
oldBlockTime = s.providerCtx().BlockTime() | ||
timeout = uint64(oldBlockTime.Add(ccv.DefaultCCVTimeoutPeriod).UnixNano()) | ||
valsetUpdateID := providerKeeper.GetValidatorSetUpdateId(s.providerCtx()) | ||
err = s.path.EndpointB.UpdateClient() | ||
s.Require().NoError(err) | ||
|
||
// receive the downtime packet on the provider chain; | ||
// RecvPacket() calls the provider endblocker and thus sends a VSC packet to the consumer | ||
err = s.path.EndpointB.RecvPacket(packet) | ||
s.Require().NoError(err) | ||
// check that the validator is successfully jailed on provider | ||
validatorJailed, ok := providerStakingKeeper.GetValidatorByConsAddr(s.providerCtx(), consAddr) | ||
s.Require().True(ok) | ||
s.Require().True(validatorJailed.Jailed) | ||
s.Require().Equal(validatorJailed.Status, stakingtypes.Unbonding) | ||
|
||
// check that the validator was removed from the provider validator set | ||
s.Require().Len(s.providerChain.Vals.Validators, validatorsPerChain-1) | ||
// check that the VSC ID is updated on the consumer chain | ||
// check that the validator's tokens were slashed | ||
var slashFraction sdk.Dec | ||
if tc == downtimeTestCase { | ||
slashFraction = providerSlashingKeeper.SlashFractionDowntime(s.providerCtx()) | ||
|
||
// update consumer client on the VSC packet sent from provider | ||
err = s.path.EndpointA.UpdateClient() | ||
s.Require().NoError(err) | ||
|
||
// reconstruct VSC packet | ||
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. This was the old way we sent the VSC packet from provider to consumer |
||
valUpdates := []abci.ValidatorUpdate{ | ||
{ | ||
PubKey: val.GetPubKey(), | ||
Power: int64(0), | ||
}, | ||
} else if tc == doubleSignTestCase { | ||
slashFraction = providerSlashingKeeper.SlashFractionDoubleSign(s.providerCtx()) | ||
} | ||
slashedAmount := slashFraction.Mul(valOldBalance.ToDec()) | ||
|
||
resultingTokens := valOldBalance.Sub(slashedAmount.TruncateInt()) | ||
s.Require().Equal(resultingTokens, validatorJailed.GetTokens()) | ||
|
||
// check that the validator's unjailing time is updated | ||
valSignInfo, found := providerSlashingKeeper.GetValidatorSigningInfo(s.providerCtx(), consAddr) | ||
s.Require().True(found) | ||
s.Require().True(valSignInfo.JailedUntil.After(s.providerCtx().BlockHeader().Time)) | ||
|
||
if tc == downtimeTestCase { | ||
// check that the outstanding slashing flag is reset on the consumer | ||
pFlag := consumerKeeper.OutstandingDowntime(s.consumerCtx(), consAddr) | ||
s.Require().False(pFlag) | ||
|
||
// check that slashing packet gets acknowledged | ||
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) | ||
err = s.path.EndpointA.AcknowledgePacket(packet, ack.Acknowledgement()) | ||
s.Require().NoError(err) | ||
|
||
} else if tc == doubleSignTestCase { | ||
// check that validator was tombstoned | ||
s.Require().True(valSignInfo.Tombstoned) | ||
s.Require().True(valSignInfo.JailedUntil.Equal(evidencetypes.DoubleSignJailEndTime)) | ||
} | ||
} | ||
packetData2 := ccv.NewValidatorSetChangePacketData(valUpdates, valsetUpdateID, []string{}) | ||
packet2 := channeltypes.NewPacket(packetData2.GetBytes(), 1, ccv.ProviderPortID, s.path.EndpointB.ChannelID, | ||
ccv.ConsumerPortID, s.path.EndpointA.ChannelID, clienttypes.Height{}, timeout) | ||
|
||
// receive VSC packet about jailing on the consumer chain | ||
err = s.path.EndpointA.RecvPacket(packet2) | ||
s.Require().NoError(err) | ||
|
||
// check that the consumer update its VSC ID for the subsequent block | ||
s.Require().Equal(consumerKeeper.GetHeightValsetUpdateID(s.consumerCtx(), uint64(s.consumerCtx().BlockHeight())+1), valsetUpdateID) | ||
|
||
// check that the validator was removed from the consumer validator set | ||
s.Require().Len(s.consumerChain.Vals.Validators, validatorsPerChain-1) | ||
|
||
err = s.path.EndpointB.UpdateClient() | ||
s.Require().NoError(err) | ||
|
||
// check that the validator is successfully jailed on provider | ||
validatorJailed, ok := providerStakingKeeper.GetValidatorByConsAddr(s.providerCtx(), consAddr) | ||
s.Require().True(ok) | ||
s.Require().True(validatorJailed.Jailed) | ||
s.Require().Equal(validatorJailed.Status, stakingtypes.Unbonding) | ||
|
||
// check that the validator's token was slashed | ||
slashedAmout := providerSlashingKeeper.SlashFractionDoubleSign(s.providerCtx()).Mul(valOldBalance.ToDec()) | ||
resultingTokens := valOldBalance.Sub(slashedAmout.TruncateInt()) | ||
s.Require().Equal(resultingTokens, validatorJailed.GetTokens()) | ||
|
||
// check that the validator's unjailing time is updated | ||
valSignInfo, found := providerSlashingKeeper.GetValidatorSigningInfo(s.providerCtx(), consAddr) | ||
s.Require().True(found) | ||
s.Require().True(valSignInfo.JailedUntil.After(s.providerCtx().BlockHeader().Time)) | ||
|
||
// check that validator was tombstoned | ||
s.Require().True(valSignInfo.Tombstoned) | ||
s.Require().True(valSignInfo.JailedUntil.Equal(evidencetypes.DoubleSignJailEndTime)) | ||
} | ||
|
||
func (s *CCVTestSuite) TestSlashPacketAcknowldgement() { | ||
func (s *CCVTestSuite) TestSlashPacketAcknowledgement() { | ||
providerKeeper := s.providerApp.GetProviderKeeper() | ||
consumerKeeper := s.consumerApp.GetConsumerKeeper() | ||
|
||
|
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.
This was removed in favor of the slashing keeper's value used below