Skip to content
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

Merged
merged 5 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
354 changes: 141 additions & 213 deletions tests/e2e/slashing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor Author

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

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)
Copy link
Contributor Author

@shaspitz shaspitz Nov 9, 2022

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why relayAllCommittedPackets is needed for VSC packets and not the Slash packets here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 relayAllCommittedPackets. Instead this test just manually sends/relays slash packets.

We could consolidate all those tests into a single tests, but prob out of the scope for this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also updated the comment for TestRelayAndApplySlashPacket to make this more clear


// 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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()

Expand Down
Loading