-
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 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 |
---|---|---|
|
@@ -14,249 +14,177 @@ 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 | ||
|
||
// update consumer client on the VSC packet sent from provider | ||
err = s.path.EndpointA.UpdateClient() | ||
s.Require().NoError(err) | ||
const ( | ||
downtimeTestCase = iota | ||
doubleSignTestCase | ||
) | ||
|
||
// reconstruct VSC packet | ||
valUpdates := []abci.ValidatorUpdate{ | ||
{ | ||
PubKey: val.GetPubKey(), | ||
Power: int64(0), | ||
}, | ||
// TestRelayAndApplySlashPacket tests that slash packets can be properly relayed | ||
// from consumer to provider, handled by provider, with a VSC and jailing/tombstoning | ||
// eventually effective on consumer and provider. | ||
// | ||
// Note: This method does not test the actual slash packet sending logic for downtime | ||
// and double-signing, see TestValidatorDowntime and TestValidatorDoubleSigning for | ||
// those types of tests. | ||
func (s *CCVTestSuite) TestRelayAndApplySlashPacket() { | ||
|
||
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) | ||
|
||
// 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) | ||
for _, tc := range testCases { | ||
|
||
// check that the validator was removed from the consumer validator set | ||
s.Require().Len(s.consumerChain.Vals.Validators, validatorsPerChain-1) | ||
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, | ||
} | ||
|
||
err = s.path.EndpointB.UpdateClient() | ||
s.Require().NoError(err) | ||
// Construct packet data depending on the test case | ||
var infractionType stakingtypes.InfractionType | ||
|
||
// check that the validator is successfully jailed on provider | ||
if tc == downtimeTestCase { | ||
infractionType = stakingtypes.Downtime | ||
} else if tc == doubleSignTestCase { | ||
infractionType = stakingtypes.DoubleSign | ||
} | ||
packetData := ccv.NewSlashPacketData(validator, valsetUpdateId, infractionType).GetBytes() | ||
|
||
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) | ||
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) | ||
|
||
// 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()) | ||
// Send the slash packet through CCV | ||
err = s.path.EndpointA.SendPacket(packet) | ||
s.Require().NoError(err) | ||
|
||
// 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 { | ||
// Set outstanding slashing flag if testing a downtime slash packet | ||
consumerKeeper.SetOutstandingDowntime(s.consumerCtx(), consAddr) | ||
} | ||
|
||
// check that the outstanding slashing flag is reset on the consumer | ||
pFlag := consumerKeeper.OutstandingDowntime(s.consumerCtx(), consAddr) | ||
s.Require().False(pFlag) | ||
// 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 slashing packet gets acknowledged | ||
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) | ||
err = s.path.EndpointA.AcknowledgePacket(packet, ack.Acknowledgement()) | ||
s.Require().NoError(err) | ||
} | ||
valsetUpdateN := providerKeeper.GetValidatorSetUpdateId(s.providerCtx()) | ||
|
||
func (s *CCVTestSuite) TestSendSlashPacketDoubleSign() { | ||
s.SetupCCVChannel() | ||
s.SetupTransferChannel() | ||
validatorsPerChain := len(s.consumerChain.Vals.Validators) | ||
// receive the downtime packet on the provider chain. RecvPacket() calls the provider endblocker twice | ||
err = s.path.EndpointB.RecvPacket(packet) | ||
s.Require().NoError(err) | ||
|
||
providerStakingKeeper := s.providerApp.GetE2eStakingKeeper() | ||
providerSlashingKeeper := s.providerApp.GetE2eSlashingKeeper() | ||
providerKeeper := s.providerApp.GetProviderKeeper() | ||
consumerKeeper := s.consumerApp.GetConsumerKeeper() | ||
// We've now advanced two blocks. | ||
|
||
// get a cross-chain validator address, pubkey and balance | ||
tmVals := s.consumerChain.Vals.Validators | ||
tmVal := tmVals[0] | ||
// 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) | ||
|
||
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, | ||
} | ||
// Confirm the valset update Id was incremented twice. | ||
valsetUpdateNPlus2 := providerKeeper.GetValidatorSetUpdateId(s.providerCtx()) | ||
s.Require().Equal(valsetUpdateN+2, valsetUpdateNPlus2) | ||
|
||
oldBlockTime := s.consumerCtx().BlockTime() | ||
packetData := types.NewSlashPacketData(validator, valsetUpdateId, stakingtypes.DoubleSign) | ||
// check that the validator was removed from the provider validator set | ||
s.Require().Len(s.providerChain.Vals.Validators, validatorsPerChain-1) | ||
|
||
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) | ||
// 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 |
||
|
||
// Send the downtime packet through CCV | ||
err = s.path.EndpointA.SendPacket(packet) | ||
s.Require().NoError(err) | ||
// 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) | ||
|
||
// save next VSC packet info | ||
oldBlockTime = s.providerCtx().BlockTime() | ||
timeout = uint64(oldBlockTime.Add(ccv.DefaultCCVTimeoutPeriod).UnixNano()) | ||
valsetUpdateID := providerKeeper.GetValidatorSetUpdateId(s.providerCtx()) | ||
// check that the validator was removed from the consumer validator set | ||
s.Require().Len(s.consumerChain.Vals.Validators, validatorsPerChain-1) | ||
|
||
// 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) | ||
err = s.path.EndpointB.UpdateClient() | ||
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 | ||
// 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) | ||
|
||
// update consumer client on the VSC packet sent from provider | ||
err = s.path.EndpointA.UpdateClient() | ||
s.Require().NoError(err) | ||
// check that the validator's tokens were slashed | ||
var slashFraction sdk.Dec | ||
if tc == downtimeTestCase { | ||
slashFraction = providerSlashingKeeper.SlashFractionDowntime(s.providerCtx()) | ||
|
||
// 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