diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index c4451a3c82..6071d7ba42 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -11,10 +11,7 @@ jobs: with: go-version: "1.18.0" # The Go version to download (if necessary) and use. - - name: Figure out where we are - run: ls - - - name: Unit tests + - name: Unit and e2e tests run: go test ./... - name: Integration tests diff --git a/README.md b/README.md index 6f8b3e2cd1..0fa0a4f1a4 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ CCV stands for cross chain validation and refers to the subset of Interchain Sec The code for CCV is housed under [x/ccv](./x/ccv). The `types` folder contains types and related functions that are used by both provider and consumer chains, while the `consumer` module contains the code run by consumer chains and the `provider` module contains the code run by provider chain. -NOTE: At the moment the testing app may not be functional, please rely on the IBC testing suite to write unit tests for the moment. - ## Instructions **Prerequisites** @@ -45,30 +43,56 @@ export PATH=$PATH:$(go env GOPATH)/bin Inspect the [Makefile](./Makefile) if curious. -**Running tests** +## Testing + +### Unit Tests + +Unit tests are useful for simple standalone functionality, and CRUD operations. Unit tests should use golang's standard testing package, and be defined in files formatted as ```_test.go``` in the same directory as the file being tested, following standard conventions. + +[Mocked external keepers](./testutil/keeper/mocks.go) (implemented with [gomock](https://github.com/golang/mock)) are available for testing more complex functionality, but still only relevant to execution within a single node. Ie. no internode or interchain communication. + +### End to End (e2e) Tests + +[e2e-tests](./e2e-tests/) utilize the [IBC Testing Package](https://github.com/cosmos/ibc-go/tree/main/testing), and test functionality that is wider in scope than a unit test, but still able to be validated in-memory. Ie. code where advancing blocks would be useful, simulated handshakes, simulated packet relays, etc. + +### Differential Tests (WIP) + +Similar to e2e tests, but they compare the system state to an expected state generated from a model implementation. + +### Integration Tests + +[Integration tests](./integration-tests/) run true consumer and provider chain binaries within a docker container and are relevant to the highest level of functionality. Integration tests use queries/transactions invoked from CLI to drive and validate the code. + +### Running Tests ```bash -# run all unit tests using make +# run all static analysis, unit, e2e, and integration tests using make +TODO +# run all unit and e2e tests using make make test -# run all unit tests using go +# run all unit and e2e tests using go go test ./... -# run all unit tests with verbose output +# run all unit and e2e tests with verbose output go test -v ./.. -# run all unit tests with coverage stats +# run all unit and e2e tests with coverage stats go test -cover ./.. # run a single unit test -go test -run / ./... +go test -run path/to/package # example: run a single unit test +go test -run TestSlashAcks ./x/ccv/provider/keeper +# run a single e2e test +go test -run / ./... +# example: run a single e2e test go test -run TestProviderTestSuite/TestPacketRoundtrip ./... -# run the integration tests +# run all integration tests go run ./integration-tests/... -# run integration tests with a local cosmos sdk +# run all integration tests with a local cosmos sdk go run ./integration-tests/... --local-sdk-path "/Users/bob/Documents/cosmos-sdk/" # run golang native fuzz tests (https://go.dev/doc/tutorial/fuzz) go test -fuzz= ``` -**Linters and static analysis** +### Linters and Static Analysis Several analyzers are used on the code including [CodeQL](https://codeql.github.com/), [SonarCloud](https://sonarcloud.io/), [golangci-lint](https://golangci-lint.run/) and [gosec](https://github.com/securego/gosec). Some of these are run on github when committing to PRs ect, but some tools are also applicable locally, and are built into golang. @@ -98,11 +122,11 @@ go install github.com/go-critic/go-critic/cmd/gocritic@latest pre-commit run --all-files ``` -**Debugging** +### Debugging If using VSCode, see [vscode-go/wiki/debugging](https://github.com/golang/vscode-go/wiki/debugging) to debug unit tests or go binaries. -**More** +### More More instructions will be added soon, in time for the testnet. diff --git a/e2e-tests/README.md b/e2e-tests/README.md new file mode 100644 index 0000000000..da07797183 --- /dev/null +++ b/e2e-tests/README.md @@ -0,0 +1,13 @@ +## End To End Testing + +E2e tests are categorized into files as follows: + +- `setup_test.go` - setup for the e2e tests +- `common_test.go` - helper functions +- `channel_init_test.go` - e2e tests for the _Channel Initialization_ sub-protocol +- `valset_update_test.go` - e2e tests for the _Validator Set Update_ sub-protocol +- `unbonding_test.go` - e2e tests for the _Completion of Unbonding Operations_ +- `slashing_test.go` - e2e tests for the _Consumer Initiated Slashing_ sub-protocol +- `distribution_test.go` - e2e tests for the _Reward Distribution_ sub-protocol +- `stop_consumer_test.go` - e2e tests for the _Consumer Chain Removal_ sub-protocol +- `normal_operations_test.go` - e2e tests for _normal operations_ of ICS enabled chains \ No newline at end of file diff --git a/e2e-tests/channel_init_test.go b/e2e-tests/channel_init_test.go new file mode 100644 index 0000000000..dc69c658c4 --- /dev/null +++ b/e2e-tests/channel_init_test.go @@ -0,0 +1,688 @@ +package e2e_test + +import ( + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + + app "github.com/cosmos/interchain-security/app/consumer" + + "fmt" + + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/interchain-security/x/ccv/utils" + + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" + + "encoding/json" + appConsumer "github.com/cosmos/interchain-security/app/consumer" + "github.com/cosmos/interchain-security/x/ccv/consumer" + providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" + ccv "github.com/cosmos/interchain-security/x/ccv/types" + "time" + + consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" + abci "github.com/tendermint/tendermint/abci/types" + crypto "github.com/tendermint/tendermint/proto/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + appProvider "github.com/cosmos/interchain-security/app/provider" + "github.com/cosmos/interchain-security/x/ccv/provider" + "github.com/cosmos/interchain-security/x/ccv/provider/types" +) + +func (suite *ConsumerKeeperTestSuite) TestConsumerGenesis() { + genesis := suite.consumerChain.App.(*app.App).ConsumerKeeper.ExportGenesis(suite.consumerChain.GetContext()) + + suite.Require().Equal(suite.providerClient, genesis.ProviderClientState) + suite.Require().Equal(suite.providerConsState, genesis.ProviderConsensusState) + + suite.Require().NotPanics(func() { + suite.consumerChain.App.(*app.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), genesis) + // reset suite to reset provider client + suite.SetupTest() + }) + + ctx := suite.consumerChain.GetContext() + portId := suite.consumerChain.App.(*app.App).ConsumerKeeper.GetPort(ctx) + suite.Require().Equal(consumertypes.PortID, portId) + + clientId, ok := suite.consumerChain.App.(*app.App).ConsumerKeeper.GetProviderClientID(ctx) + suite.Require().True(ok) + clientState, ok := suite.consumerChain.App.GetIBCKeeper().ClientKeeper.GetClientState(ctx, clientId) + suite.Require().True(ok) + suite.Require().Equal(genesis.ProviderClientState, clientState, "client state not set correctly after InitGenesis") + + suite.SetupCCVChannel() + + origTime := suite.consumerChain.GetContext().BlockTime() + + pk1, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + suite.Require().NoError(err) + pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + suite.Require().NoError(err) + pd := ccv.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{ + { + PubKey: pk1, + Power: 30, + }, + { + PubKey: pk2, + Power: 20, + }, + }, + 1, + nil, + ) + packet := channeltypes.NewPacket(pd.GetBytes(), 1, providertypes.PortID, suite.path.EndpointB.ChannelID, consumertypes.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + suite.consumerChain.App.(*app.App).ConsumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) + + // mocking the fact that consumer chain validators should be provider chain validators + // TODO: Fix testing suite so we can initialize both chains with the same validator set + valUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) + + restartGenesis := suite.consumerChain.App.(*app.App).ConsumerKeeper.ExportGenesis(suite.consumerChain.GetContext()) + restartGenesis.InitialValSet = valUpdates + + // ensure reset genesis is set correctly + providerChannel := suite.path.EndpointA.ChannelID + suite.Require().Equal(providerChannel, restartGenesis.ProviderChannelId) + maturityTime := suite.consumerChain.App.(*app.App).ConsumerKeeper.GetPacketMaturityTime(suite.consumerChain.GetContext(), 1) + unbondingPeriod, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetUnbondingTime(suite.ctx) + suite.Require().True(found) + suite.Require().Equal(uint64(origTime.Add(unbondingPeriod).UnixNano()), maturityTime, "maturity time is not set correctly in genesis") + + suite.Require().NotPanics(func() { + suite.consumerChain.App.(*app.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), restartGenesis) + }) +} + +func (suite *ConsumerTestSuite) TestOnChanOpenInit() { + channelID := "channel-1" + testCases := []struct { + name string + setup func(suite *ConsumerTestSuite) + expError bool + }{ + { + name: "success", + setup: func(suite *ConsumerTestSuite) { + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: false, + }, + { + name: "invalid: provider channel already established", + setup: func(suite *ConsumerTestSuite) { + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, "channel-2") + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: true, + }, + { + name: "invalid: UNORDERED channel", + setup: func(suite *ConsumerTestSuite) { + // set path ORDER to UNORDERED + suite.path.EndpointA.ChannelConfig.Order = channeltypes.UNORDERED + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.UNORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: true, + }, + { + name: "invalid: incorrect port", + setup: func(suite *ConsumerTestSuite) { + // set path port to invalid portID + suite.path.EndpointA.ChannelConfig.PortID = "invalidPort" + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.UNORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: true, + }, + { + name: "invalid: incorrect version", + setup: func(suite *ConsumerTestSuite) { + // set path port to invalid version + suite.path.EndpointA.ChannelConfig.Version = "invalidVersion" + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.UNORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: true, + }, + { + name: "invalid: verify provider chain failed", + setup: func(suite *ConsumerTestSuite) { + // setup a new path with provider client on consumer chain being different from genesis client + path := ibctesting.NewPath(suite.consumerChain, suite.providerChain) + // - client config + providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) + path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod + path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction + consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod + path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction + // - channel config + path.EndpointA.ChannelConfig.PortID = consumertypes.PortID + path.EndpointB.ChannelConfig.PortID = providertypes.PortID + path.EndpointA.ChannelConfig.Version = ccv.Version + path.EndpointB.ChannelConfig.Version = ccv.Version + path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED + + // create consumer client on provider chain, and provider client on consumer chain + err := suite.CreateCustomClient(path.EndpointB, consumerUnbondingPeriod) + suite.Require().NoError(err) + err = suite.CreateCustomClient(path.EndpointA, providerUnbondingPeriod) + suite.Require().NoError(err) + + suite.coordinator.CreateConnections(path) + suite.path = path + + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { + suite.SetupTest() // reset suite + tc.setup(suite) + + consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) + chanCap, err := suite.consumerChain.App.GetScopedIBCKeeper().NewCapability(suite.ctx, host.ChannelCapabilityPath(consumertypes.PortID, suite.path.EndpointA.ChannelID)) + suite.Require().NoError(err) + + err = consumerModule.OnChanOpenInit(suite.ctx, suite.path.EndpointA.ChannelConfig.Order, []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.PortID, + suite.path.EndpointA.ChannelID, chanCap, channeltypes.NewCounterparty(providertypes.PortID, ""), suite.path.EndpointA.ChannelConfig.Version) + + if tc.expError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + }) + } +} + +func (suite *ConsumerTestSuite) TestOnChanOpenTry() { + // OnOpenTry must error even with correct arguments + consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) + chanCap, err := suite.consumerChain.App.GetScopedIBCKeeper().NewCapability(suite.ctx, host.ChannelCapabilityPath(consumertypes.PortID, suite.path.EndpointA.ChannelID)) + suite.Require().NoError(err) + + _, err = consumerModule.OnChanOpenTry(suite.ctx, channeltypes.ORDERED, []string{"connection-1"}, consumertypes.PortID, "channel-1", chanCap, channeltypes.NewCounterparty(providertypes.PortID, "channel-1"), ccv.Version) + suite.Require().Error(err, "OnChanOpenTry callback must error on consumer chain") +} + +func (suite *ConsumerTestSuite) TestOnChanOpenAck() { + channelID := "channel-1" + counterChannelID := "channel-2" + testCases := []struct { + name string + setup func(suite *ConsumerTestSuite) + expError bool + }{ + { + name: "success", + setup: func(suite *ConsumerTestSuite) { + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: false, + }, + { + name: "invalid: provider channel already established", + setup: func(suite *ConsumerTestSuite) { + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, "channel-2") + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: true, + }, + { + name: "invalid: mismatched versions", + setup: func(suite *ConsumerTestSuite) { + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + // set provider version to invalid version + suite.path.EndpointB.ChannelConfig.Version = "invalidVersion" + }, + expError: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { + suite.SetupTest() // reset suite + tc.setup(suite) + + consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) + + md := providertypes.HandshakeMetadata{ + ProviderFeePoolAddr: "", // dummy address used + Version: suite.path.EndpointB.ChannelConfig.Version, + } + mdBz, err := (&md).Marshal() + suite.Require().NoError(err) + + err = consumerModule.OnChanOpenAck(suite.ctx, consumertypes.PortID, channelID, counterChannelID, string(mdBz)) + if tc.expError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + }) + } +} + +func (suite *ConsumerTestSuite) TestOnChanOpenConfirm() { + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, "channel-1", + channeltypes.NewChannel( + channeltypes.TRYOPEN, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, "channel-1"), + []string{"connection-1"}, ccv.Version, + )) + + consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) + + err := consumerModule.OnChanOpenConfirm(suite.ctx, consumertypes.PortID, "channel-1") + suite.Require().Error(err, "OnChanOpenConfirm must always fail") +} + +func (suite *ConsumerTestSuite) TestOnChanCloseInit() { + channelID := "channel-1" + testCases := []struct { + name string + setup func(suite *ConsumerTestSuite) + expError bool + }{ + { + name: "can close duplicate in-progress channel once provider channel is established", + setup: func(suite *ConsumerTestSuite) { + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, "different-channel") + }, + expError: false, + }, + { + name: "can close duplicate open channel once provider channel is established", + setup: func(suite *ConsumerTestSuite) { + // create open channel + suite.coordinator.CreateChannels(suite.path) + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, "different-channel") + }, + expError: false, + }, + { + name: "cannot close in-progress channel, no established channel yet", + setup: func(suite *ConsumerTestSuite) { + // Set INIT channel on consumer chain + suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, + channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), + []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), + ) + suite.path.EndpointA.ChannelID = channelID + }, + expError: true, + }, + { + name: "cannot close provider channel", + setup: func(suite *ConsumerTestSuite) { + // create open channel + suite.coordinator.CreateChannels(suite.path) + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, suite.path.EndpointA.ChannelID) + }, + expError: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { + suite.SetupTest() // reset suite + tc.setup(suite) + + consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) + + err := consumerModule.OnChanCloseInit(suite.ctx, consumertypes.PortID, suite.path.EndpointA.ChannelID) + + if tc.expError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + }) + } +} + +// TestProviderClientMatches tests that the provider client managed by the consumer keeper matches the client keeper's client state +func (suite *ConsumerKeeperTestSuite) TestProviderClientMatches() { + providerClientID, ok := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.ctx) + suite.Require().True(ok) + + clientState, _ := suite.consumerChain.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.ctx, providerClientID) + suite.Require().Equal(suite.providerClient, clientState, "stored client state does not match genesis provider client") +} + +// TestVerifyProviderChain tests the VerifyProviderChain method for the consumer keeper +func (suite *ConsumerKeeperTestSuite) TestVerifyProviderChain() { + var connectionHops []string + channelID := "channel-0" + testCases := []struct { + name string + setup func(suite *ConsumerKeeperTestSuite) + connectionHops []string + expError bool + }{ + { + name: "success", + setup: func(suite *ConsumerKeeperTestSuite) { + // create consumer client on provider chain + providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) + consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + suite.CreateCustomClient(suite.path.EndpointB, consumerUnbondingPeriod) + err := suite.path.EndpointB.CreateClient() + suite.Require().NoError(err) + + suite.coordinator.CreateConnections(suite.path) + + // set connection hops to be connection hop from path endpoint + connectionHops = []string{suite.path.EndpointA.ConnectionID} + }, + connectionHops: []string{suite.path.EndpointA.ConnectionID}, + expError: false, + }, + { + name: "connection hops is not length 1", + setup: func(suite *ConsumerKeeperTestSuite) { + // create consumer client on provider chain + providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) + consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + suite.CreateCustomClient(suite.path.EndpointB, consumerUnbondingPeriod) + + suite.coordinator.CreateConnections(suite.path) + + // set connection hops to be connection hop from path endpoint + connectionHops = []string{suite.path.EndpointA.ConnectionID, "connection-2"} + }, + expError: true, + }, + { + name: "connection does not exist", + setup: func(suite *ConsumerKeeperTestSuite) { + // set connection hops to be connection hop from path endpoint + connectionHops = []string{"connection-dne"} + }, + expError: true, + }, + { + name: "clientID does not match", + setup: func(suite *ConsumerKeeperTestSuite) { + // create consumer client on provider chain + providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) + consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + suite.CreateCustomClient(suite.path.EndpointB, consumerUnbondingPeriod) + + // create a new provider client on consumer chain that is different from the one in genesis + suite.CreateCustomClient(suite.path.EndpointA, providerUnbondingPeriod) + + suite.coordinator.CreateConnections(suite.path) + + // set connection hops to be connection hop from path endpoint + connectionHops = []string{suite.path.EndpointA.ConnectionID} + }, + expError: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { + suite.SetupTest() // reset suite + + tc.setup(suite) + + // Verify ProviderChain on consumer chain using path returned by setup + err := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.VerifyProviderChain(suite.ctx, channelID, connectionHops) + + if tc.expError { + suite.Require().Error(err, "invalid case did not return error") + } else { + suite.Require().NoError(err, "valid case returned error") + } + }) + } +} + +// TestConsumerChainProposalHandler tests the handler for consumer chain proposals +// for both CreateConsumerChainProposal and StopConsumerChainProposal +// +// TODO: Determine if it's possible to make this a unit test +func (suite *ProviderTestSuite) TestConsumerChainProposalHandler() { + var ( + ctx sdk.Context + content govtypes.Content + err error + ) + + testCases := []struct { + name string + malleate func(*ProviderTestSuite) + expPass bool + }{ + { + "valid create consumerchain proposal", func(suite *ProviderTestSuite) { + initialHeight := clienttypes.NewHeight(2, 3) + // ctx blocktime is after proposal's spawn time + ctx = suite.providerChain.GetContext().WithBlockTime(time.Now().Add(time.Hour)) + content, err = types.NewCreateConsumerChainProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now()) + suite.Require().NoError(err) + }, true, + }, + { + "valid stop consumerchain proposal", func(suite *ProviderTestSuite) { + ctx = suite.providerChain.GetContext().WithBlockTime(time.Now().Add(time.Hour)) + content, err = types.NewStopConsumerChainProposal("title", "description", "chainID", time.Now()) + suite.Require().NoError(err) + }, true, + }, + { + "nil proposal", func(suite *ProviderTestSuite) { + ctx = suite.providerChain.GetContext() + content = nil + }, false, + }, + { + "unsupported proposal type", func(suite *ProviderTestSuite) { + ctx = suite.providerChain.GetContext() + content = distributiontypes.NewCommunityPoolSpendProposal(ibctesting.Title, ibctesting.Description, suite.providerChain.SenderAccount.GetAddress(), sdk.NewCoins(sdk.NewCoin("communityfunds", sdk.NewInt(10)))) + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + tc.malleate(suite) + + proposalHandler := provider.NewConsumerChainProposalHandler(suite.providerChain.App.(*appProvider.App).ProviderKeeper) + + err = proposalHandler(ctx, content) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *ProviderKeeperTestSuite) TestMakeConsumerGenesis() { + suite.SetupTest() + + actualGenesis, err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.MakeConsumerGenesis(suite.providerChain.GetContext()) + suite.Require().NoError(err) + + jsonString := `{"params":{"enabled":true, "blocks_per_distribution_transmission":1000, "lock_unbonding_on_timeout": false},"new_chain":true,"provider_client_state":{"chain_id":"testchain1","trust_level":{"numerator":1,"denominator":3},"trusting_period":907200000000000,"unbonding_period":1814400000000000,"max_clock_drift":10000000000,"frozen_height":{},"latest_height":{"revision_height":5},"proof_specs":[{"leaf_spec":{"hash":1,"prehash_value":1,"length":1,"prefix":"AA=="},"inner_spec":{"child_order":[0,1],"child_size":33,"min_prefix_length":4,"max_prefix_length":12,"hash":1}},{"leaf_spec":{"hash":1,"prehash_value":1,"length":1,"prefix":"AA=="},"inner_spec":{"child_order":[0,1],"child_size":32,"min_prefix_length":1,"max_prefix_length":1,"hash":1}}],"upgrade_path":["upgrade","upgradedIBCState"],"allow_update_after_expiry":true,"allow_update_after_misbehaviour":true},"provider_consensus_state":{"timestamp":"2020-01-02T00:00:10Z","root":{"hash":"LpGpeyQVLUo9HpdsgJr12NP2eCICspcULiWa5u9udOA="},"next_validators_hash":"E30CE736441FB9101FADDAF7E578ABBE6DFDB67207112350A9A904D554E1F5BE"},"unbonding_sequences":null,"initial_val_set":[{"pub_key":{"type":"tendermint/PubKeyEd25519","value":"dcASx5/LIKZqagJWN0frOlFtcvz91frYmj/zmoZRWro="},"power":1}]}` + + var expectedGenesis consumertypes.GenesisState + err = json.Unmarshal([]byte(jsonString), &expectedGenesis) + suite.Require().NoError(err) + + // Zero out differing fields- TODO: figure out how to get the test suite to + // keep these deterministic + actualGenesis.ProviderConsensusState.NextValidatorsHash = []byte{} + expectedGenesis.ProviderConsensusState.NextValidatorsHash = []byte{} + + // set valset to one empty validator because SetupTest() creates 4 validators per chain + actualGenesis.InitialValSet = []abci.ValidatorUpdate{{PubKey: crypto.PublicKey{}, Power: actualGenesis.InitialValSet[0].Power}} + expectedGenesis.InitialValSet[0].PubKey = crypto.PublicKey{} + + actualGenesis.ProviderConsensusState.Root.Hash = []byte{} + expectedGenesis.ProviderConsensusState.Root.Hash = []byte{} + + suite.Require().Equal(actualGenesis, expectedGenesis, "consumer chain genesis created incorrectly") +} + +func (suite *ProviderKeeperTestSuite) TestCreateConsumerChainProposal() { + var ( + ctx sdk.Context + proposal *types.CreateConsumerChainProposal + ok bool + ) + + chainID := "chainID" + initialHeight := clienttypes.NewHeight(2, 3) + lockUbdOnTimeout := false + + testCases := []struct { + name string + malleate func(*ProviderKeeperTestSuite) + expPass bool + spawnReached bool + }{ + { + "valid create consumer chain proposal: spawn time reached", func(suite *ProviderKeeperTestSuite) { + // ctx blocktime is after proposal's spawn time + ctx = suite.providerChain.GetContext().WithBlockTime(time.Now().Add(time.Hour)) + content, err := types.NewCreateConsumerChainProposal("title", "description", chainID, initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now()) + suite.Require().NoError(err) + proposal, ok = content.(*types.CreateConsumerChainProposal) + suite.Require().True(ok) + proposal.LockUnbondingOnTimeout = lockUbdOnTimeout + }, true, true, + }, + { + "valid proposal: spawn time has not yet been reached", func(suite *ProviderKeeperTestSuite) { + // ctx blocktime is before proposal's spawn time + ctx = suite.providerChain.GetContext().WithBlockTime(time.Now()) + content, err := types.NewCreateConsumerChainProposal("title", "description", chainID, initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now().Add(time.Hour)) + suite.Require().NoError(err) + proposal, ok = content.(*types.CreateConsumerChainProposal) + suite.Require().True(ok) + proposal.LockUnbondingOnTimeout = lockUbdOnTimeout + }, true, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate(suite) + + err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerChainProposal(ctx, proposal) + if tc.expPass { + suite.Require().NoError(err, "error returned on valid case") + if tc.spawnReached { + clientId, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId(ctx, chainID) + suite.Require().True(found, "consumer client not found") + consumerGenesis, ok := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis(ctx, chainID) + suite.Require().True(ok) + + expectedGenesis, err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.MakeConsumerGenesis(ctx) + suite.Require().NoError(err) + + suite.Require().Equal(expectedGenesis, consumerGenesis) + suite.Require().NotEqual("", clientId, "consumer client was not created after spawn time reached") + } else { + gotProposal := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetPendingCreateProposal(ctx, proposal.SpawnTime, chainID) + suite.Require().Equal(initialHeight, gotProposal.InitialHeight, "unexpected pending proposal (InitialHeight)") + suite.Require().Equal(lockUbdOnTimeout, gotProposal.LockUnbondingOnTimeout, "unexpected pending proposal (LockUnbondingOnTimeout)") + } + } else { + suite.Require().Error(err, "did not return error on invalid case") + } + }) + } +} diff --git a/e2e-tests/common_test.go b/e2e-tests/common_test.go new file mode 100644 index 0000000000..3624e0b093 --- /dev/null +++ b/e2e-tests/common_test.go @@ -0,0 +1,340 @@ +package e2e_test + +import ( + "strings" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" + providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" + ccv "github.com/cosmos/interchain-security/x/ccv/types" + "github.com/cosmos/interchain-security/x/ccv/utils" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/ibc-go/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + + appConsumer "github.com/cosmos/interchain-security/app/consumer" + appProvider "github.com/cosmos/interchain-security/app/provider" +) + +// ChainType defines the type of chain (either provider or consumer) +type ChainType int + +const ( + Provider ChainType = iota + Consumer +) + +func (s *ProviderTestSuite) providerCtx() sdk.Context { + return s.providerChain.GetContext() +} + +func (s *ProviderTestSuite) consumerCtx() sdk.Context { + return s.consumerChain.GetContext() +} + +func (s *ProviderTestSuite) providerBondDenom() string { + return s.providerChain.App.(*appProvider.App).StakingKeeper.BondDenom(s.providerCtx()) +} + +func (s *ProviderTestSuite) getVal(index int) (validator stakingtypes.Validator, valAddr sdk.ValAddress) { + // Choose a validator, and get its address and data structure into the correct types + tmValidator := s.providerChain.Vals.Validators[index] + valAddr, err := sdk.ValAddressFromHex(tmValidator.Address.String()) + s.Require().NoError(err) + validator, found := s.providerChain.App.(*appProvider.App).StakingKeeper.GetValidator(s.providerCtx(), valAddr) + s.Require().True(found) + + return validator, valAddr +} + +func getBalance(s *ProviderTestSuite, providerCtx sdk.Context, delAddr sdk.AccAddress) sdk.Int { + return s.providerChain.App.(*appProvider.App).BankKeeper.GetBalance(providerCtx, delAddr, s.providerBondDenom()).Amount +} + +// delegateAndUndelegate delegates bondAmt from delAddr to the first validator +// and then immediately undelegates 1/shareDiv of that delegation +func delegateAndUndelegate(s *ProviderTestSuite, delAddr sdk.AccAddress, bondAmt sdk.Int, shareDiv int64) (initBalance sdk.Int, valsetUpdateId uint64) { + // delegate + initBalance, shares, valAddr := delegate(s, delAddr, bondAmt) + + // check that the correct number of tokens were taken out of the delegator's account + s.Require().True(getBalance(s, s.providerCtx(), delAddr).Equal(initBalance.Sub(bondAmt))) + + // undelegate 1/shareDiv + valsetUpdateId = undelegate(s, delAddr, valAddr, shares.QuoInt64(shareDiv)) + + // check that the tokens have not been returned yet + s.Require().True(getBalance(s, s.providerCtx(), delAddr).Equal(initBalance.Sub(bondAmt))) + + return initBalance, valsetUpdateId +} + +// delegate delegates bondAmt to the first validator +func delegate(s *ProviderTestSuite, delAddr sdk.AccAddress, bondAmt sdk.Int) (initBalance sdk.Int, shares sdk.Dec, valAddr sdk.ValAddress) { + initBalance = getBalance(s, s.providerCtx(), delAddr) + // choose a validator + validator, valAddr := s.getVal(0) + // delegate bondAmt tokens on provider to change validator powers + shares, err := s.providerChain.App.(*appProvider.App).StakingKeeper.Delegate( + s.providerCtx(), + delAddr, + bondAmt, + stakingtypes.Unbonded, + stakingtypes.Validator(validator), + true, + ) + s.Require().NoError(err) + // check that the correct number of tokens were taken out of the delegator's account + s.Require().True(getBalance(s, s.providerCtx(), delAddr).Equal(initBalance.Sub(bondAmt))) + return initBalance, shares, valAddr +} + +// undelegate unbonds an amount of delegator shares from a given validator +func undelegate(s *ProviderTestSuite, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) (valsetUpdateId uint64) { + _, err := s.providerChain.App.(*appProvider.App).StakingKeeper.Undelegate(s.providerCtx(), delAddr, valAddr, sharesAmount) + s.Require().NoError(err) + + // save the current valset update ID + valsetUpdateID := s.providerChain.App.(*appProvider.App).ProviderKeeper.GetValidatorSetUpdateId(s.providerCtx()) + + return valsetUpdateID +} + +// relayAllCommittedPackets relays all committed packets from `srcChain` on `path` +func relayAllCommittedPackets( + s *ProviderTestSuite, + srcChain *ibctesting.TestChain, + path *ibctesting.Path, + portID string, + channelID string, + expectedPackets int, +) { + // check that the packets are committed in state + commitments := srcChain.App.GetIBCKeeper().ChannelKeeper.GetAllPacketCommitmentsAtChannel( + srcChain.GetContext(), + portID, + channelID, + ) + s.Require().Equal(expectedPackets, len(commitments), "did not find packet commitments") + + // relay all packets from srcChain to counterparty + for _, commitment := range commitments { + // - get packets + packet, found := srcChain.GetSentPacket(commitment.Sequence) + s.Require().True(found, "did not find sent packet") + // - relay the packet + err := path.RelayPacket(packet) + s.Require().NoError(err) + } +} + +// incrementTimeByUnbondingPeriod increments the overall time by +// - if provider == true, the unbonding period on the provider; +// - otherwise, the unbonding period on the consumer. +// +// Note that it is expected for the provider unbonding period +// to be one day larger than the consumer unbonding period. +func incrementTimeByUnbondingPeriod(s *ProviderTestSuite, chainType ChainType) { + // Get unboding period from staking keeper + providerUnbondingPeriod := s.providerChain.App.GetStakingKeeper().UnbondingTime(s.providerCtx()) + consumerUnbondingPeriod, found := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetUnbondingTime(s.consumerCtx()) + s.Require().True(found) + expectedUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + s.Require().Equal(expectedUnbondingPeriod+24*time.Hour, providerUnbondingPeriod, "unexpected provider unbonding period") + s.Require().Equal(expectedUnbondingPeriod, consumerUnbondingPeriod, "unexpected consumer unbonding period") + var jumpPeriod time.Duration + if chainType == Provider { + jumpPeriod = providerUnbondingPeriod + } else { + jumpPeriod = consumerUnbondingPeriod + } + // Make sure the clients do not expire + jumpPeriod = jumpPeriod/4 + time.Hour + for i := 0; i < 4; i++ { + s.coordinator.IncrementTimeBy(jumpPeriod) + // Update the provider client on the consumer + err := s.path.EndpointA.UpdateClient() + s.Require().NoError(err) + // Update the consumer client on the provider + err = s.path.EndpointB.UpdateClient() + s.Require().NoError(err) + } +} + +func checkStakingUnbondingOps(s *ProviderTestSuite, id uint64, found bool, onHold bool) { + stakingUnbondingOp, wasFound := getStakingUnbondingDelegationEntry(s.providerCtx(), s.providerChain.App.(*appProvider.App).StakingKeeper, id) + s.Require().True(found == wasFound) + s.Require().True(onHold == (0 < stakingUnbondingOp.UnbondingOnHoldRefCount)) +} + +func checkCCVUnbondingOp(s *ProviderTestSuite, providerCtx sdk.Context, chainID string, valUpdateID uint64, found bool) { + entries, wasFound := s.providerChain.App.(*appProvider.App).ProviderKeeper.GetUnbondingOpsFromIndex(providerCtx, chainID, valUpdateID) + s.Require().True(found == wasFound) + if found { + s.Require().True(len(entries) > 0, "No unbonding ops found") + s.Require().True(len(entries[0].UnbondingConsumerChains) > 0, "Unbonding op with no consumer chains") + s.Require().True(strings.Compare(entries[0].UnbondingConsumerChains[0], "testchain2") == 0, "Unbonding op with unexpected consumer chain") + } +} + +func getStakingUnbondingDelegationEntry(ctx sdk.Context, k stakingkeeper.Keeper, id uint64) (stakingUnbondingOp stakingtypes.UnbondingDelegationEntry, found bool) { + stakingUbd, found := k.GetUnbondingDelegationByUnbondingId(ctx, id) + + for _, entry := range stakingUbd.Entries { + if entry.UnbondingId == id { + stakingUnbondingOp = entry + found = true + break + } + } + + return stakingUnbondingOp, found +} + +// SendEmptyVSCPacket sends a VSC packet without any changes +// to ensure that the channel gets established +func (suite *ConsumerKeeperTestSuite) SendEmptyVSCPacket() { + providerKeeper := suite.providerChain.App.(*appProvider.App).ProviderKeeper + + oldBlockTime := suite.providerChain.GetContext().BlockTime() + timeout := uint64(ccv.GetTimeoutTimestamp(oldBlockTime).UnixNano()) + + valUpdateID := providerKeeper.GetValidatorSetUpdateId(suite.providerChain.GetContext()) + + pd := ccv.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{}, + valUpdateID, + nil, + ) + + seq, ok := suite.providerChain.App.(*appProvider.App).GetIBCKeeper().ChannelKeeper.GetNextSequenceSend( + suite.providerChain.GetContext(), providertypes.PortID, suite.path.EndpointB.ChannelID) + suite.Require().True(ok) + + packet := channeltypes.NewPacket(pd.GetBytes(), seq, providertypes.PortID, suite.path.EndpointB.ChannelID, + consumertypes.PortID, suite.path.EndpointA.ChannelID, clienttypes.Height{}, timeout) + + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + err = suite.path.EndpointA.RecvPacket(packet) + suite.Require().NoError(err) +} + +// commitSlashPacket returns a commit hash for the given slash packet data +// Note that it must be called before sending the embedding IBC packet. +func (suite *ConsumerKeeperTestSuite) commitSlashPacket(ctx sdk.Context, packetData ccv.SlashPacketData) []byte { + oldBlockTime := ctx.BlockTime() + timeout := uint64(ccv.GetTimeoutTimestamp(oldBlockTime).UnixNano()) + + packet := channeltypes.NewPacket(packetData.GetBytes(), 1, consumertypes.PortID, suite.path.EndpointA.ChannelID, + providertypes.PortID, suite.path.EndpointB.ChannelID, clienttypes.Height{}, timeout) + + return channeltypes.CommitPacket(suite.consumerChain.App.AppCodec(), packet) +} + +// incrementTimeBy increments the overall time by jumpPeriod +func incrementTimeBy(s *ConsumerKeeperTestSuite, jumpPeriod time.Duration) { + // Get unboding period from staking keeper + consumerUnbondingPeriod, found := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetUnbondingTime(s.consumerChain.GetContext()) + s.Require().True(found) + split := 1 + if jumpPeriod > consumerUnbondingPeriod/utils.TrustingPeriodFraction { + // Make sure the clients do not expire + split = 4 + jumpPeriod = jumpPeriod / 4 + } + for i := 0; i < split; i++ { + s.coordinator.IncrementTimeBy(jumpPeriod) + // Update the provider client on the consumer + err := s.path.EndpointA.UpdateClient() + s.Require().NoError(err) + // Update the consumer client on the provider + err = s.path.EndpointB.UpdateClient() + s.Require().NoError(err) + } +} + +// TODO: The two CreateCustomClient methods below can be consolidated when test suite structures are consolidated + +// CreateCustomClient creates an IBC client on the endpoint +// using the given unbonding period. +// It will update the clientID for the endpoint if the message +// is successfully executed. +func (suite *ConsumerKeeperTestSuite) CreateCustomClient(endpoint *ibctesting.Endpoint, unbondingPeriod time.Duration) { + // ensure counterparty has committed state + endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) + + suite.Require().Equal(exported.Tendermint, endpoint.ClientConfig.GetClientType(), "only Tendermint client supported") + + tmConfig, ok := endpoint.ClientConfig.(*ibctesting.TendermintConfig) + require.True(endpoint.Chain.T, ok) + tmConfig.UnbondingPeriod = unbondingPeriod + tmConfig.TrustingPeriod = unbondingPeriod / utils.TrustingPeriodFraction + + height := endpoint.Counterparty.Chain.LastHeader.GetHeight().(clienttypes.Height) + UpgradePath := []string{"upgrade", "upgradedIBCState"} + clientState := ibctmtypes.NewClientState( + endpoint.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, + ) + consensusState := endpoint.Counterparty.Chain.LastHeader.ConsensusState() + + msg, err := clienttypes.NewMsgCreateClient( + clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.T, err) + + res, err := endpoint.Chain.SendMsgs(msg) + require.NoError(endpoint.Chain.T, err) + + endpoint.ClientID, err = ibctesting.ParseClientIDFromEvents(res.GetEvents()) + require.NoError(endpoint.Chain.T, err) +} + +// CreateCustomClient creates an IBC client on the endpoint +// using the given unbonding period. +// It will update the clientID for the endpoint if the message +// is successfully executed. +func (suite *ConsumerTestSuite) CreateCustomClient(endpoint *ibctesting.Endpoint, unbondingPeriod time.Duration) (err error) { + // ensure counterparty has committed state + endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) + + suite.Require().Equal(exported.Tendermint, endpoint.ClientConfig.GetClientType(), "only Tendermint client supported") + + tmConfig, ok := endpoint.ClientConfig.(*ibctesting.TendermintConfig) + require.True(endpoint.Chain.T, ok) + tmConfig.UnbondingPeriod = unbondingPeriod + tmConfig.TrustingPeriod = unbondingPeriod / utils.TrustingPeriodFraction + + height := endpoint.Counterparty.Chain.LastHeader.GetHeight().(clienttypes.Height) + UpgradePath := []string{"upgrade", "upgradedIBCState"} + clientState := ibctmtypes.NewClientState( + endpoint.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, + ) + consensusState := endpoint.Counterparty.Chain.LastHeader.ConsensusState() + + msg, err := clienttypes.NewMsgCreateClient( + clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.T, err) + + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ClientID, err = ibctesting.ParseClientIDFromEvents(res.GetEvents()) + require.NoError(endpoint.Chain.T, err) + + return nil +} diff --git a/e2e-tests/distribution_test.go b/e2e-tests/distribution_test.go new file mode 100644 index 0000000000..e3b0c96959 --- /dev/null +++ b/e2e-tests/distribution_test.go @@ -0,0 +1,3 @@ +package e2e_test + +// TODO: Add tests for the reward distribution sub protocol diff --git a/e2e-tests/normal_operations_test.go b/e2e-tests/normal_operations_test.go new file mode 100644 index 0000000000..4acd881563 --- /dev/null +++ b/e2e-tests/normal_operations_test.go @@ -0,0 +1,86 @@ +package e2e_test + +import ( + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + appConsumer "github.com/cosmos/interchain-security/app/consumer" + "github.com/cosmos/interchain-security/x/ccv/consumer/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +// Tests the tracking of historical info in the context of new blocks being committed +func (k ConsumerKeeperTestSuite) TestTrackHistoricalInfo() { + consumerKeeper := k.consumerChain.App.(*appConsumer.App).ConsumerKeeper + cCtx := k.consumerChain.GetContext + + // save init consumer valset length + initValsetLen := len(consumerKeeper.GetAllCCValidator(cCtx())) + // save current block height + initHeight := cCtx().BlockHeight() + + // define an utility function that creates a new cross-chain validator + // and then call track historical info in the next block + createVal := func(k ConsumerKeeperTestSuite) { + // add new validator to consumer states + pk := ed25519.GenPrivKey().PubKey() + cVal, err := types.NewCCValidator(pk.Address(), int64(1), pk) + k.Require().NoError(err) + + consumerKeeper.SetCCValidator(k.consumerChain.GetContext(), cVal) + + // commit block in order to call TrackHistoricalInfo + k.consumerChain.NextBlock() + } + + // testsetup create 2 validators and then call track historical info with header block height + // increased by HistoricalEntries in order to prune the historical info less or equal to the current block height + // Note that historical info containing the created validators are stored during the next block BeginBlocker + // and thus are indexed with the respective block heights InitHeight+1 and InitHeight+2 + testSetup := []func(ConsumerKeeperTestSuite){ + createVal, + createVal, + func(k ConsumerKeeperTestSuite) { + newHeight := k.consumerChain.GetContext().BlockHeight() + int64(types.HistoricalEntries) + header := tmproto.Header{ + ChainID: "HelloChain", + Height: newHeight, + } + ctx := k.consumerChain.GetContext().WithBlockHeader(header) + k.consumerChain.App.(*appConsumer.App).ConsumerKeeper.TrackHistoricalInfo(ctx) + }, + } + + for _, ts := range testSetup { + ts(k) + } + + // test cases verify that historical info entries are pruned when their height + // is below CurrentHeight - HistoricalEntries, and check that their valset gets updated + testCases := []struct { + height int64 + found bool + expLen int + }{ + { + height: initHeight + 1, + found: false, + expLen: 0, + }, + { + height: initHeight + 2, + found: false, + expLen: 0, + }, + { + height: initHeight + int64(types.HistoricalEntries) + 2, + found: true, + expLen: initValsetLen + 2, + }, + } + + for _, tc := range testCases { + cCtx().WithBlockHeight(tc.height) + hi, found := consumerKeeper.GetHistoricalInfo(cCtx().WithBlockHeight(tc.height), tc.height) + k.Require().Equal(tc.found, found) + k.Require().Len(hi.Valset, tc.expLen) + } +} diff --git a/e2e-tests/setup_test.go b/e2e-tests/setup_test.go new file mode 100644 index 0000000000..5e34d856eb --- /dev/null +++ b/e2e-tests/setup_test.go @@ -0,0 +1,475 @@ +package e2e_test + +import ( + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" + + "bytes" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + ccv "github.com/cosmos/interchain-security/x/ccv/types" + "github.com/cosmos/interchain-security/x/ccv/utils" + + transfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + + appConsumer "github.com/cosmos/interchain-security/app/consumer" + appProvider "github.com/cosmos/interchain-security/app/provider" + "github.com/cosmos/interchain-security/testutil/simapp" + consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" + providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" + "github.com/cosmos/interchain-security/x/ccv/types" + + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/stretchr/testify/suite" +) + +type ProviderTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains + providerChain *ibctesting.TestChain + consumerChain *ibctesting.TestChain + + path *ibctesting.Path + transferPath *ibctesting.Path +} + +func TestProviderTestSuite(t *testing.T) { + suite.Run(t, new(ProviderTestSuite)) +} + +func (suite *ProviderTestSuite) SetupTest() { + suite.coordinator, suite.providerChain, suite.consumerChain = simapp.NewProviderConsumerCoordinator(suite.T()) + + // valsets must match + providerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) + consumerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.consumerChain.Vals) + suite.Require().True(len(providerValUpdates) == len(consumerValUpdates), "initial valset not matching") + for i := 0; i < len(providerValUpdates); i++ { + addr1 := utils.GetChangePubKeyAddress(providerValUpdates[i]) + addr2 := utils.GetChangePubKeyAddress(consumerValUpdates[i]) + suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") + } + + // move both chains to the next block + suite.providerChain.NextBlock() + suite.consumerChain.NextBlock() + + // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. + err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerClient( + suite.providerCtx(), + suite.consumerChain.ChainID, + suite.consumerChain.LastHeader.GetHeight().(clienttypes.Height), + false, + ) + suite.Require().NoError(err) + // move provider to next block to commit the state + suite.providerChain.NextBlock() + + // initialize the consumer chain with the genesis state stored on the provider + consumerGenesis, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis( + suite.providerCtx(), + suite.consumerChain.ChainID, + ) + suite.Require().True(found, "consumer genesis not found") + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), &consumerGenesis) + + // create path for the CCV channel + suite.path = ibctesting.NewPath(suite.consumerChain, suite.providerChain) + + // update CCV path with correct info + // - set provider endpoint's clientID + consumerClient, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId( + suite.providerCtx(), + suite.consumerChain.ChainID, + ) + suite.Require().True(found, "consumer client not found") + suite.path.EndpointB.ClientID = consumerClient + // - set consumer endpoint's clientID + providerClient, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.consumerChain.GetContext()) + suite.Require().True(found, "provider client not found") + suite.path.EndpointA.ClientID = providerClient + // - client config + providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerCtx()) + suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod + suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction + consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod + suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction + // - channel config + suite.path.EndpointA.ChannelConfig.PortID = consumertypes.PortID + suite.path.EndpointB.ChannelConfig.PortID = providertypes.PortID + suite.path.EndpointA.ChannelConfig.Version = types.Version + suite.path.EndpointB.ChannelConfig.Version = types.Version + suite.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + suite.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED + + // set chains sender account number + // TODO: to be fixed in #151 + err = suite.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) + suite.Require().NoError(err) + err = suite.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) + suite.Require().NoError(err) + + // create path for the transfer channel + suite.transferPath = ibctesting.NewPath(suite.consumerChain, suite.providerChain) + suite.transferPath.EndpointA.ChannelConfig.PortID = transfertypes.PortID + suite.transferPath.EndpointB.ChannelConfig.PortID = transfertypes.PortID + suite.transferPath.EndpointA.ChannelConfig.Version = transfertypes.Version + suite.transferPath.EndpointB.ChannelConfig.Version = transfertypes.Version +} + +func (suite *ProviderTestSuite) SetupCCVChannel() { + suite.StartSetupCCVChannel() + suite.CompleteSetupCCVChannel() + suite.SetupTransferChannel() +} + +func (suite *ProviderTestSuite) StartSetupCCVChannel() { + suite.coordinator.CreateConnections(suite.path) + + err := suite.path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = suite.path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) +} + +func (suite *ProviderTestSuite) CompleteSetupCCVChannel() { + err := suite.path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + err = suite.path.EndpointB.ChanOpenConfirm() + suite.Require().NoError(err) + + // ensure counterparty is up to date + err = suite.path.EndpointA.UpdateClient() + suite.Require().NoError(err) +} + +func (suite *ProviderTestSuite) SetupTransferChannel() { + // transfer path will use the same connection as ccv path + + suite.transferPath.EndpointA.ClientID = suite.path.EndpointA.ClientID + suite.transferPath.EndpointA.ConnectionID = suite.path.EndpointA.ConnectionID + suite.transferPath.EndpointB.ClientID = suite.path.EndpointB.ClientID + suite.transferPath.EndpointB.ConnectionID = suite.path.EndpointB.ConnectionID + + // CCV channel handshake will automatically initiate transfer channel handshake on ACK + // so transfer channel will be on stage INIT when CompleteSetupCCVChannel returns. + suite.transferPath.EndpointA.ChannelID = suite.consumerChain.App.(*appConsumer.App). + ConsumerKeeper.GetDistributionTransmissionChannel(suite.consumerChain.GetContext()) + + // Complete TRY, ACK, CONFIRM for transfer path + err := suite.transferPath.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = suite.transferPath.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + err = suite.transferPath.EndpointB.ChanOpenConfirm() + suite.Require().NoError(err) + + // ensure counterparty is up to date + err = suite.transferPath.EndpointA.UpdateClient() + suite.Require().NoError(err) +} + +// TODO: Can this be consolidated with ProviderTestSuite above? +type ProviderKeeperTestSuite struct { + suite.Suite + coordinator *ibctesting.Coordinator + + // testing chains + providerChain *ibctesting.TestChain + consumerChain *ibctesting.TestChain + path *ibctesting.Path + ctx sdk.Context +} + +func TestProviderKeeperTestSuite(t *testing.T) { + suite.Run(t, new(ProviderKeeperTestSuite)) +} + +func (suite *ProviderKeeperTestSuite) SetupTest() { + suite.coordinator, suite.providerChain, suite.consumerChain = simapp.NewProviderConsumerCoordinator(suite.T()) + + // valsets must match + providerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) + consumerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.consumerChain.Vals) + suite.Require().True(len(providerValUpdates) == len(consumerValUpdates), "initial valset not matching") + for i := 0; i < len(providerValUpdates); i++ { + addr1 := utils.GetChangePubKeyAddress(providerValUpdates[i]) + addr2 := utils.GetChangePubKeyAddress(consumerValUpdates[i]) + suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") + } + + // move both chains to the next block + suite.providerChain.NextBlock() + suite.consumerChain.NextBlock() + + // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. + err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerClient( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + suite.consumerChain.LastHeader.GetHeight().(clienttypes.Height), + false, + ) + suite.Require().NoError(err) + // move provider to next block to commit the state + suite.providerChain.NextBlock() + + // initialize the consumer chain with the genesis state stored on the provider + consumerGenesis, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + ) + suite.Require().True(found, "consumer genesis not found") + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), &consumerGenesis) + + // create path for the CCV channel + suite.path = ibctesting.NewPath(suite.consumerChain, suite.providerChain) + + // update CCV path with correct info + // - set provider endpoint's clientID + consumerClient, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + ) + suite.Require().True(found, "consumer client not found") + suite.path.EndpointB.ClientID = consumerClient + // - set consumer endpoint's clientID + providerClient, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.consumerChain.GetContext()) + suite.Require().True(found, "provider client not found") + suite.path.EndpointA.ClientID = providerClient + // - client config + providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) + suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod + suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction + consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod + suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction + // - channel config + suite.path.EndpointA.ChannelConfig.PortID = consumertypes.PortID + suite.path.EndpointB.ChannelConfig.PortID = providertypes.PortID + suite.path.EndpointA.ChannelConfig.Version = types.Version + suite.path.EndpointB.ChannelConfig.Version = types.Version + suite.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + suite.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED + + // set chains sender account number + // TODO: to be fixed in #151 + err = suite.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) + suite.Require().NoError(err) + err = suite.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) + suite.Require().NoError(err) + + suite.ctx = suite.providerChain.GetContext() +} + +type ConsumerTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains + providerChain *ibctesting.TestChain + consumerChain *ibctesting.TestChain + + path *ibctesting.Path + + ctx sdk.Context +} + +func TestConsumerTestSuite(t *testing.T) { + suite.Run(t, new(ConsumerTestSuite)) +} + +func (suite *ConsumerTestSuite) SetupTest() { + suite.coordinator, suite.providerChain, suite.consumerChain = simapp.NewProviderConsumerCoordinator(suite.T()) + + // valsets must match + providerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) + consumerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.consumerChain.Vals) + suite.Require().True(len(providerValUpdates) == len(consumerValUpdates), "initial valset not matching") + for i := 0; i < len(providerValUpdates); i++ { + addr1 := utils.GetChangePubKeyAddress(providerValUpdates[i]) + addr2 := utils.GetChangePubKeyAddress(consumerValUpdates[i]) + suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") + } + + // move both chains to the next block + suite.providerChain.NextBlock() + suite.consumerChain.NextBlock() + + // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. + err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerClient( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + suite.consumerChain.LastHeader.GetHeight().(clienttypes.Height), + false, + ) + suite.Require().NoError(err) + // move provider to next block to commit the state + suite.providerChain.NextBlock() + + // initialize the consumer chain with the genesis state stored on the provider + consumerGenesis, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + ) + suite.Require().True(found, "consumer genesis not found") + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), &consumerGenesis) + + // create path for the CCV channel + suite.path = ibctesting.NewPath(suite.consumerChain, suite.providerChain) + + // update CCV path with correct info + // - set provider endpoint's clientID + consumerClient, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + ) + suite.Require().True(found, "consumer client not found") + suite.path.EndpointB.ClientID = consumerClient + // - set consumer endpoint's clientID + providerClient, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.consumerChain.GetContext()) + suite.Require().True(found, "provider client not found") + suite.path.EndpointA.ClientID = providerClient + // - client config + providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) + suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod + suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction + consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod + suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction + // - channel config + suite.path.EndpointA.ChannelConfig.PortID = consumertypes.PortID + suite.path.EndpointB.ChannelConfig.PortID = providertypes.PortID + suite.path.EndpointA.ChannelConfig.Version = ccv.Version + suite.path.EndpointB.ChannelConfig.Version = ccv.Version + suite.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + suite.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED + + // set chains sender account number + // TODO: to be fixed in #151 + err = suite.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) + suite.Require().NoError(err) + err = suite.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) + suite.Require().NoError(err) + + suite.ctx = suite.consumerChain.GetContext() + + suite.coordinator.CreateConnections(suite.path) +} + +// TODO: Can this be consolidated with ConsumerTestSuite above? +type ConsumerKeeperTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains + providerChain *ibctesting.TestChain + consumerChain *ibctesting.TestChain + + providerClient *ibctmtypes.ClientState + providerConsState *ibctmtypes.ConsensusState + + path *ibctesting.Path + + ctx sdk.Context +} + +func TestConsumerKeeperTestSuite(t *testing.T) { + suite.Run(t, new(ConsumerKeeperTestSuite)) +} + +func (suite *ConsumerKeeperTestSuite) SetupTest() { + suite.coordinator, suite.providerChain, suite.consumerChain = simapp.NewProviderConsumerCoordinator(suite.T()) + + // valsets must match + providerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) + consumerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.consumerChain.Vals) + suite.Require().True(len(providerValUpdates) == len(consumerValUpdates), "initial valset not matching") + for i := 0; i < len(providerValUpdates); i++ { + addr1 := utils.GetChangePubKeyAddress(providerValUpdates[i]) + addr2 := utils.GetChangePubKeyAddress(consumerValUpdates[i]) + suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") + } + + // move both chains to the next block + suite.providerChain.NextBlock() + suite.consumerChain.NextBlock() + + // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. + err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerClient( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + suite.consumerChain.LastHeader.GetHeight().(clienttypes.Height), + false, + ) + suite.Require().NoError(err) + // move provider to next block to commit the state + suite.providerChain.NextBlock() + + // initialize the consumer chain with the genesis state stored on the provider + consumerGenesis, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + ) + suite.Require().True(found, "consumer genesis not found") + suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), &consumerGenesis) + suite.providerClient = consumerGenesis.ProviderClientState + suite.providerConsState = consumerGenesis.ProviderConsensusState + + // create path for the CCV channel + suite.path = ibctesting.NewPath(suite.consumerChain, suite.providerChain) + + // update CCV path with correct info + // - set provider endpoint's clientID + consumerClient, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId( + suite.providerChain.GetContext(), + suite.consumerChain.ChainID, + ) + suite.Require().True(found, "consumer client not found") + suite.path.EndpointB.ClientID = consumerClient + // - set consumer endpoint's clientID + providerClient, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.consumerChain.GetContext()) + suite.Require().True(found, "provider client not found") + suite.path.EndpointA.ClientID = providerClient + // - client config + providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) + suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod + suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction + consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) + suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod + suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction + // - channel config + suite.path.EndpointA.ChannelConfig.PortID = consumertypes.PortID + suite.path.EndpointB.ChannelConfig.PortID = providertypes.PortID + suite.path.EndpointA.ChannelConfig.Version = ccv.Version + suite.path.EndpointB.ChannelConfig.Version = ccv.Version + suite.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + suite.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED + + // set chains sender account number + // TODO: to be fixed in #151 + err = suite.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) + suite.Require().NoError(err) + err = suite.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) + suite.Require().NoError(err) + + suite.ctx = suite.consumerChain.GetContext() +} + +func (suite *ConsumerKeeperTestSuite) SetupCCVChannel() { + suite.coordinator.CreateConnections(suite.path) + suite.coordinator.CreateChannels(suite.path) +} diff --git a/e2e-tests/slashing_test.go b/e2e-tests/slashing_test.go new file mode 100644 index 0000000000..66b78a823b --- /dev/null +++ b/e2e-tests/slashing_test.go @@ -0,0 +1,720 @@ +package e2e_test + +import ( + "fmt" + "time" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ccv "github.com/cosmos/interchain-security/x/ccv/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + + appConsumer "github.com/cosmos/interchain-security/app/consumer" + appProvider "github.com/cosmos/interchain-security/app/provider" + consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" + providertypes "github.com/cosmos/interchain-security/x/ccv/provider/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 *ProviderTestSuite) TestSendSlashPacketDowntime() { + s.SetupCCVChannel() + validatorsPerChain := len(s.consumerChain.Vals.Validators) + + providerStakingKeeper := s.providerChain.App.(*appProvider.App).StakingKeeper + providerSlashingKeeper := s.providerChain.App.(*appProvider.App).SlashingKeeper + + consumerKeeper := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper + + // 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(types.GetTimeoutTimestamp(oldBlockTime).UnixNano()) + packet := channeltypes.NewPacket(packetData.GetBytes(), 1, consumertypes.PortID, s.path.EndpointA.ChannelID, + providertypes.PortID, 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 + s.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetOutstandingDowntime(s.consumerCtx(), consAddr) + + // save next VSC packet info + oldBlockTime = s.providerCtx().BlockTime() + timeout = uint64(types.GetTimeoutTimestamp(oldBlockTime).UnixNano()) + valsetUpdateID := s.providerChain.App.(*appProvider.App).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) + + // reconstruct VSC packet + valUpdates := []abci.ValidatorUpdate{ + { + PubKey: val.GetPubKey(), + Power: int64(0), + }, + } + packetData2 := ccv.NewValidatorSetChangePacketData(valUpdates, valsetUpdateID, []string{consAddr.String()}) + packet2 := channeltypes.NewPacket(packetData2.GetBytes(), 1, providertypes.PortID, s.path.EndpointB.ChannelID, + consumertypes.PortID, 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 := s.providerChain.App.(*appProvider.App).StakingKeeper.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 := sdk.NewDec(1).QuoInt64(slashFraction).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 the outstanding slashing flag is reset on the consumer + pFlag := s.consumerChain.App.(*appConsumer.App).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) +} + +func (s *ProviderTestSuite) TestSendSlashPacketDoubleSign() { + s.SetupCCVChannel() + validatorsPerChain := len(s.consumerChain.Vals.Validators) + + providerStakingKeeper := s.providerChain.App.(*appProvider.App).StakingKeeper + providerSlashingKeeper := s.providerChain.App.(*appProvider.App).SlashingKeeper + consumerKeeper := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper + + // 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() + packetData := types.NewSlashPacketData(validator, valsetUpdateId, stakingtypes.DoubleSign) + + timeout := uint64(types.GetTimeoutTimestamp(oldBlockTime).UnixNano()) + packet := channeltypes.NewPacket(packetData.GetBytes(), 1, consumertypes.PortID, s.path.EndpointA.ChannelID, + providertypes.PortID, s.path.EndpointB.ChannelID, clienttypes.Height{}, timeout) + + // Send the downtime packet through CCV + err = s.path.EndpointA.SendPacket(packet) + s.Require().NoError(err) + + // save next VSC packet info + oldBlockTime = s.providerCtx().BlockTime() + timeout = uint64(types.GetTimeoutTimestamp(oldBlockTime).UnixNano()) + valsetUpdateID := s.providerChain.App.(*appProvider.App).ProviderKeeper.GetValidatorSetUpdateId(s.providerCtx()) + + // 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 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) + + // reconstruct VSC packet + valUpdates := []abci.ValidatorUpdate{ + { + PubKey: val.GetPubKey(), + Power: int64(0), + }, + } + packetData2 := ccv.NewValidatorSetChangePacketData(valUpdates, valsetUpdateID, []string{}) + packet2 := channeltypes.NewPacket(packetData2.GetBytes(), 1, providertypes.PortID, s.path.EndpointB.ChannelID, + consumertypes.PortID, 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 := s.providerChain.App.(*appProvider.App).StakingKeeper.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 *ProviderTestSuite) TestSlashPacketAcknowldgement() { + providerKeeper := s.providerChain.App.(*appProvider.App).ProviderKeeper + consumerKeeper := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper + + packet := channeltypes.NewPacket([]byte{}, 1, consumertypes.PortID, s.path.EndpointA.ChannelID, + providertypes.PortID, "wrongchannel", clienttypes.Height{}, 0) + + ack := providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, ccv.SlashPacketData{}) + s.Require().NotNil(ack) + + err := consumerKeeper.OnAcknowledgementPacket(s.consumerCtx(), packet, channeltypes.NewResultAcknowledgement(ack.Acknowledgement())) + s.Require().NoError(err) + + err = consumerKeeper.OnAcknowledgementPacket(s.consumerCtx(), packet, channeltypes.NewErrorAcknowledgement("another error")) + s.Require().Error(err) +} + +// TestHandleSlashPacketDoubleSigning tests the handling of a double-signing related slash packet, with e2e tests +func (suite *ProviderKeeperTestSuite) TestHandleSlashPacketDoubleSigning() { + providerKeeper := suite.providerChain.App.(*appProvider.App).ProviderKeeper + providerSlashingKeeper := suite.providerChain.App.(*appProvider.App).SlashingKeeper + providerStakingKeeper := suite.providerChain.App.(*appProvider.App).StakingKeeper + + tmVal := suite.providerChain.Vals.Validators[0] + consAddr := sdk.ConsAddress(tmVal.Address) + + // check that validator bonded status + validator, found := providerStakingKeeper.GetValidatorByConsAddr(suite.ctx, consAddr) + suite.Require().True(found) + suite.Require().Equal(stakingtypes.Bonded, validator.GetStatus()) + + // set init VSC id for chain0 + providerKeeper.SetInitChainHeight(suite.ctx, suite.consumerChain.ChainID, uint64(suite.ctx.BlockHeight())) + + // set validator signing-info + providerSlashingKeeper.SetValidatorSigningInfo( + suite.ctx, + consAddr, + slashingtypes.ValidatorSigningInfo{Address: consAddr.String()}, + ) + + _, err := providerKeeper.HandleSlashPacket(suite.ctx, suite.consumerChain.ChainID, + ccv.NewSlashPacketData( + abci.Validator{Address: tmVal.Address, Power: 0}, + uint64(0), + stakingtypes.DoubleSign, + ), + ) + suite.NoError(err) + + // verify that validator is jailed in the staking and slashing mdodules' states + suite.Require().True(providerStakingKeeper.IsValidatorJailed(suite.ctx, consAddr)) + + signingInfo, _ := providerSlashingKeeper.GetValidatorSigningInfo(suite.ctx, consAddr) + suite.Require().True(signingInfo.JailedUntil.Equal(evidencetypes.DoubleSignJailEndTime)) + suite.Require().True(signingInfo.Tombstoned) +} + +// TestHandleSlashPacketErrors tests errors for the HandleSlashPacket method in an e2e testing setting +func (suite *ProviderKeeperTestSuite) TestHandleSlashPacketErrors() { + providerStakingKeeper := suite.providerChain.App.(*appProvider.App).StakingKeeper + ProviderKeeper := suite.providerChain.App.(*appProvider.App).ProviderKeeper + providerSlashingKeeper := suite.providerChain.App.(*appProvider.App).SlashingKeeper + consumerChainID := suite.consumerChain.ChainID + + // sync contexts block height + suite.ctx = suite.providerChain.GetContext() + + // expect an error if initial block height isn't set for consumer chain + _, err := ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, ccv.SlashPacketData{}) + suite.Require().Error(err, "slash validator with invalid infraction height") + + // save VSC ID + vID := ProviderKeeper.GetValidatorSetUpdateId(suite.ctx) + + // set faulty block height for current VSC ID + ProviderKeeper.SetValsetUpdateBlockHeight(suite.ctx, vID, 0) + + // expect an error if block height mapping VSC ID is zero + _, err = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, ccv.SlashPacketData{ValsetUpdateId: vID}) + suite.Require().Error(err, "slash with height mapping to zero") + + // construct slashing packet with non existing validator + slashingPkt := ccv.NewSlashPacketData( + abci.Validator{Address: ed25519.GenPrivKey().PubKey().Address(), + Power: int64(0)}, uint64(0), stakingtypes.Downtime, + ) + + // Set initial block height for consumer chain + ProviderKeeper.SetInitChainHeight(suite.ctx, consumerChainID, uint64(suite.ctx.BlockHeight())) + + // expect the slash to not succeed if validator doesn't exist + success, err := ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) + suite.Require().NoError(err, "slashing an unknown validator should not result in error") + suite.Require().False(success, "did slash unknown validator") + + // jail an existing validator + val := suite.providerChain.Vals.Validators[0] + consAddr := sdk.ConsAddress(val.Address) + providerStakingKeeper.Jail(suite.ctx, consAddr) + // commit block to set VSC ID + suite.coordinator.CommitBlock(suite.providerChain) + // Update suite.ctx bc CommitBlock updates only providerChain's current header block height + suite.ctx = suite.providerChain.GetContext() + suite.Require().NotZero(ProviderKeeper.GetValsetUpdateBlockHeight(suite.ctx, vID)) + + // create validator signing info + valInfo := slashingtypes.NewValidatorSigningInfo(sdk.ConsAddress(val.Address), suite.ctx.BlockHeight(), + suite.ctx.BlockHeight()-1, time.Time{}.UTC(), false, int64(0)) + providerSlashingKeeper.SetValidatorSigningInfo(suite.ctx, sdk.ConsAddress(val.Address), valInfo) + + // update validator address and VSC ID + slashingPkt.Validator.Address = val.Address + slashingPkt.ValsetUpdateId = vID + + // expect to slash and jail validator + _, err = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) + suite.Require().NoError(err, "did slash jail validator") + + // expect error when infraction type in unspecified + tmAddr := suite.providerChain.Vals.Validators[1].Address + slashingPkt.Validator.Address = tmAddr + slashingPkt.Infraction = stakingtypes.InfractionEmpty + + valInfo.Address = sdk.ConsAddress(tmAddr).String() + providerSlashingKeeper.SetValidatorSigningInfo(suite.ctx, sdk.ConsAddress(tmAddr), valInfo) + + _, err = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) + suite.Require().EqualError(err, fmt.Sprintf("invalid infraction type: %v", stakingtypes.InfractionEmpty)) + + // expect to slash jail validator + slashingPkt.Infraction = stakingtypes.DoubleSign + _, err = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) + suite.Require().NoError(err) + + // expect the slash to not succeed when validator is tombstoned + success, _ = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) + suite.Require().False(success) +} + +// TestHandleSlashPacketDistribution tests the slashing of an undelegation balance +// by varying the slash packet VSC ID mapping to infraction heights +// lesser, equal or greater than the undelegation entry creation height +func (suite *ProviderKeeperTestSuite) TestHandleSlashPacketDistribution() { + providerStakingKeeper := suite.providerChain.App.(*appProvider.App).StakingKeeper + providerKeeper := suite.providerChain.App.(*appProvider.App).ProviderKeeper + + // choose a validator + tmValidator := suite.providerChain.Vals.Validators[0] + valAddr, err := sdk.ValAddressFromHex(tmValidator.Address.String()) + suite.Require().NoError(err) + + validator, found := providerStakingKeeper.GetValidator(suite.providerChain.GetContext(), valAddr) + suite.Require().True(found) + + // unbonding operations parameters + delAddr := suite.providerChain.SenderAccount.GetAddress() + bondAmt := sdk.NewInt(1000000) + + // new delegator shares used + testShares := sdk.Dec{} + + // setup the test with a delegation, a no-op and an undelegation + setupOperations := []struct { + fn func(suite *ProviderKeeperTestSuite) error + }{ + { + func(suite *ProviderKeeperTestSuite) error { + testShares, err = providerStakingKeeper.Delegate(suite.providerChain.GetContext(), delAddr, bondAmt, stakingtypes.Unbonded, stakingtypes.Validator(validator), true) + return err + }, + }, { + func(suite *ProviderKeeperTestSuite) error { + return nil + }, + }, { + // undelegate a quarter of the new shares created + func(suite *ProviderKeeperTestSuite) error { + _, err = providerStakingKeeper.Undelegate(suite.providerChain.GetContext(), delAddr, valAddr, testShares.QuoInt64(4)) + return err + }, + }, + } + + // execute the setup operations, distributed uniformly in three blocks. + // For each of them, save their current VSC Id value which map correspond respectively + // to the block heights lesser, equal and greater than the undelegation creation height. + vscIDs := make([]uint64, 0, 3) + for _, so := range setupOperations { + err := so.fn(suite) + suite.Require().NoError(err) + + vscIDs = append(vscIDs, providerKeeper.GetValidatorSetUpdateId(suite.providerChain.GetContext())) + suite.providerChain.NextBlock() + } + + // create validator signing info to test slashing + suite.providerChain.App.(*appProvider.App).SlashingKeeper.SetValidatorSigningInfo( + suite.providerChain.GetContext(), + sdk.ConsAddress(tmValidator.Address), + slashingtypes.ValidatorSigningInfo{Address: tmValidator.Address.String()}, + ) + + // the test cases verify that only the unbonding tokens get slashed for the VSC ids + // mapping to the block heights before and during the undelegation otherwise not. + testCases := []struct { + expSlash bool + vscID uint64 + }{ + {expSlash: true, vscID: vscIDs[0]}, + {expSlash: true, vscID: vscIDs[1]}, + {expSlash: false, vscID: vscIDs[2]}, + } + + // save unbonding balance before slashing tests + ubd, found := providerStakingKeeper.GetUnbondingDelegation(suite.providerChain.GetContext(), delAddr, valAddr) + suite.Require().True(found) + ubdBalance := ubd.Entries[0].Balance + + for _, tc := range testCases { + slashPacket := ccv.NewSlashPacketData( + abci.Validator{Address: tmValidator.Address, Power: tmValidator.VotingPower}, + tc.vscID, + stakingtypes.Downtime, + ) + + // slash + _, err := providerKeeper.HandleSlashPacket(suite.providerChain.GetContext(), suite.consumerChain.ChainID, slashPacket) + suite.Require().NoError(err) + + ubd, found := providerStakingKeeper.GetUnbondingDelegation(suite.providerChain.GetContext(), delAddr, valAddr) + suite.Require().True(found) + + isUbdSlashed := ubdBalance.GT(ubd.Entries[0].Balance) + suite.Require().True(tc.expSlash == isUbdSlashed) + + // update balance + ubdBalance = ubd.Entries[0].Balance + } +} + +// TestValidatorDowntime tests if a slash packet is sent +// and if the outstanding slashing flag is switched +// when a validator has downtime on the slashing module +func (suite *ConsumerKeeperTestSuite) TestValidatorDowntime() { + // initial setup + suite.SetupCCVChannel() + suite.SendEmptyVSCPacket() + + // sync suite context after CCV channel is established + suite.ctx = suite.consumerChain.GetContext() + + app := suite.consumerChain.App.(*appConsumer.App) + channelID := suite.path.EndpointA.ChannelID + + // pick a cross-chain validator + vals := app.ConsumerKeeper.GetAllCCValidator(suite.ctx) + consAddr := sdk.ConsAddress(vals[0].Address) + + // save next sequence before sending a slash packet + seq, ok := app.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(suite.ctx, consumertypes.PortID, channelID) + suite.Require().True(ok) + + // Sign 100 blocks + valPower := int64(1) + height, signedBlocksWindow := int64(0), app.SlashingKeeper.SignedBlocksWindow(suite.ctx) + for ; height < signedBlocksWindow; height++ { + suite.ctx = suite.ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(suite.ctx, vals[0].Address, valPower, true) + } + + missedBlockThreshold := (2 * signedBlocksWindow) - app.SlashingKeeper.MinSignedPerWindow(suite.ctx) + + // construct slash packet to be sent and get its commit + packetData := ccv.NewSlashPacketData( + abci.Validator{Address: vals[0].Address, Power: valPower}, + // get the VSC ID mapping the infraction height + app.ConsumerKeeper.GetHeightValsetUpdateID(suite.ctx, uint64(missedBlockThreshold-sdk.ValidatorUpdateDelay-1)), + stakingtypes.Downtime, + ) + expCommit := suite.commitSlashPacket(suite.ctx, packetData) + + // Miss 50 blocks and expect a slash packet to be sent + for ; height <= missedBlockThreshold; height++ { + suite.ctx = suite.ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(suite.ctx, vals[0].Address, valPower, false) + } + + // check validator signing info + res, _ := app.SlashingKeeper.GetValidatorSigningInfo(suite.ctx, consAddr) + // expect increased jail time + suite.Require().True(res.JailedUntil.Equal(suite.ctx.BlockTime().Add(app.SlashingKeeper.DowntimeJailDuration(suite.ctx))), "did not update validator jailed until signing info") + // expect missed block counters reseted + suite.Require().Zero(res.MissedBlocksCounter, "did not reset validator missed block counter") + suite.Require().Zero(res.IndexOffset) + app.SlashingKeeper.IterateValidatorMissedBlockBitArray(suite.ctx, consAddr, func(_ int64, missed bool) bool { + suite.Require().True(missed) + return false + }) + + // verify that the slash packet was sent + gotCommit := app.IBCKeeper.ChannelKeeper.GetPacketCommitment(suite.ctx, consumertypes.PortID, channelID, seq) + suite.Require().NotNil(gotCommit, "did not found slash packet commitment") + suite.Require().EqualValues(expCommit, gotCommit, "invalid slash packet commitment") + + // verify that the slash packet was sent + suite.Require().True(app.ConsumerKeeper.OutstandingDowntime(suite.ctx, consAddr)) + + // check that the outstanding slashing flag prevents the jailed validator to keep missing block + for ; height < missedBlockThreshold+signedBlocksWindow; height++ { + suite.ctx = suite.ctx.WithBlockHeight(height) + app.SlashingKeeper.HandleValidatorSignature(suite.ctx, vals[0].Address, valPower, false) + } + + res, _ = app.SlashingKeeper.GetValidatorSigningInfo(suite.ctx, consAddr) + + suite.Require().Zero(res.MissedBlocksCounter, "did not reset validator missed block counter") + suite.Require().Zero(res.IndexOffset) + app.SlashingKeeper.IterateValidatorMissedBlockBitArray(suite.ctx, consAddr, func(_ int64, missed bool) bool { + suite.Require().True(missed, "did not reset validator missed block bit array") + return false + }) +} + +// TestValidatorDoubleSigning tests if a slash packet is sent +// when a double-signing evidence is handled by the evidence module +func (suite *ConsumerKeeperTestSuite) TestValidatorDoubleSigning() { + // initial setup + suite.SetupCCVChannel() + suite.SendEmptyVSCPacket() + + // sync suite context after CCV channel is established + suite.ctx = suite.consumerChain.GetContext() + + app := suite.consumerChain.App.(*appConsumer.App) + channelID := suite.path.EndpointA.ChannelID + + // create a validator pubkey and address + // note that the validator wont't necessarily be in valset to due the TM delay + pubkey := ed25519.GenPrivKey().PubKey() + consAddr := sdk.ConsAddress(pubkey.Address()) + + // set an arbitrary infraction height + infractionHeight := suite.ctx.BlockHeight() - 1 + power := int64(100) + + // create evidence + e := &evidencetypes.Equivocation{ + Height: infractionHeight, + Power: power, + Time: time.Now().UTC(), + ConsensusAddress: consAddr.String(), + } + + // add validator signing-info to the store + app.SlashingKeeper.SetValidatorSigningInfo(suite.ctx, consAddr, slashingtypes.ValidatorSigningInfo{ + Address: consAddr.String(), + Tombstoned: false, + }) + + // save next sequence before sending a slash packet + seq, ok := app.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(suite.ctx, consumertypes.PortID, channelID) + suite.Require().True(ok) + + // construct slash packet data and get the expcted commit hash + packetData := ccv.NewSlashPacketData( + abci.Validator{Address: consAddr.Bytes(), Power: power}, + // get VSC ID mapping to the infraction height with the TM delay substracted + app.ConsumerKeeper.GetHeightValsetUpdateID(suite.ctx, uint64(infractionHeight-sdk.ValidatorUpdateDelay)), + stakingtypes.DoubleSign, + ) + expCommit := suite.commitSlashPacket(suite.ctx, packetData) + + // expect to send slash packet when handling double-sign evidence + app.EvidenceKeeper.HandleEquivocationEvidence(suite.ctx, e) + + // check that slash packet is sent + gotCommit := app.IBCKeeper.ChannelKeeper.GetPacketCommitment(suite.ctx, consumertypes.PortID, channelID, seq) + suite.NotNil(gotCommit) + + suite.Require().EqualValues(expCommit, gotCommit) +} + +// TestSendSlashPacket tests the functionality of SendSlashPacket and asserts state changes related to that method +func (suite *ConsumerKeeperTestSuite) TestSendSlashPacket() { + suite.SetupCCVChannel() + + app := suite.consumerChain.App.(*appConsumer.App) + ctx := suite.consumerChain.GetContext() + channelID := suite.path.EndpointA.ChannelID + + // check that CCV channel isn't established + _, ok := app.ConsumerKeeper.GetProviderChannel(ctx) + suite.Require().False(ok) + + // expect to store 4 slash requests for downtime + // and 4 slash request for double-signing + type slashedVal struct { + validator abci.Validator + infraction stakingtypes.InfractionType + } + slashedVals := []slashedVal{} + + infraction := stakingtypes.Downtime + for j := 0; j < 2; j++ { + for i := 0; i < 4; i++ { + addr := ed25519.GenPrivKey().PubKey().Address() + val := abci.Validator{ + Address: addr} + app.ConsumerKeeper.SendSlashPacket(ctx, val, 0, infraction) + slashedVals = append(slashedVals, slashedVal{validator: val, infraction: infraction}) + } + infraction = stakingtypes.DoubleSign + } + + // expect to store a duplicate for each slash request + // in order to test the outstanding downtime logic + for _, sv := range slashedVals { + app.ConsumerKeeper.SendSlashPacket(ctx, sv.validator, 0, sv.infraction) + } + + // verify that all requests are stored + requests := app.ConsumerKeeper.GetPendingSlashRequests(ctx) + suite.Require().Len(requests, 16) + + // save consumer next sequence + seq, _ := app.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(ctx, consumertypes.PortID, channelID) + + // establish ccv channel by sending an empty VSC packet to consumer endpoint + suite.SendEmptyVSCPacket() + + // check that each pending slash requests is sent once + // and that the downtime slash request duplicates are skipped (due to the outstanding downtime flag) + for i := 0; i < 16; i++ { + commit := app.IBCKeeper.ChannelKeeper.GetPacketCommitment(ctx, consumertypes.PortID, channelID, seq+uint64(i)) + if i > 11 { + suite.Require().Nil(commit) + continue + } + suite.Require().NotNil(commit) + } + + // check that outstanding downtime flags + // are all set to true for validators slashed for downtime requests + for _, r := range requests { + downtime := r.Infraction == stakingtypes.Downtime + if downtime { + consAddr := sdk.ConsAddress(r.Packet.Validator.Address) + suite.Require().True(app.ConsumerKeeper.OutstandingDowntime(ctx, consAddr)) + } + } + + // check that pending slash requests get cleared after being sent + requests = app.ConsumerKeeper.GetPendingSlashRequests(ctx) + suite.Require().Len(requests, 0) + + // check that slash requests aren't stored when channel is established + app.ConsumerKeeper.SendSlashPacket(ctx, abci.Validator{}, 0, stakingtypes.Downtime) + app.ConsumerKeeper.SendSlashPacket(ctx, abci.Validator{}, 0, stakingtypes.DoubleSign) + + requests = app.ConsumerKeeper.GetPendingSlashRequests(ctx) + suite.Require().Len(requests, 0) +} diff --git a/x/ccv/provider/stop_consumer_test.go b/e2e-tests/stop_consumer_test.go similarity index 99% rename from x/ccv/provider/stop_consumer_test.go rename to e2e-tests/stop_consumer_test.go index 0fae709366..15d71cd333 100644 --- a/x/ccv/provider/stop_consumer_test.go +++ b/e2e-tests/stop_consumer_test.go @@ -1,4 +1,4 @@ -package provider_test +package e2e_test import ( "time" diff --git a/x/ccv/provider/unbonding_test.go b/e2e-tests/unbonding_test.go similarity index 62% rename from x/ccv/provider/unbonding_test.go rename to e2e-tests/unbonding_test.go index e402b03f24..0d6cf1e82e 100644 --- a/x/ccv/provider/unbonding_test.go +++ b/e2e-tests/unbonding_test.go @@ -1,19 +1,12 @@ -package provider_test +package e2e_test import ( - "strings" "time" sdk "github.com/cosmos/cosmos-sdk/types" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" - "github.com/cosmos/interchain-security/x/ccv/utils" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - - appConsumer "github.com/cosmos/interchain-security/app/consumer" appProvider "github.com/cosmos/interchain-security/app/provider" ) @@ -236,155 +229,3 @@ func (s *ProviderTestSuite) TestUnbondingNoConsumer() { // - check that half the coins have been returned s.Require().True(getBalance(s, s.providerCtx(), delAddr).Equal(initBalance.Sub(bondAmt.Quo(sdk.NewInt(2))))) } - -func getBalance(s *ProviderTestSuite, providerCtx sdk.Context, delAddr sdk.AccAddress) sdk.Int { - return s.providerChain.App.(*appProvider.App).BankKeeper.GetBalance(providerCtx, delAddr, s.providerBondDenom()).Amount -} - -// delegateAndUndelegate delegates bondAmt from delAddr to the first validator -// and then immediately undelegates 1/shareDiv of that delegation -func delegateAndUndelegate(s *ProviderTestSuite, delAddr sdk.AccAddress, bondAmt sdk.Int, shareDiv int64) (initBalance sdk.Int, valsetUpdateId uint64) { - // delegate - initBalance, shares, valAddr := delegate(s, delAddr, bondAmt) - - // check that the correct number of tokens were taken out of the delegator's account - s.Require().True(getBalance(s, s.providerCtx(), delAddr).Equal(initBalance.Sub(bondAmt))) - - // undelegate 1/shareDiv - valsetUpdateId = undelegate(s, delAddr, valAddr, shares.QuoInt64(shareDiv)) - - // check that the tokens have not been returned yet - s.Require().True(getBalance(s, s.providerCtx(), delAddr).Equal(initBalance.Sub(bondAmt))) - - return initBalance, valsetUpdateId -} - -// delegate delegates bondAmt to the first validator -func delegate(s *ProviderTestSuite, delAddr sdk.AccAddress, bondAmt sdk.Int) (initBalance sdk.Int, shares sdk.Dec, valAddr sdk.ValAddress) { - initBalance = getBalance(s, s.providerCtx(), delAddr) - // choose a validator - validator, valAddr := s.getVal(0) - // delegate bondAmt tokens on provider to change validator powers - shares, err := s.providerChain.App.(*appProvider.App).StakingKeeper.Delegate( - s.providerCtx(), - delAddr, - bondAmt, - stakingtypes.Unbonded, - stakingtypes.Validator(validator), - true, - ) - s.Require().NoError(err) - // check that the correct number of tokens were taken out of the delegator's account - s.Require().True(getBalance(s, s.providerCtx(), delAddr).Equal(initBalance.Sub(bondAmt))) - return initBalance, shares, valAddr -} - -// undelegate unbonds an amount of delegator shares from a given validator -func undelegate(s *ProviderTestSuite, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) (valsetUpdateId uint64) { - _, err := s.providerChain.App.(*appProvider.App).StakingKeeper.Undelegate(s.providerCtx(), delAddr, valAddr, sharesAmount) - s.Require().NoError(err) - - // save the current valset update ID - valsetUpdateID := s.providerChain.App.(*appProvider.App).ProviderKeeper.GetValidatorSetUpdateId(s.providerCtx()) - - return valsetUpdateID -} - -// ChainType defines the type of chain (either provider or consumer) -type ChainType int - -const ( - Provider ChainType = iota - Consumer -) - -// incrementTimeByUnbondingPeriod increments the overall time by -// - if provider == true, the unbonding period on the provider; -// - otherwise, the unbonding period on the consumer. -// -// Note that it is expected for the provider unbonding period -// to be one day larger than the consumer unbonding period. -func incrementTimeByUnbondingPeriod(s *ProviderTestSuite, chainType ChainType) { - // Get unboding period from staking keeper - providerUnbondingPeriod := s.providerChain.App.GetStakingKeeper().UnbondingTime(s.providerCtx()) - consumerUnbondingPeriod, found := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetUnbondingTime(s.consumerCtx()) - s.Require().True(found) - expectedUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - s.Require().Equal(expectedUnbondingPeriod+24*time.Hour, providerUnbondingPeriod, "unexpected provider unbonding period") - s.Require().Equal(expectedUnbondingPeriod, consumerUnbondingPeriod, "unexpected consumer unbonding period") - var jumpPeriod time.Duration - if chainType == Provider { - jumpPeriod = providerUnbondingPeriod - } else { - jumpPeriod = consumerUnbondingPeriod - } - // Make sure the clients do not expire - jumpPeriod = jumpPeriod/4 + time.Hour - for i := 0; i < 4; i++ { - s.coordinator.IncrementTimeBy(jumpPeriod) - // Update the provider client on the consumer - err := s.path.EndpointA.UpdateClient() - s.Require().NoError(err) - // Update the consumer client on the provider - err = s.path.EndpointB.UpdateClient() - s.Require().NoError(err) - } -} - -// relayAllCommittedPackets relays all committed packets from `srcChain` on `path` -func relayAllCommittedPackets( - s *ProviderTestSuite, - srcChain *ibctesting.TestChain, - path *ibctesting.Path, - portID string, - channelID string, - expectedPackets int, -) { - // check that the packets are committed in state - commitments := srcChain.App.GetIBCKeeper().ChannelKeeper.GetAllPacketCommitmentsAtChannel( - srcChain.GetContext(), - portID, - channelID, - ) - s.Require().Equal(expectedPackets, len(commitments), "did not find packet commitments") - - // relay all packets from srcChain to counterparty - for _, commitment := range commitments { - // - get packets - packet, found := srcChain.GetSentPacket(commitment.Sequence) - s.Require().True(found, "did not find sent packet") - // - relay the packet - err := path.RelayPacket(packet) - s.Require().NoError(err) - } -} - -func checkStakingUnbondingOps(s *ProviderTestSuite, id uint64, found bool, onHold bool) { - stakingUnbondingOp, wasFound := getStakingUnbondingDelegationEntry(s.providerCtx(), s.providerChain.App.(*appProvider.App).StakingKeeper, id) - s.Require().True(found == wasFound) - s.Require().True(onHold == (0 < stakingUnbondingOp.UnbondingOnHoldRefCount)) -} - -func checkCCVUnbondingOp(s *ProviderTestSuite, providerCtx sdk.Context, chainID string, valUpdateID uint64, found bool) { - entries, wasFound := s.providerChain.App.(*appProvider.App).ProviderKeeper.GetUnbondingOpsFromIndex(providerCtx, chainID, valUpdateID) - s.Require().True(found == wasFound) - if found { - s.Require().True(len(entries) > 0, "No unbonding ops found") - s.Require().True(len(entries[0].UnbondingConsumerChains) > 0, "Unbonding op with no consumer chains") - s.Require().True(strings.Compare(entries[0].UnbondingConsumerChains[0], "testchain2") == 0, "Unbonding op with unexpected consumer chain") - } -} - -func getStakingUnbondingDelegationEntry(ctx sdk.Context, k stakingkeeper.Keeper, id uint64) (stakingUnbondingOp stakingtypes.UnbondingDelegationEntry, found bool) { - stakingUbd, found := k.GetUnbondingDelegationByUnbondingId(ctx, id) - - for _, entry := range stakingUbd.Entries { - if entry.UnbondingId == id { - stakingUnbondingOp = entry - found = true - break - } - } - - return stakingUnbondingOp, found -} diff --git a/e2e-tests/valset_update_test.go b/e2e-tests/valset_update_test.go new file mode 100644 index 0000000000..33fc660c32 --- /dev/null +++ b/e2e-tests/valset_update_test.go @@ -0,0 +1,128 @@ +package e2e_test + +import ( + "time" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + appConsumer "github.com/cosmos/interchain-security/app/consumer" + consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" + providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" + "github.com/cosmos/interchain-security/x/ccv/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// TestPacketRoundtrip tests a CCV packet roundtrip when tokens are bonded on provider +func (s *ProviderTestSuite) TestPacketRoundtrip() { + s.SetupCCVChannel() + + // Bond some tokens on provider to change validator powers + bondAmt := sdk.NewInt(1000000) + delAddr := s.providerChain.SenderAccount.GetAddress() + delegate(s, delAddr, bondAmt) + + // Send CCV packet to consumer + s.providerChain.NextBlock() + + // Relay 1 VSC packet from provider to consumer + relayAllCommittedPackets(s, s.providerChain, s.path, providertypes.PortID, s.path.EndpointB.ChannelID, 1) + + // Increment time so that the unbonding period ends on the provider + incrementTimeByUnbondingPeriod(s, Provider) + + // Relay 1 VSCMatured packet from consumer to provider + relayAllCommittedPackets(s, s.consumerChain, s.path, consumertypes.PortID, s.path.EndpointA.ChannelID, 1) +} + +// TestUnbondMaturePackets tests the behavior of UnbondMaturePackets and related state checks +func (suite *ConsumerKeeperTestSuite) TestUnbondMaturePackets() { + // setup CCV channel + suite.SetupCCVChannel() + + // send 3 packets to consumer chain at different times + pk, err := cryptocodec.FromTmPubKeyInterface(suite.providerChain.Vals.Validators[0].PubKey) + suite.Require().NoError(err) + pk1, err := cryptocodec.ToTmProtoPublicKey(pk) + suite.Require().NoError(err) + pk, err = cryptocodec.FromTmPubKeyInterface(suite.providerChain.Vals.Validators[1].PubKey) + suite.Require().NoError(err) + pk2, err := cryptocodec.ToTmProtoPublicKey(pk) + suite.Require().NoError(err) + + pd := types.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{ + { + PubKey: pk1, + Power: 30, + }, + { + PubKey: pk2, + Power: 20, + }, + }, + 1, + nil, + ) + + // send first packet + packet := channeltypes.NewPacket(pd.GetBytes(), 1, providertypes.PortID, suite.path.EndpointB.ChannelID, consumertypes.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + ack := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) + suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") + suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") + + // increase time + incrementTimeBy(suite, time.Hour) + + // update time and send second packet + pd.ValidatorUpdates[0].Power = 15 + pd.ValsetUpdateId = 2 + packet.Data = pd.GetBytes() + packet.Sequence = 2 + ack = suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) + suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") + suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") + + // increase time + incrementTimeBy(suite, 24*time.Hour) + + // update time and send third packet + pd.ValidatorUpdates[1].Power = 40 + pd.ValsetUpdateId = 3 + packet.Data = pd.GetBytes() + packet.Sequence = 3 + ack = suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) + suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") + suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") + + // increase time such that first two packets are unbonded but third is not. + unbondingPeriod, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetUnbondingTime(suite.consumerChain.GetContext()) + suite.Require().True(found) + // increase time + incrementTimeBy(suite, unbondingPeriod-time.Hour) + + err = suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.UnbondMaturePackets(suite.consumerChain.GetContext()) + suite.Require().NoError(err) + + // ensure first two packets are unbonded and VSCMatured packets are sent + // unbonded time is deleted + time1 := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetPacketMaturityTime(suite.consumerChain.GetContext(), 1) + time2 := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetPacketMaturityTime(suite.consumerChain.GetContext(), 2) + suite.Require().Equal(uint64(0), time1, "maturity time not deleted for mature packet 1") + suite.Require().Equal(uint64(0), time2, "maturity time not deleted for mature packet 2") + // ensure that third packet did not get unbonded and is still in store + time3 := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetPacketMaturityTime(suite.consumerChain.GetContext(), 3) + suite.Require().True(time3 > uint64(suite.consumerChain.GetContext().BlockTime().UnixNano()), "maturity time for packet 3 is not after current time") + + // check that the packets are committed in state + commitments := suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.GetAllPacketCommitmentsAtChannel( + suite.consumerChain.GetContext(), + consumertypes.PortID, + suite.path.EndpointA.ChannelID, + ) + suite.Require().Equal(2, len(commitments), "did not find packet commitments") + suite.Require().Equal(uint64(1), commitments[0].Sequence, "did not send VSCMatured packet for VSC packet 1") + suite.Require().Equal(uint64(2), commitments[1].Sequence, "did not send VSCMatured packet for VSC packet 2") +} diff --git a/x/ccv/consumer/keeper/genesis_test.go b/x/ccv/consumer/keeper/genesis_test.go deleted file mode 100644 index 88268145d7..0000000000 --- a/x/ccv/consumer/keeper/genesis_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package keeper_test - -import ( - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - - app "github.com/cosmos/interchain-security/app/consumer" - appConsumer "github.com/cosmos/interchain-security/app/consumer" - consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" - providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" - "github.com/cosmos/interchain-security/x/ccv/types" - - abci "github.com/tendermint/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" -) - -func (suite *KeeperTestSuite) TestGenesis() { - genesis := suite.consumerChain.App.(*app.App).ConsumerKeeper.ExportGenesis(suite.consumerChain.GetContext()) - - suite.Require().Equal(suite.providerClient, genesis.ProviderClientState) - suite.Require().Equal(suite.providerConsState, genesis.ProviderConsensusState) - - suite.Require().NotPanics(func() { - suite.consumerChain.App.(*app.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), genesis) - // reset suite to reset provider client - suite.SetupTest() - }) - - ctx := suite.consumerChain.GetContext() - portId := suite.consumerChain.App.(*app.App).ConsumerKeeper.GetPort(ctx) - suite.Require().Equal(consumertypes.PortID, portId) - - clientId, ok := suite.consumerChain.App.(*app.App).ConsumerKeeper.GetProviderClientID(ctx) - suite.Require().True(ok) - clientState, ok := suite.consumerChain.App.GetIBCKeeper().ClientKeeper.GetClientState(ctx, clientId) - suite.Require().True(ok) - suite.Require().Equal(genesis.ProviderClientState, clientState, "client state not set correctly after InitGenesis") - - suite.SetupCCVChannel() - - origTime := suite.consumerChain.GetContext().BlockTime() - - pk1, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) - suite.Require().NoError(err) - pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) - suite.Require().NoError(err) - pd := types.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{ - { - PubKey: pk1, - Power: 30, - }, - { - PubKey: pk2, - Power: 20, - }, - }, - 1, - nil, - ) - packet := channeltypes.NewPacket(pd.GetBytes(), 1, providertypes.PortID, suite.path.EndpointB.ChannelID, consumertypes.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - suite.consumerChain.App.(*app.App).ConsumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) - - // mocking the fact that consumer chain validators should be provider chain validators - // TODO: Fix testing suite so we can initialize both chains with the same validator set - valUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) - - restartGenesis := suite.consumerChain.App.(*app.App).ConsumerKeeper.ExportGenesis(suite.consumerChain.GetContext()) - restartGenesis.InitialValSet = valUpdates - - // ensure reset genesis is set correctly - providerChannel := suite.path.EndpointA.ChannelID - suite.Require().Equal(providerChannel, restartGenesis.ProviderChannelId) - maturityTime := suite.consumerChain.App.(*app.App).ConsumerKeeper.GetPacketMaturityTime(suite.consumerChain.GetContext(), 1) - unbondingPeriod, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetUnbondingTime(suite.ctx) - suite.Require().True(found) - suite.Require().Equal(uint64(origTime.Add(unbondingPeriod).UnixNano()), maturityTime, "maturity time is not set correctly in genesis") - - suite.Require().NotPanics(func() { - suite.consumerChain.App.(*app.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), restartGenesis) - }) -} diff --git a/x/ccv/consumer/keeper/keeper_test.go b/x/ccv/consumer/keeper/keeper_test.go index 0757078588..6c40a6b0bd 100644 --- a/x/ccv/consumer/keeper/keeper_test.go +++ b/x/ccv/consumer/keeper/keeper_test.go @@ -1,141 +1,21 @@ package keeper_test import ( - "bytes" - "fmt" "testing" "time" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - sdk "github.com/cosmos/cosmos-sdk/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/ibc-go/modules/core/exported" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - appConsumer "github.com/cosmos/interchain-security/app/consumer" - appProvider "github.com/cosmos/interchain-security/app/provider" testkeeper "github.com/cosmos/interchain-security/testutil/keeper" - "github.com/cosmos/interchain-security/testutil/simapp" "github.com/cosmos/interchain-security/x/ccv/consumer/types" - consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" - providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/x/ccv/types" - utils "github.com/cosmos/interchain-security/x/ccv/utils" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" abci "github.com/tendermint/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" ) -type KeeperTestSuite struct { - suite.Suite - - coordinator *ibctesting.Coordinator - - // testing chains - providerChain *ibctesting.TestChain - consumerChain *ibctesting.TestChain - - providerClient *ibctmtypes.ClientState - providerConsState *ibctmtypes.ConsensusState - - path *ibctesting.Path - - ctx sdk.Context -} - -func (suite *KeeperTestSuite) SetupTest() { - suite.coordinator, suite.providerChain, suite.consumerChain = simapp.NewProviderConsumerCoordinator(suite.T()) - - // valsets must match - providerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) - consumerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.consumerChain.Vals) - suite.Require().True(len(providerValUpdates) == len(consumerValUpdates), "initial valset not matching") - for i := 0; i < len(providerValUpdates); i++ { - addr1 := utils.GetChangePubKeyAddress(providerValUpdates[i]) - addr2 := utils.GetChangePubKeyAddress(consumerValUpdates[i]) - suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") - } - - // move both chains to the next block - suite.providerChain.NextBlock() - suite.consumerChain.NextBlock() - - // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. - err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerClient( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - suite.consumerChain.LastHeader.GetHeight().(clienttypes.Height), - false, - ) - suite.Require().NoError(err) - // move provider to next block to commit the state - suite.providerChain.NextBlock() - - // initialize the consumer chain with the genesis state stored on the provider - consumerGenesis, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - ) - suite.Require().True(found, "consumer genesis not found") - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), &consumerGenesis) - suite.providerClient = consumerGenesis.ProviderClientState - suite.providerConsState = consumerGenesis.ProviderConsensusState - - // create path for the CCV channel - suite.path = ibctesting.NewPath(suite.consumerChain, suite.providerChain) - - // update CCV path with correct info - // - set provider endpoint's clientID - consumerClient, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - ) - suite.Require().True(found, "consumer client not found") - suite.path.EndpointB.ClientID = consumerClient - // - set consumer endpoint's clientID - providerClient, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.consumerChain.GetContext()) - suite.Require().True(found, "provider client not found") - suite.path.EndpointA.ClientID = providerClient - // - client config - providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) - suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod - suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction - consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod - suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction - // - channel config - suite.path.EndpointA.ChannelConfig.PortID = consumertypes.PortID - suite.path.EndpointB.ChannelConfig.PortID = providertypes.PortID - suite.path.EndpointA.ChannelConfig.Version = ccv.Version - suite.path.EndpointB.ChannelConfig.Version = ccv.Version - suite.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED - suite.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED - - // set chains sender account number - // TODO: to be fixed in #151 - err = suite.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) - suite.Require().NoError(err) - err = suite.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) - suite.Require().NoError(err) - - suite.ctx = suite.consumerChain.GetContext() -} - -func (suite *KeeperTestSuite) SetupCCVChannel() { - suite.coordinator.CreateConnections(suite.path) - suite.coordinator.CreateChannels(suite.path) -} - // TestUnbondingTime tests getter and setter functionality for the unbonding period of a consumer chain func TestUnbondingTime(t *testing.T) { consumerKeeper, ctx := testkeeper.GetConsumerKeeperAndCtx(t) @@ -148,15 +28,6 @@ func TestUnbondingTime(t *testing.T) { require.Equal(t, storedUnbondingPeriod, unbondingPeriod) } -// TestProviderClientMatches tests that the provider client managed by the consumer keeper matches the client keeper's client state -func (suite *KeeperTestSuite) TestProviderClientMatches() { - providerClientID, ok := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.ctx) - suite.Require().True(ok) - - clientState, _ := suite.consumerChain.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.ctx, providerClientID) - suite.Require().Equal(suite.providerClient, clientState, "stored client state does not match genesis provider client") -} - // TestProviderClientID tests getter and setter functionality for the client ID stored on consumer keeper func TestProviderClientID(t *testing.T) { consumerKeeper, ctx := testkeeper.GetConsumerKeeperAndCtx(t) @@ -241,354 +112,6 @@ func TestPacketMaturityTime(t *testing.T) { }) } -// TestVerifyProviderChain tests the VerifyProviderChain method for the consumer keeper -func (suite *KeeperTestSuite) TestVerifyProviderChain() { - var connectionHops []string - channelID := "channel-0" - testCases := []struct { - name string - setup func(suite *KeeperTestSuite) - connectionHops []string - expError bool - }{ - { - name: "success", - setup: func(suite *KeeperTestSuite) { - // create consumer client on provider chain - providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) - consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - suite.CreateCustomClient(suite.path.EndpointB, consumerUnbondingPeriod) - err := suite.path.EndpointB.CreateClient() - suite.Require().NoError(err) - - suite.coordinator.CreateConnections(suite.path) - - // set connection hops to be connection hop from path endpoint - connectionHops = []string{suite.path.EndpointA.ConnectionID} - }, - connectionHops: []string{suite.path.EndpointA.ConnectionID}, - expError: false, - }, - { - name: "connection hops is not length 1", - setup: func(suite *KeeperTestSuite) { - // create consumer client on provider chain - providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) - consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - suite.CreateCustomClient(suite.path.EndpointB, consumerUnbondingPeriod) - - suite.coordinator.CreateConnections(suite.path) - - // set connection hops to be connection hop from path endpoint - connectionHops = []string{suite.path.EndpointA.ConnectionID, "connection-2"} - }, - expError: true, - }, - { - name: "connection does not exist", - setup: func(suite *KeeperTestSuite) { - // set connection hops to be connection hop from path endpoint - connectionHops = []string{"connection-dne"} - }, - expError: true, - }, - { - name: "clientID does not match", - setup: func(suite *KeeperTestSuite) { - // create consumer client on provider chain - providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) - consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - suite.CreateCustomClient(suite.path.EndpointB, consumerUnbondingPeriod) - - // create a new provider client on consumer chain that is different from the one in genesis - suite.CreateCustomClient(suite.path.EndpointA, providerUnbondingPeriod) - - suite.coordinator.CreateConnections(suite.path) - - // set connection hops to be connection hop from path endpoint - connectionHops = []string{suite.path.EndpointA.ConnectionID} - }, - expError: true, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { - suite.SetupTest() // reset suite - - tc.setup(suite) - - // Verify ProviderChain on consumer chain using path returned by setup - err := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.VerifyProviderChain(suite.ctx, channelID, connectionHops) - - if tc.expError { - suite.Require().Error(err, "invalid case did not return error") - } else { - suite.Require().NoError(err, "valid case returned error") - } - }) - } -} - -// CreateCustomClient creates an IBC client on the endpoint -// using the given unbonding period. -// It will update the clientID for the endpoint if the message -// is successfully executed. -func (suite *KeeperTestSuite) CreateCustomClient(endpoint *ibctesting.Endpoint, unbondingPeriod time.Duration) { - // ensure counterparty has committed state - endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - - suite.Require().Equal(exported.Tendermint, endpoint.ClientConfig.GetClientType(), "only Tendermint client supported") - - tmConfig, ok := endpoint.ClientConfig.(*ibctesting.TendermintConfig) - require.True(endpoint.Chain.T, ok) - tmConfig.UnbondingPeriod = unbondingPeriod - tmConfig.TrustingPeriod = unbondingPeriod / utils.TrustingPeriodFraction - - height := endpoint.Counterparty.Chain.LastHeader.GetHeight().(clienttypes.Height) - UpgradePath := []string{"upgrade", "upgradedIBCState"} - clientState := ibctmtypes.NewClientState( - endpoint.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, - height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, - ) - consensusState := endpoint.Counterparty.Chain.LastHeader.ConsensusState() - - msg, err := clienttypes.NewMsgCreateClient( - clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), - ) - require.NoError(endpoint.Chain.T, err) - - res, err := endpoint.Chain.SendMsgs(msg) - require.NoError(endpoint.Chain.T, err) - - endpoint.ClientID, err = ibctesting.ParseClientIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.T, err) -} - -// TestValidatorDowntime tests if a slash packet is sent -// and if the outstanding slashing flag is switched -// when a validator has downtime on the slashing module -func (suite *KeeperTestSuite) TestValidatorDowntime() { - // initial setup - suite.SetupCCVChannel() - suite.SendEmptyVSCPacket() - - // sync suite context after CCV channel is established - suite.ctx = suite.consumerChain.GetContext() - - app := suite.consumerChain.App.(*appConsumer.App) - channelID := suite.path.EndpointA.ChannelID - - // pick a cross-chain validator - vals := app.ConsumerKeeper.GetAllCCValidator(suite.ctx) - consAddr := sdk.ConsAddress(vals[0].Address) - - // save next sequence before sending a slash packet - seq, ok := app.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(suite.ctx, types.PortID, channelID) - suite.Require().True(ok) - - // Sign 100 blocks - valPower := int64(1) - height, signedBlocksWindow := int64(0), app.SlashingKeeper.SignedBlocksWindow(suite.ctx) - for ; height < signedBlocksWindow; height++ { - suite.ctx = suite.ctx.WithBlockHeight(height) - app.SlashingKeeper.HandleValidatorSignature(suite.ctx, vals[0].Address, valPower, true) - } - - missedBlockThreshold := (2 * signedBlocksWindow) - app.SlashingKeeper.MinSignedPerWindow(suite.ctx) - - // construct slash packet to be sent and get its commit - packetData := ccv.NewSlashPacketData( - abci.Validator{Address: vals[0].Address, Power: valPower}, - // get the VSC ID mapping the infraction height - app.ConsumerKeeper.GetHeightValsetUpdateID(suite.ctx, uint64(missedBlockThreshold-sdk.ValidatorUpdateDelay-1)), - stakingtypes.Downtime, - ) - expCommit := suite.commitSlashPacket(suite.ctx, packetData) - - // Miss 50 blocks and expect a slash packet to be sent - for ; height <= missedBlockThreshold; height++ { - suite.ctx = suite.ctx.WithBlockHeight(height) - app.SlashingKeeper.HandleValidatorSignature(suite.ctx, vals[0].Address, valPower, false) - } - - // check validator signing info - res, _ := app.SlashingKeeper.GetValidatorSigningInfo(suite.ctx, consAddr) - // expect increased jail time - suite.Require().True(res.JailedUntil.Equal(suite.ctx.BlockTime().Add(app.SlashingKeeper.DowntimeJailDuration(suite.ctx))), "did not update validator jailed until signing info") - // expect missed block counters reseted - suite.Require().Zero(res.MissedBlocksCounter, "did not reset validator missed block counter") - suite.Require().Zero(res.IndexOffset) - app.SlashingKeeper.IterateValidatorMissedBlockBitArray(suite.ctx, consAddr, func(_ int64, missed bool) bool { - suite.Require().True(missed) - return false - }) - - // verify that the slash packet was sent - gotCommit := app.IBCKeeper.ChannelKeeper.GetPacketCommitment(suite.ctx, types.PortID, channelID, seq) - suite.Require().NotNil(gotCommit, "did not found slash packet commitment") - suite.Require().EqualValues(expCommit, gotCommit, "invalid slash packet commitment") - - // verify that the slash packet was sent - suite.Require().True(app.ConsumerKeeper.OutstandingDowntime(suite.ctx, consAddr)) - - // check that the outstanding slashing flag prevents the jailed validator to keep missing block - for ; height < missedBlockThreshold+signedBlocksWindow; height++ { - suite.ctx = suite.ctx.WithBlockHeight(height) - app.SlashingKeeper.HandleValidatorSignature(suite.ctx, vals[0].Address, valPower, false) - } - - res, _ = app.SlashingKeeper.GetValidatorSigningInfo(suite.ctx, consAddr) - - suite.Require().Zero(res.MissedBlocksCounter, "did not reset validator missed block counter") - suite.Require().Zero(res.IndexOffset) - app.SlashingKeeper.IterateValidatorMissedBlockBitArray(suite.ctx, consAddr, func(_ int64, missed bool) bool { - suite.Require().True(missed, "did not reset validator missed block bit array") - return false - }) -} - -// TestValidatorDoubleSigning tests if a slash packet is sent -// when a double-signing evidence is handled by the evidence module -func (suite *KeeperTestSuite) TestValidatorDoubleSigning() { - // initial setup - suite.SetupCCVChannel() - suite.SendEmptyVSCPacket() - - // sync suite context after CCV channel is established - suite.ctx = suite.consumerChain.GetContext() - - app := suite.consumerChain.App.(*appConsumer.App) - channelID := suite.path.EndpointA.ChannelID - - // create a validator pubkey and address - // note that the validator wont't necessarily be in valset to due the TM delay - pubkey := ed25519.GenPrivKey().PubKey() - consAddr := sdk.ConsAddress(pubkey.Address()) - - // set an arbitrary infraction height - infractionHeight := suite.ctx.BlockHeight() - 1 - power := int64(100) - - // create evidence - e := &evidencetypes.Equivocation{ - Height: infractionHeight, - Power: power, - Time: time.Now().UTC(), - ConsensusAddress: consAddr.String(), - } - - // add validator signing-info to the store - app.SlashingKeeper.SetValidatorSigningInfo(suite.ctx, consAddr, slashingtypes.ValidatorSigningInfo{ - Address: consAddr.String(), - Tombstoned: false, - }) - - // save next sequence before sending a slash packet - seq, ok := app.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(suite.ctx, types.PortID, channelID) - suite.Require().True(ok) - - // construct slash packet data and get the expcted commit hash - packetData := ccv.NewSlashPacketData( - abci.Validator{Address: consAddr.Bytes(), Power: power}, - // get VSC ID mapping to the infraction height with the TM delay substracted - app.ConsumerKeeper.GetHeightValsetUpdateID(suite.ctx, uint64(infractionHeight-sdk.ValidatorUpdateDelay)), - stakingtypes.DoubleSign, - ) - expCommit := suite.commitSlashPacket(suite.ctx, packetData) - - // expect to send slash packet when handling double-sign evidence - app.EvidenceKeeper.HandleEquivocationEvidence(suite.ctx, e) - - // check that slash packet is sent - gotCommit := app.IBCKeeper.ChannelKeeper.GetPacketCommitment(suite.ctx, types.PortID, channelID, seq) - suite.NotNil(gotCommit) - - suite.Require().EqualValues(expCommit, gotCommit) -} - -// TestSendSlashPacket tests the functionality of SendSlashPacket and asserts state changes related to that method -func (suite *KeeperTestSuite) TestSendSlashPacket() { - suite.SetupCCVChannel() - - app := suite.consumerChain.App.(*appConsumer.App) - ctx := suite.consumerChain.GetContext() - channelID := suite.path.EndpointA.ChannelID - - // check that CCV channel isn't established - _, ok := app.ConsumerKeeper.GetProviderChannel(ctx) - suite.Require().False(ok) - - // expect to store 4 slash requests for downtime - // and 4 slash request for double-signing - type slashedVal struct { - validator abci.Validator - infraction stakingtypes.InfractionType - } - slashedVals := []slashedVal{} - - infraction := stakingtypes.Downtime - for j := 0; j < 2; j++ { - for i := 0; i < 4; i++ { - addr := ed25519.GenPrivKey().PubKey().Address() - val := abci.Validator{ - Address: addr} - app.ConsumerKeeper.SendSlashPacket(ctx, val, 0, infraction) - slashedVals = append(slashedVals, slashedVal{validator: val, infraction: infraction}) - } - infraction = stakingtypes.DoubleSign - } - - // expect to store a duplicate for each slash request - // in order to test the outstanding downtime logic - for _, sv := range slashedVals { - app.ConsumerKeeper.SendSlashPacket(ctx, sv.validator, 0, sv.infraction) - } - - // verify that all requests are stored - requests := app.ConsumerKeeper.GetPendingSlashRequests(ctx) - suite.Require().Len(requests, 16) - - // save consumer next sequence - seq, _ := app.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(ctx, types.PortID, channelID) - - // establish ccv channel by sending an empty VSC packet to consumer endpoint - suite.SendEmptyVSCPacket() - - // check that each pending slash requests is sent once - // and that the downtime slash request duplicates are skipped (due to the outstanding downtime flag) - for i := 0; i < 16; i++ { - commit := app.IBCKeeper.ChannelKeeper.GetPacketCommitment(ctx, types.PortID, channelID, seq+uint64(i)) - if i > 11 { - suite.Require().Nil(commit) - continue - } - suite.Require().NotNil(commit) - } - - // check that outstanding downtime flags - // are all set to true for validators slashed for downtime requests - for _, r := range requests { - downtime := r.Infraction == stakingtypes.Downtime - if downtime { - consAddr := sdk.ConsAddress(r.Packet.Validator.Address) - suite.Require().True(app.ConsumerKeeper.OutstandingDowntime(ctx, consAddr)) - } - } - - // check that pending slash requests get cleared after being sent - requests = app.ConsumerKeeper.GetPendingSlashRequests(ctx) - suite.Require().Len(requests, 0) - - // check that slash requests aren't stored when channel is established - app.ConsumerKeeper.SendSlashPacket(ctx, abci.Validator{}, 0, stakingtypes.Downtime) - app.ConsumerKeeper.SendSlashPacket(ctx, abci.Validator{}, 0, stakingtypes.DoubleSign) - - requests = app.ConsumerKeeper.GetPendingSlashRequests(ctx) - suite.Require().Len(requests, 0) -} - // TestCrossChainValidator tests the getter, setter, and deletion method for cross chain validator records func TestCrossChainValidator(t *testing.T) { @@ -673,48 +196,3 @@ func TestPendingSlashRequests(t *testing.T) { require.Len(t, requests, tc.expLen) } } - -// SendEmptyVSCPacket sends a VSC packet without any changes -// to ensure that the channel gets established -func (suite *KeeperTestSuite) SendEmptyVSCPacket() { - providerKeeper := suite.providerChain.App.(*appProvider.App).ProviderKeeper - - oldBlockTime := suite.providerChain.GetContext().BlockTime() - timeout := uint64(ccv.GetTimeoutTimestamp(oldBlockTime).UnixNano()) - - valUpdateID := providerKeeper.GetValidatorSetUpdateId(suite.providerChain.GetContext()) - - pd := ccv.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{}, - valUpdateID, - nil, - ) - - seq, ok := suite.providerChain.App.(*appProvider.App).GetIBCKeeper().ChannelKeeper.GetNextSequenceSend( - suite.providerChain.GetContext(), providertypes.PortID, suite.path.EndpointB.ChannelID) - suite.Require().True(ok) - - packet := channeltypes.NewPacket(pd.GetBytes(), seq, providertypes.PortID, suite.path.EndpointB.ChannelID, - consumertypes.PortID, suite.path.EndpointA.ChannelID, clienttypes.Height{}, timeout) - - err := suite.path.EndpointB.SendPacket(packet) - suite.Require().NoError(err) - err = suite.path.EndpointA.RecvPacket(packet) - suite.Require().NoError(err) -} - -func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(KeeperTestSuite)) -} - -// commitSlashPacket returns a commit hash for the given slash packet data -// Note that it must be called before sending the embedding IBC packet. -func (suite *KeeperTestSuite) commitSlashPacket(ctx sdk.Context, packetData ccv.SlashPacketData) []byte { - oldBlockTime := ctx.BlockTime() - timeout := uint64(ccv.GetTimeoutTimestamp(oldBlockTime).UnixNano()) - - packet := channeltypes.NewPacket(packetData.GetBytes(), 1, consumertypes.PortID, suite.path.EndpointA.ChannelID, - providertypes.PortID, suite.path.EndpointB.ChannelID, clienttypes.Height{}, timeout) - - return channeltypes.CommitPacket(suite.consumerChain.App.AppCodec(), packet) -} diff --git a/x/ccv/consumer/keeper/relay_test.go b/x/ccv/consumer/keeper/relay_test.go index 4c3654b59e..bee68ffab6 100644 --- a/x/ccv/consumer/keeper/relay_test.go +++ b/x/ccv/consumer/keeper/relay_test.go @@ -12,12 +12,10 @@ 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" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" - appConsumer "github.com/cosmos/interchain-security/app/consumer" testkeeper "github.com/cosmos/interchain-security/testutil/keeper" consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" "github.com/cosmos/interchain-security/x/ccv/types" - "github.com/cosmos/interchain-security/x/ccv/utils" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -201,119 +199,6 @@ func TestOnRecvVSCPacket(t *testing.T) { } } -// TestUnbondMaturePackets tests the behavior of UnbondMaturePackets and related state checks -func (suite *KeeperTestSuite) TestUnbondMaturePackets() { - // setup CCV channel - suite.SetupCCVChannel() - - // send 3 packets to consumer chain at different times - pk, err := cryptocodec.FromTmPubKeyInterface(suite.providerChain.Vals.Validators[0].PubKey) - suite.Require().NoError(err) - pk1, err := cryptocodec.ToTmProtoPublicKey(pk) - suite.Require().NoError(err) - pk, err = cryptocodec.FromTmPubKeyInterface(suite.providerChain.Vals.Validators[1].PubKey) - suite.Require().NoError(err) - pk2, err := cryptocodec.ToTmProtoPublicKey(pk) - suite.Require().NoError(err) - - pd := types.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{ - { - PubKey: pk1, - Power: 30, - }, - { - PubKey: pk2, - Power: 20, - }, - }, - 1, - nil, - ) - - // send first packet - packet := channeltypes.NewPacket(pd.GetBytes(), 1, providertypes.PortID, suite.path.EndpointB.ChannelID, consumertypes.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - ack := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) - suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") - suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") - - // increase time - incrementTimeBy(suite, time.Hour) - - // update time and send second packet - pd.ValidatorUpdates[0].Power = 15 - pd.ValsetUpdateId = 2 - packet.Data = pd.GetBytes() - packet.Sequence = 2 - ack = suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) - suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") - suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") - - // increase time - incrementTimeBy(suite, 24*time.Hour) - - // update time and send third packet - pd.ValidatorUpdates[1].Power = 40 - pd.ValsetUpdateId = 3 - packet.Data = pd.GetBytes() - packet.Sequence = 3 - ack = suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) - suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") - suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") - - // increase time such that first two packets are unbonded but third is not. - unbondingPeriod, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetUnbondingTime(suite.consumerChain.GetContext()) - suite.Require().True(found) - // increase time - incrementTimeBy(suite, unbondingPeriod-time.Hour) - - err = suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.UnbondMaturePackets(suite.consumerChain.GetContext()) - suite.Require().NoError(err) - - // ensure first two packets are unbonded and VSCMatured packets are sent - // unbonded time is deleted - time1 := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetPacketMaturityTime(suite.consumerChain.GetContext(), 1) - time2 := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetPacketMaturityTime(suite.consumerChain.GetContext(), 2) - suite.Require().Equal(uint64(0), time1, "maturity time not deleted for mature packet 1") - suite.Require().Equal(uint64(0), time2, "maturity time not deleted for mature packet 2") - // ensure that third packet did not get unbonded and is still in store - time3 := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetPacketMaturityTime(suite.consumerChain.GetContext(), 3) - suite.Require().True(time3 > uint64(suite.consumerChain.GetContext().BlockTime().UnixNano()), "maturity time for packet 3 is not after current time") - - // check that the packets are committed in state - commitments := suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.GetAllPacketCommitmentsAtChannel( - suite.consumerChain.GetContext(), - consumertypes.PortID, - suite.path.EndpointA.ChannelID, - ) - suite.Require().Equal(2, len(commitments), "did not find packet commitments") - suite.Require().Equal(uint64(1), commitments[0].Sequence, "did not send VSCMatured packet for VSC packet 1") - suite.Require().Equal(uint64(2), commitments[1].Sequence, "did not send VSCMatured packet for VSC packet 2") -} - -// incrementTimeBy increments the overall time by jumpPeriod -func incrementTimeBy(s *KeeperTestSuite, jumpPeriod time.Duration) { - // Get unboding period from staking keeper - consumerUnbondingPeriod, found := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetUnbondingTime(s.consumerChain.GetContext()) - s.Require().True(found) - split := 1 - if jumpPeriod > consumerUnbondingPeriod/utils.TrustingPeriodFraction { - // Make sure the clients do not expire - split = 4 - jumpPeriod = jumpPeriod / 4 - } - for i := 0; i < split; i++ { - s.coordinator.IncrementTimeBy(jumpPeriod) - // Update the provider client on the consumer - err := s.path.EndpointA.UpdateClient() - s.Require().NoError(err) - // Update the consumer client on the provider - err = s.path.EndpointB.UpdateClient() - s.Require().NoError(err) - } -} - // TestOnAcknowledgementPacket tests application logic for acknowledgments of sent VSCMatured and Slash packets // in conjunction with the ibc module's execution of "acknowledgePacket", // according to https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics#processing-acknowledgements diff --git a/x/ccv/consumer/keeper/validators_test.go b/x/ccv/consumer/keeper/validators_test.go index 4b034c20e7..15f14ed1b5 100644 --- a/x/ccv/consumer/keeper/validators_test.go +++ b/x/ccv/consumer/keeper/validators_test.go @@ -6,17 +6,14 @@ import ( "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - appConsumer "github.com/cosmos/interchain-security/app/consumer" testkeeper "github.com/cosmos/interchain-security/testutil/keeper" "github.com/cosmos/interchain-security/x/ccv/consumer/keeper" "github.com/cosmos/interchain-security/x/ccv/consumer/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" tmrand "github.com/tendermint/tendermint/libs/rand" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtypes "github.com/tendermint/tendermint/types" ) @@ -170,84 +167,6 @@ func TestHistoricalInfo(t *testing.T) { require.True(t, IsValSetSorted(recv.Valset, sdk.DefaultPowerReduction), "HistoricalInfo validators is not sorted") } -// Tests the tracking of historical info in the context of new blocks being committed -func (k KeeperTestSuite) TestTrackHistoricalInfo() { - consumerKeeper := k.consumerChain.App.(*appConsumer.App).ConsumerKeeper - cCtx := k.consumerChain.GetContext - - // save init consumer valset length - initValsetLen := len(consumerKeeper.GetAllCCValidator(cCtx())) - // save current block height - initHeight := cCtx().BlockHeight() - - // define an utility function that creates a new cross-chain validator - // and then call track historical info in the next block - createVal := func(k KeeperTestSuite) { - // add new validator to consumer states - pk := ed25519.GenPrivKey().PubKey() - cVal, err := types.NewCCValidator(pk.Address(), int64(1), pk) - k.Require().NoError(err) - - consumerKeeper.SetCCValidator(k.consumerChain.GetContext(), cVal) - - // commit block in order to call TrackHistoricalInfo - k.consumerChain.NextBlock() - } - - // testsetup create 2 validators and then call track historical info with header block height - // increased by HistoricalEntries in order to prune the historical info less or equal to the current block height - // Note that historical info containing the created validators are stored during the next block BeginBlocker - // and thus are indexed with the respective block heights InitHeight+1 and InitHeight+2 - testSetup := []func(KeeperTestSuite){ - createVal, - createVal, - func(k KeeperTestSuite) { - newHeight := k.consumerChain.GetContext().BlockHeight() + int64(types.HistoricalEntries) - header := tmproto.Header{ - ChainID: "HelloChain", - Height: newHeight, - } - ctx := k.consumerChain.GetContext().WithBlockHeader(header) - k.consumerChain.App.(*appConsumer.App).ConsumerKeeper.TrackHistoricalInfo(ctx) - }, - } - - for _, ts := range testSetup { - ts(k) - } - - // test cases verify that historical info entries are pruned when their height - // is below CurrentHeight - HistoricalEntries, and check that their valset gets updated - testCases := []struct { - height int64 - found bool - expLen int - }{ - { - height: initHeight + 1, - found: false, - expLen: 0, - }, - { - height: initHeight + 2, - found: false, - expLen: 0, - }, - { - height: initHeight + int64(types.HistoricalEntries) + 2, - found: true, - expLen: initValsetLen + 2, - }, - } - - for _, tc := range testCases { - cCtx().WithBlockHeight(tc.height) - hi, found := consumerKeeper.GetHistoricalInfo(cCtx().WithBlockHeight(tc.height), tc.height) - k.Require().Equal(tc.found, found) - k.Require().Len(hi.Valset, tc.expLen) - } -} - // IsValSetSorted reports whether valset is sorted. func IsValSetSorted(data []stakingtypes.Validator, powerReduction sdk.Int) bool { n := len(data) diff --git a/x/ccv/consumer/module_test.go b/x/ccv/consumer/module_test.go deleted file mode 100644 index 40fd42615c..0000000000 --- a/x/ccv/consumer/module_test.go +++ /dev/null @@ -1,482 +0,0 @@ -package consumer_test - -import ( - "bytes" - "fmt" - "testing" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/cosmos/ibc-go/modules/core/exported" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - host "github.com/cosmos/ibc-go/v3/modules/core/24-host" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - - appConsumer "github.com/cosmos/interchain-security/app/consumer" - appProvider "github.com/cosmos/interchain-security/app/provider" - "github.com/cosmos/interchain-security/testutil/simapp" - "github.com/cosmos/interchain-security/x/ccv/consumer" - consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" - providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" - ccv "github.com/cosmos/interchain-security/x/ccv/types" - "github.com/cosmos/interchain-security/x/ccv/utils" - - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -type ConsumerTestSuite struct { - suite.Suite - - coordinator *ibctesting.Coordinator - - // testing chains - providerChain *ibctesting.TestChain - consumerChain *ibctesting.TestChain - - path *ibctesting.Path - - ctx sdk.Context -} - -func (suite *ConsumerTestSuite) SetupTest() { - suite.coordinator, suite.providerChain, suite.consumerChain = simapp.NewProviderConsumerCoordinator(suite.T()) - - // valsets must match - providerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) - consumerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.consumerChain.Vals) - suite.Require().True(len(providerValUpdates) == len(consumerValUpdates), "initial valset not matching") - for i := 0; i < len(providerValUpdates); i++ { - addr1 := utils.GetChangePubKeyAddress(providerValUpdates[i]) - addr2 := utils.GetChangePubKeyAddress(consumerValUpdates[i]) - suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") - } - - // move both chains to the next block - suite.providerChain.NextBlock() - suite.consumerChain.NextBlock() - - // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. - err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerClient( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - suite.consumerChain.LastHeader.GetHeight().(clienttypes.Height), - false, - ) - suite.Require().NoError(err) - // move provider to next block to commit the state - suite.providerChain.NextBlock() - - // initialize the consumer chain with the genesis state stored on the provider - consumerGenesis, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - ) - suite.Require().True(found, "consumer genesis not found") - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), &consumerGenesis) - - // create path for the CCV channel - suite.path = ibctesting.NewPath(suite.consumerChain, suite.providerChain) - - // update CCV path with correct info - // - set provider endpoint's clientID - consumerClient, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - ) - suite.Require().True(found, "consumer client not found") - suite.path.EndpointB.ClientID = consumerClient - // - set consumer endpoint's clientID - providerClient, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.consumerChain.GetContext()) - suite.Require().True(found, "provider client not found") - suite.path.EndpointA.ClientID = providerClient - // - client config - providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) - suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod - suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction - consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod - suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction - // - channel config - suite.path.EndpointA.ChannelConfig.PortID = consumertypes.PortID - suite.path.EndpointB.ChannelConfig.PortID = providertypes.PortID - suite.path.EndpointA.ChannelConfig.Version = ccv.Version - suite.path.EndpointB.ChannelConfig.Version = ccv.Version - suite.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED - suite.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED - - // set chains sender account number - // TODO: to be fixed in #151 - err = suite.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) - suite.Require().NoError(err) - err = suite.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) - suite.Require().NoError(err) - - suite.ctx = suite.consumerChain.GetContext() - - suite.coordinator.CreateConnections(suite.path) -} - -func (suite *ConsumerTestSuite) TestOnChanOpenInit() { - channelID := "channel-1" - testCases := []struct { - name string - setup func(suite *ConsumerTestSuite) - expError bool - }{ - { - name: "success", - setup: func(suite *ConsumerTestSuite) { - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: false, - }, - { - name: "invalid: provider channel already established", - setup: func(suite *ConsumerTestSuite) { - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, "channel-2") - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: true, - }, - { - name: "invalid: UNORDERED channel", - setup: func(suite *ConsumerTestSuite) { - // set path ORDER to UNORDERED - suite.path.EndpointA.ChannelConfig.Order = channeltypes.UNORDERED - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.UNORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: true, - }, - { - name: "invalid: incorrect port", - setup: func(suite *ConsumerTestSuite) { - // set path port to invalid portID - suite.path.EndpointA.ChannelConfig.PortID = "invalidPort" - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.UNORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: true, - }, - { - name: "invalid: incorrect version", - setup: func(suite *ConsumerTestSuite) { - // set path port to invalid version - suite.path.EndpointA.ChannelConfig.Version = "invalidVersion" - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.UNORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: true, - }, - { - name: "invalid: verify provider chain failed", - setup: func(suite *ConsumerTestSuite) { - // setup a new path with provider client on consumer chain being different from genesis client - path := ibctesting.NewPath(suite.consumerChain, suite.providerChain) - // - client config - providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) - path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod - path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction - consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod - path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction - // - channel config - path.EndpointA.ChannelConfig.PortID = consumertypes.PortID - path.EndpointB.ChannelConfig.PortID = providertypes.PortID - path.EndpointA.ChannelConfig.Version = ccv.Version - path.EndpointB.ChannelConfig.Version = ccv.Version - path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED - path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED - - // create consumer client on provider chain, and provider client on consumer chain - err := suite.CreateCustomClient(path.EndpointB, consumerUnbondingPeriod) - suite.Require().NoError(err) - err = suite.CreateCustomClient(path.EndpointA, providerUnbondingPeriod) - suite.Require().NoError(err) - - suite.coordinator.CreateConnections(path) - suite.path = path - - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: true, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { - suite.SetupTest() // reset suite - tc.setup(suite) - - consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) - chanCap, err := suite.consumerChain.App.GetScopedIBCKeeper().NewCapability(suite.ctx, host.ChannelCapabilityPath(consumertypes.PortID, suite.path.EndpointA.ChannelID)) - suite.Require().NoError(err) - - err = consumerModule.OnChanOpenInit(suite.ctx, suite.path.EndpointA.ChannelConfig.Order, []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.PortID, - suite.path.EndpointA.ChannelID, chanCap, channeltypes.NewCounterparty(providertypes.PortID, ""), suite.path.EndpointA.ChannelConfig.Version) - - if tc.expError { - suite.Require().Error(err) - } else { - suite.Require().NoError(err) - } - }) - } -} - -// CreateCustomClient creates an IBC client on the endpoint -// using the given unbonding period. -// It will update the clientID for the endpoint if the message -// is successfully executed. -func (suite *ConsumerTestSuite) CreateCustomClient(endpoint *ibctesting.Endpoint, unbondingPeriod time.Duration) (err error) { - // ensure counterparty has committed state - endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - - suite.Require().Equal(exported.Tendermint, endpoint.ClientConfig.GetClientType(), "only Tendermint client supported") - - tmConfig, ok := endpoint.ClientConfig.(*ibctesting.TendermintConfig) - require.True(endpoint.Chain.T, ok) - tmConfig.UnbondingPeriod = unbondingPeriod - tmConfig.TrustingPeriod = unbondingPeriod / utils.TrustingPeriodFraction - - height := endpoint.Counterparty.Chain.LastHeader.GetHeight().(clienttypes.Height) - UpgradePath := []string{"upgrade", "upgradedIBCState"} - clientState := ibctmtypes.NewClientState( - endpoint.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, - height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, - ) - consensusState := endpoint.Counterparty.Chain.LastHeader.ConsensusState() - - msg, err := clienttypes.NewMsgCreateClient( - clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), - ) - require.NoError(endpoint.Chain.T, err) - - res, err := endpoint.Chain.SendMsgs(msg) - if err != nil { - return err - } - - endpoint.ClientID, err = ibctesting.ParseClientIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.T, err) - - return nil -} - -func (suite *ConsumerTestSuite) TestOnChanOpenTry() { - // OnOpenTry must error even with correct arguments - consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) - chanCap, err := suite.consumerChain.App.GetScopedIBCKeeper().NewCapability(suite.ctx, host.ChannelCapabilityPath(consumertypes.PortID, suite.path.EndpointA.ChannelID)) - suite.Require().NoError(err) - - _, err = consumerModule.OnChanOpenTry(suite.ctx, channeltypes.ORDERED, []string{"connection-1"}, consumertypes.PortID, "channel-1", chanCap, channeltypes.NewCounterparty(providertypes.PortID, "channel-1"), ccv.Version) - suite.Require().Error(err, "OnChanOpenTry callback must error on consumer chain") -} - -func (suite *ConsumerTestSuite) TestOnChanOpenAck() { - channelID := "channel-1" - counterChannelID := "channel-2" - testCases := []struct { - name string - setup func(suite *ConsumerTestSuite) - expError bool - }{ - { - name: "success", - setup: func(suite *ConsumerTestSuite) { - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: false, - }, - { - name: "invalid: provider channel already established", - setup: func(suite *ConsumerTestSuite) { - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, "channel-2") - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: true, - }, - { - name: "invalid: mismatched versions", - setup: func(suite *ConsumerTestSuite) { - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - // set provider version to invalid version - suite.path.EndpointB.ChannelConfig.Version = "invalidVersion" - }, - expError: true, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { - suite.SetupTest() // reset suite - tc.setup(suite) - - consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) - - md := providertypes.HandshakeMetadata{ - ProviderFeePoolAddr: "", // dummy address used - Version: suite.path.EndpointB.ChannelConfig.Version, - } - mdBz, err := (&md).Marshal() - suite.Require().NoError(err) - - err = consumerModule.OnChanOpenAck(suite.ctx, consumertypes.PortID, channelID, counterChannelID, string(mdBz)) - if tc.expError { - suite.Require().Error(err) - } else { - suite.Require().NoError(err) - } - }) - } -} - -func (suite *ConsumerTestSuite) TestOnChanOpenConfirm() { - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, "channel-1", - channeltypes.NewChannel( - channeltypes.TRYOPEN, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, "channel-1"), - []string{"connection-1"}, ccv.Version, - )) - - consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) - - err := consumerModule.OnChanOpenConfirm(suite.ctx, consumertypes.PortID, "channel-1") - suite.Require().Error(err, "OnChanOpenConfirm must always fail") -} - -func (suite *ConsumerTestSuite) TestOnChanCloseInit() { - channelID := "channel-1" - testCases := []struct { - name string - setup func(suite *ConsumerTestSuite) - expError bool - }{ - { - name: "can close duplicate in-progress channel once provider channel is established", - setup: func(suite *ConsumerTestSuite) { - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, "different-channel") - }, - expError: false, - }, - { - name: "can close duplicate open channel once provider channel is established", - setup: func(suite *ConsumerTestSuite) { - // create open channel - suite.coordinator.CreateChannels(suite.path) - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, "different-channel") - }, - expError: false, - }, - { - name: "cannot close in-progress channel, no established channel yet", - setup: func(suite *ConsumerTestSuite) { - // Set INIT channel on consumer chain - suite.consumerChain.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.ctx, consumertypes.PortID, channelID, - channeltypes.NewChannel( - channeltypes.INIT, channeltypes.ORDERED, channeltypes.NewCounterparty(providertypes.PortID, ""), - []string{suite.path.EndpointA.ConnectionID}, suite.path.EndpointA.ChannelConfig.Version), - ) - suite.path.EndpointA.ChannelID = channelID - }, - expError: true, - }, - { - name: "cannot close provider channel", - setup: func(suite *ConsumerTestSuite) { - // create open channel - suite.coordinator.CreateChannels(suite.path) - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetProviderChannel(suite.ctx, suite.path.EndpointA.ChannelID) - }, - expError: true, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { - suite.SetupTest() // reset suite - tc.setup(suite) - - consumerModule := consumer.NewAppModule(suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper) - - err := consumerModule.OnChanCloseInit(suite.ctx, consumertypes.PortID, suite.path.EndpointA.ChannelID) - - if tc.expError { - suite.Require().Error(err) - } else { - suite.Require().NoError(err) - } - }) - } -} - -func TestConsumerTestSuite(t *testing.T) { - suite.Run(t, new(ConsumerTestSuite)) -} diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index a564534e5f..3d81746b4d 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -1,10 +1,7 @@ package keeper_test import ( - "bytes" - "fmt" "testing" - "time" capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" @@ -13,122 +10,18 @@ import ( cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" - slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" ibcsimapp "github.com/cosmos/ibc-go/v3/testing/simapp" - appConsumer "github.com/cosmos/interchain-security/app/consumer" - appProvider "github.com/cosmos/interchain-security/app/provider" testkeeper "github.com/cosmos/interchain-security/testutil/keeper" - "github.com/cosmos/interchain-security/testutil/simapp" - consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" - providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" - "github.com/cosmos/interchain-security/x/ccv/types" ccv "github.com/cosmos/interchain-security/x/ccv/types" - utils "github.com/cosmos/interchain-security/x/ccv/utils" abci "github.com/tendermint/tendermint/abci/types" tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" - tmtypes "github.com/tendermint/tendermint/types" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" ) -type KeeperTestSuite struct { - suite.Suite - coordinator *ibctesting.Coordinator - - // testing chains - providerChain *ibctesting.TestChain - consumerChain *ibctesting.TestChain - path *ibctesting.Path - ctx sdk.Context -} - -func (suite *KeeperTestSuite) SetupTest() { - suite.coordinator, suite.providerChain, suite.consumerChain = simapp.NewProviderConsumerCoordinator(suite.T()) - - // valsets must match - providerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) - consumerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.consumerChain.Vals) - suite.Require().True(len(providerValUpdates) == len(consumerValUpdates), "initial valset not matching") - for i := 0; i < len(providerValUpdates); i++ { - addr1 := utils.GetChangePubKeyAddress(providerValUpdates[i]) - addr2 := utils.GetChangePubKeyAddress(consumerValUpdates[i]) - suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") - } - - // move both chains to the next block - suite.providerChain.NextBlock() - suite.consumerChain.NextBlock() - - // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. - err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerClient( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - suite.consumerChain.LastHeader.GetHeight().(clienttypes.Height), - false, - ) - suite.Require().NoError(err) - // move provider to next block to commit the state - suite.providerChain.NextBlock() - - // initialize the consumer chain with the genesis state stored on the provider - consumerGenesis, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - ) - suite.Require().True(found, "consumer genesis not found") - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), &consumerGenesis) - - // create path for the CCV channel - suite.path = ibctesting.NewPath(suite.consumerChain, suite.providerChain) - - // update CCV path with correct info - // - set provider endpoint's clientID - consumerClient, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId( - suite.providerChain.GetContext(), - suite.consumerChain.ChainID, - ) - suite.Require().True(found, "consumer client not found") - suite.path.EndpointB.ClientID = consumerClient - // - set consumer endpoint's clientID - providerClient, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.consumerChain.GetContext()) - suite.Require().True(found, "provider client not found") - suite.path.EndpointA.ClientID = providerClient - // - client config - providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerChain.GetContext()) - suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod - suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction - consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod - suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction - // - channel config - suite.path.EndpointA.ChannelConfig.PortID = consumertypes.PortID - suite.path.EndpointB.ChannelConfig.PortID = providertypes.PortID - suite.path.EndpointA.ChannelConfig.Version = types.Version - suite.path.EndpointB.ChannelConfig.Version = types.Version - suite.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED - suite.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED - - // set chains sender account number - // TODO: to be fixed in #151 - err = suite.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) - suite.Require().NoError(err) - err = suite.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) - suite.Require().NoError(err) - - suite.ctx = suite.providerChain.GetContext() -} - -func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(KeeperTestSuite)) -} - // TestValsetUpdateBlockHeight tests the getter, setter, and deletion methods for valset updates mapped to block height func TestValsetUpdateBlockHeight(t *testing.T) { providerKeeper, ctx := testkeeper.GetProviderKeeperAndCtx(t) @@ -287,47 +180,6 @@ func TestInitHeight(t *testing.T) { } } -// TestHandleSlashPacketDoubleSigning tests the handling of a double-signing related slash packet, with e2e tests -func (suite *KeeperTestSuite) TestHandleSlashPacketDoubleSigning() { - providerKeeper := suite.providerChain.App.(*appProvider.App).ProviderKeeper - providerSlashingKeeper := suite.providerChain.App.(*appProvider.App).SlashingKeeper - providerStakingKeeper := suite.providerChain.App.(*appProvider.App).StakingKeeper - - tmVal := suite.providerChain.Vals.Validators[0] - consAddr := sdk.ConsAddress(tmVal.Address) - - // check that validator bonded status - validator, found := providerStakingKeeper.GetValidatorByConsAddr(suite.ctx, consAddr) - suite.Require().True(found) - suite.Require().Equal(stakingtypes.Bonded, validator.GetStatus()) - - // set init VSC id for chain0 - providerKeeper.SetInitChainHeight(suite.ctx, suite.consumerChain.ChainID, uint64(suite.ctx.BlockHeight())) - - // set validator signing-info - providerSlashingKeeper.SetValidatorSigningInfo( - suite.ctx, - consAddr, - slashingtypes.ValidatorSigningInfo{Address: consAddr.String()}, - ) - - _, err := providerKeeper.HandleSlashPacket(suite.ctx, suite.consumerChain.ChainID, - ccv.NewSlashPacketData( - abci.Validator{Address: tmVal.Address, Power: 0}, - uint64(0), - stakingtypes.DoubleSign, - ), - ) - suite.NoError(err) - - // verify that validator is jailed in the staking and slashing mdodules' states - suite.Require().True(providerStakingKeeper.IsValidatorJailed(suite.ctx, consAddr)) - - signingInfo, _ := providerSlashingKeeper.GetValidatorSigningInfo(suite.ctx, consAddr) - suite.Require().True(signingInfo.JailedUntil.Equal(evidencetypes.DoubleSignJailEndTime)) - suite.Require().True(signingInfo.Tombstoned) -} - // TestHandleSlashPacketDoubleSigning tests the handling of a double-signing related slash packet, with mocks and unit tests func TestHandleSlashPacketDoubleSigning(t *testing.T) { ctrl := gomock.NewController(t) @@ -400,189 +252,6 @@ func TestHandleSlashPacketDoubleSigning(t *testing.T) { require.True(t, success) } -// TestHandleSlashPacketErrors tests errors for the HandleSlashPacket method in an e2e testing setting -func (suite *KeeperTestSuite) TestHandleSlashPacketErrors() { - providerStakingKeeper := suite.providerChain.App.(*appProvider.App).StakingKeeper - ProviderKeeper := suite.providerChain.App.(*appProvider.App).ProviderKeeper - providerSlashingKeeper := suite.providerChain.App.(*appProvider.App).SlashingKeeper - consumerChainID := suite.consumerChain.ChainID - - // sync contexts block height - suite.ctx = suite.providerChain.GetContext() - - // expect an error if initial block height isn't set for consumer chain - _, err := ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, ccv.SlashPacketData{}) - suite.Require().Error(err, "slash validator with invalid infraction height") - - // save VSC ID - vID := ProviderKeeper.GetValidatorSetUpdateId(suite.ctx) - - // set faulty block height for current VSC ID - ProviderKeeper.SetValsetUpdateBlockHeight(suite.ctx, vID, 0) - - // expect an error if block height mapping VSC ID is zero - _, err = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, ccv.SlashPacketData{ValsetUpdateId: vID}) - suite.Require().Error(err, "slash with height mapping to zero") - - // construct slashing packet with non existing validator - slashingPkt := ccv.NewSlashPacketData( - abci.Validator{Address: ed25519.GenPrivKey().PubKey().Address(), - Power: int64(0)}, uint64(0), stakingtypes.Downtime, - ) - - // Set initial block height for consumer chain - ProviderKeeper.SetInitChainHeight(suite.ctx, consumerChainID, uint64(suite.ctx.BlockHeight())) - - // expect the slash to not succeed if validator doesn't exist - success, err := ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) - suite.Require().NoError(err, "slashing an unknown validator should not result in error") - suite.Require().False(success, "did slash unknown validator") - - // jail an existing validator - val := suite.providerChain.Vals.Validators[0] - consAddr := sdk.ConsAddress(val.Address) - providerStakingKeeper.Jail(suite.ctx, consAddr) - // commit block to set VSC ID - suite.coordinator.CommitBlock(suite.providerChain) - // Update suite.ctx bc CommitBlock updates only providerChain's current header block height - suite.ctx = suite.providerChain.GetContext() - suite.Require().NotZero(ProviderKeeper.GetValsetUpdateBlockHeight(suite.ctx, vID)) - - // create validator signing info - valInfo := slashingtypes.NewValidatorSigningInfo(sdk.ConsAddress(val.Address), suite.ctx.BlockHeight(), - suite.ctx.BlockHeight()-1, time.Time{}.UTC(), false, int64(0)) - providerSlashingKeeper.SetValidatorSigningInfo(suite.ctx, sdk.ConsAddress(val.Address), valInfo) - - // update validator address and VSC ID - slashingPkt.Validator.Address = val.Address - slashingPkt.ValsetUpdateId = vID - - // expect to slash and jail validator - _, err = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) - suite.Require().NoError(err, "did slash jail validator") - - // expect error when infraction type in unspecified - tmAddr := suite.providerChain.Vals.Validators[1].Address - slashingPkt.Validator.Address = tmAddr - slashingPkt.Infraction = stakingtypes.InfractionEmpty - - valInfo.Address = sdk.ConsAddress(tmAddr).String() - providerSlashingKeeper.SetValidatorSigningInfo(suite.ctx, sdk.ConsAddress(tmAddr), valInfo) - - _, err = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) - suite.Require().EqualError(err, fmt.Sprintf("invalid infraction type: %v", stakingtypes.InfractionEmpty)) - - // expect to slash jail validator - slashingPkt.Infraction = stakingtypes.DoubleSign - _, err = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) - suite.Require().NoError(err) - - // expect the slash to not succeed when validator is tombstoned - success, _ = ProviderKeeper.HandleSlashPacket(suite.ctx, consumerChainID, slashingPkt) - suite.Require().False(success) -} - -// TestHandleSlashPacketDistribution tests the slashing of an undelegation balance -// by varying the slash packet VSC ID mapping to infraction heights -// lesser, equal or greater than the undelegation entry creation height -func (suite *KeeperTestSuite) TestHandleSlashPacketDistribution() { - providerStakingKeeper := suite.providerChain.App.(*appProvider.App).StakingKeeper - providerKeeper := suite.providerChain.App.(*appProvider.App).ProviderKeeper - - // choose a validator - tmValidator := suite.providerChain.Vals.Validators[0] - valAddr, err := sdk.ValAddressFromHex(tmValidator.Address.String()) - suite.Require().NoError(err) - - validator, found := providerStakingKeeper.GetValidator(suite.providerChain.GetContext(), valAddr) - suite.Require().True(found) - - // unbonding operations parameters - delAddr := suite.providerChain.SenderAccount.GetAddress() - bondAmt := sdk.NewInt(1000000) - - // new delegator shares used - testShares := sdk.Dec{} - - // setup the test with a delegation, a no-op and an undelegation - setupOperations := []struct { - fn func(suite *KeeperTestSuite) error - }{ - { - func(suite *KeeperTestSuite) error { - testShares, err = providerStakingKeeper.Delegate(suite.providerChain.GetContext(), delAddr, bondAmt, stakingtypes.Unbonded, stakingtypes.Validator(validator), true) - return err - }, - }, { - func(suite *KeeperTestSuite) error { - return nil - }, - }, { - // undelegate a quarter of the new shares created - func(suite *KeeperTestSuite) error { - _, err = providerStakingKeeper.Undelegate(suite.providerChain.GetContext(), delAddr, valAddr, testShares.QuoInt64(4)) - return err - }, - }, - } - - // execute the setup operations, distributed uniformly in three blocks. - // For each of them, save their current VSC Id value which map correspond respectively - // to the block heights lesser, equal and greater than the undelegation creation height. - vscIDs := make([]uint64, 0, 3) - for _, so := range setupOperations { - err := so.fn(suite) - suite.Require().NoError(err) - - vscIDs = append(vscIDs, providerKeeper.GetValidatorSetUpdateId(suite.providerChain.GetContext())) - suite.providerChain.NextBlock() - } - - // create validator signing info to test slashing - suite.providerChain.App.(*appProvider.App).SlashingKeeper.SetValidatorSigningInfo( - suite.providerChain.GetContext(), - sdk.ConsAddress(tmValidator.Address), - slashingtypes.ValidatorSigningInfo{Address: tmValidator.Address.String()}, - ) - - // the test cases verify that only the unbonding tokens get slashed for the VSC ids - // mapping to the block heights before and during the undelegation otherwise not. - testCases := []struct { - expSlash bool - vscID uint64 - }{ - {expSlash: true, vscID: vscIDs[0]}, - {expSlash: true, vscID: vscIDs[1]}, - {expSlash: false, vscID: vscIDs[2]}, - } - - // save unbonding balance before slashing tests - ubd, found := providerStakingKeeper.GetUnbondingDelegation(suite.providerChain.GetContext(), delAddr, valAddr) - suite.Require().True(found) - ubdBalance := ubd.Entries[0].Balance - - for _, tc := range testCases { - slashPacket := ccv.NewSlashPacketData( - abci.Validator{Address: tmValidator.Address, Power: tmValidator.VotingPower}, - tc.vscID, - stakingtypes.Downtime, - ) - - // slash - _, err := providerKeeper.HandleSlashPacket(suite.providerChain.GetContext(), suite.consumerChain.ChainID, slashPacket) - suite.Require().NoError(err) - - ubd, found := providerStakingKeeper.GetUnbondingDelegation(suite.providerChain.GetContext(), delAddr, valAddr) - suite.Require().True(found) - - isUbdSlashed := ubdBalance.GT(ubd.Entries[0].Balance) - suite.Require().True(tc.expSlash == isUbdSlashed) - - // update balance - ubdBalance = ubd.Entries[0].Balance - } -} - func TestIterateOverUnbondingOpIndex(t *testing.T) { providerKeeper, ctx := testkeeper.GetProviderKeeperAndCtx(t) chainID := "6" diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index c66ceda54e..f62f246458 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -1,124 +1,15 @@ package keeper_test import ( - "encoding/json" "testing" "time" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - appProvider "github.com/cosmos/interchain-security/app/provider" testkeeper "github.com/cosmos/interchain-security/testutil/keeper" - consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" "github.com/cosmos/interchain-security/x/ccv/provider/types" - abci "github.com/tendermint/tendermint/abci/types" - crypto "github.com/tendermint/tendermint/proto/tendermint/crypto" ) -func (suite *KeeperTestSuite) TestMakeConsumerGenesis() { - suite.SetupTest() - - actualGenesis, err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.MakeConsumerGenesis(suite.providerChain.GetContext()) - suite.Require().NoError(err) - - jsonString := `{"params":{"enabled":true, "blocks_per_distribution_transmission":1000, "lock_unbonding_on_timeout": false},"new_chain":true,"provider_client_state":{"chain_id":"testchain1","trust_level":{"numerator":1,"denominator":3},"trusting_period":907200000000000,"unbonding_period":1814400000000000,"max_clock_drift":10000000000,"frozen_height":{},"latest_height":{"revision_height":5},"proof_specs":[{"leaf_spec":{"hash":1,"prehash_value":1,"length":1,"prefix":"AA=="},"inner_spec":{"child_order":[0,1],"child_size":33,"min_prefix_length":4,"max_prefix_length":12,"hash":1}},{"leaf_spec":{"hash":1,"prehash_value":1,"length":1,"prefix":"AA=="},"inner_spec":{"child_order":[0,1],"child_size":32,"min_prefix_length":1,"max_prefix_length":1,"hash":1}}],"upgrade_path":["upgrade","upgradedIBCState"],"allow_update_after_expiry":true,"allow_update_after_misbehaviour":true},"provider_consensus_state":{"timestamp":"2020-01-02T00:00:10Z","root":{"hash":"LpGpeyQVLUo9HpdsgJr12NP2eCICspcULiWa5u9udOA="},"next_validators_hash":"E30CE736441FB9101FADDAF7E578ABBE6DFDB67207112350A9A904D554E1F5BE"},"unbonding_sequences":null,"initial_val_set":[{"pub_key":{"type":"tendermint/PubKeyEd25519","value":"dcASx5/LIKZqagJWN0frOlFtcvz91frYmj/zmoZRWro="},"power":1}]}` - - var expectedGenesis consumertypes.GenesisState - err = json.Unmarshal([]byte(jsonString), &expectedGenesis) - suite.Require().NoError(err) - - // Zero out differing fields- TODO: figure out how to get the test suite to - // keep these deterministic - actualGenesis.ProviderConsensusState.NextValidatorsHash = []byte{} - expectedGenesis.ProviderConsensusState.NextValidatorsHash = []byte{} - - // set valset to one empty validator because SetupTest() creates 4 validators per chain - actualGenesis.InitialValSet = []abci.ValidatorUpdate{{PubKey: crypto.PublicKey{}, Power: actualGenesis.InitialValSet[0].Power}} - expectedGenesis.InitialValSet[0].PubKey = crypto.PublicKey{} - - actualGenesis.ProviderConsensusState.Root.Hash = []byte{} - expectedGenesis.ProviderConsensusState.Root.Hash = []byte{} - - suite.Require().Equal(actualGenesis, expectedGenesis, "consumer chain genesis created incorrectly") -} - -func (suite *KeeperTestSuite) TestCreateConsumerChainProposal() { - var ( - ctx sdk.Context - proposal *types.CreateConsumerChainProposal - ok bool - ) - - chainID := "chainID" - initialHeight := clienttypes.NewHeight(2, 3) - lockUbdOnTimeout := false - - testCases := []struct { - name string - malleate func(*KeeperTestSuite) - expPass bool - spawnReached bool - }{ - { - "valid create consumer chain proposal: spawn time reached", func(suite *KeeperTestSuite) { - // ctx blocktime is after proposal's spawn time - ctx = suite.providerChain.GetContext().WithBlockTime(time.Now().Add(time.Hour)) - content, err := types.NewCreateConsumerChainProposal("title", "description", chainID, initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now()) - suite.Require().NoError(err) - proposal, ok = content.(*types.CreateConsumerChainProposal) - suite.Require().True(ok) - proposal.LockUnbondingOnTimeout = lockUbdOnTimeout - }, true, true, - }, - { - "valid proposal: spawn time has not yet been reached", func(suite *KeeperTestSuite) { - // ctx blocktime is before proposal's spawn time - ctx = suite.providerChain.GetContext().WithBlockTime(time.Now()) - content, err := types.NewCreateConsumerChainProposal("title", "description", chainID, initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now().Add(time.Hour)) - suite.Require().NoError(err) - proposal, ok = content.(*types.CreateConsumerChainProposal) - suite.Require().True(ok) - proposal.LockUnbondingOnTimeout = lockUbdOnTimeout - }, true, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - - tc.malleate(suite) - - err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerChainProposal(ctx, proposal) - if tc.expPass { - suite.Require().NoError(err, "error returned on valid case") - if tc.spawnReached { - clientId, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId(ctx, chainID) - suite.Require().True(found, "consumer client not found") - consumerGenesis, ok := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis(ctx, chainID) - suite.Require().True(ok) - - expectedGenesis, err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.MakeConsumerGenesis(ctx) - suite.Require().NoError(err) - - suite.Require().Equal(expectedGenesis, consumerGenesis) - suite.Require().NotEqual("", clientId, "consumer client was not created after spawn time reached") - } else { - gotProposal := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetPendingCreateProposal(ctx, proposal.SpawnTime, chainID) - suite.Require().Equal(initialHeight, gotProposal.InitialHeight, "unexpected pending proposal (InitialHeight)") - suite.Require().Equal(lockUbdOnTimeout, gotProposal.LockUnbondingOnTimeout, "unexpected pending proposal (LockUnbondingOnTimeout)") - } - } else { - suite.Require().Error(err, "did not return error on invalid case") - } - }) - } -} - func TestPendingStopProposalDeletion(t *testing.T) { testCases := []struct { diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go deleted file mode 100644 index cfce155545..0000000000 --- a/x/ccv/provider/proposal_handler_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package provider_test - -import ( - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - appProvider "github.com/cosmos/interchain-security/app/provider" - "github.com/cosmos/interchain-security/x/ccv/provider" - "github.com/cosmos/interchain-security/x/ccv/provider/types" -) - -// TestConsumerChainProposalHandler tests the handler for consumer chain proposals -// for both CreateConsumerChainProposal and StopConsumerChainProposal -func (suite *ProviderTestSuite) TestConsumerChainProposalHandler() { - var ( - ctx sdk.Context - content govtypes.Content - err error - ) - - testCases := []struct { - name string - malleate func(*ProviderTestSuite) - expPass bool - }{ - { - "valid create consumerchain proposal", func(suite *ProviderTestSuite) { - initialHeight := clienttypes.NewHeight(2, 3) - // ctx blocktime is after proposal's spawn time - ctx = suite.providerChain.GetContext().WithBlockTime(time.Now().Add(time.Hour)) - content, err = types.NewCreateConsumerChainProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now()) - suite.Require().NoError(err) - }, true, - }, - { - "valid stop consumerchain proposal", func(suite *ProviderTestSuite) { - ctx = suite.providerChain.GetContext().WithBlockTime(time.Now().Add(time.Hour)) - content, err = types.NewStopConsumerChainProposal("title", "description", "chainID", time.Now()) - suite.Require().NoError(err) - }, true, - }, - { - "nil proposal", func(suite *ProviderTestSuite) { - ctx = suite.providerChain.GetContext() - content = nil - }, false, - }, - { - "unsupported proposal type", func(suite *ProviderTestSuite) { - ctx = suite.providerChain.GetContext() - content = distributiontypes.NewCommunityPoolSpendProposal(ibctesting.Title, ibctesting.Description, suite.providerChain.SenderAccount.GetAddress(), sdk.NewCoins(sdk.NewCoin("communityfunds", sdk.NewInt(10)))) - }, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - - tc.malleate(suite) - - proposalHandler := provider.NewConsumerChainProposalHandler(suite.providerChain.App.(*appProvider.App).ProviderKeeper) - - err = proposalHandler(ctx, content) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/x/ccv/provider/provider_test.go b/x/ccv/provider/provider_test.go deleted file mode 100644 index 37a15a1d89..0000000000 --- a/x/ccv/provider/provider_test.go +++ /dev/null @@ -1,478 +0,0 @@ -package provider_test - -import ( - "bytes" - "testing" - "time" - - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - ccv "github.com/cosmos/interchain-security/x/ccv/types" - "github.com/cosmos/interchain-security/x/ccv/utils" - - transfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - - appConsumer "github.com/cosmos/interchain-security/app/consumer" - appProvider "github.com/cosmos/interchain-security/app/provider" - "github.com/cosmos/interchain-security/testutil/simapp" - consumertypes "github.com/cosmos/interchain-security/x/ccv/consumer/types" - providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" - "github.com/cosmos/interchain-security/x/ccv/types" - - abci "github.com/tendermint/tendermint/abci/types" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/stretchr/testify/suite" -) - -type ProviderTestSuite struct { - suite.Suite - - coordinator *ibctesting.Coordinator - - // testing chains - providerChain *ibctesting.TestChain - consumerChain *ibctesting.TestChain - - path *ibctesting.Path - transferPath *ibctesting.Path -} - -func (suite *ProviderTestSuite) SetupTest() { - suite.coordinator, suite.providerChain, suite.consumerChain = simapp.NewProviderConsumerCoordinator(suite.T()) - - // valsets must match - providerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.providerChain.Vals) - consumerValUpdates := tmtypes.TM2PB.ValidatorUpdates(suite.consumerChain.Vals) - suite.Require().True(len(providerValUpdates) == len(consumerValUpdates), "initial valset not matching") - for i := 0; i < len(providerValUpdates); i++ { - addr1 := utils.GetChangePubKeyAddress(providerValUpdates[i]) - addr2 := utils.GetChangePubKeyAddress(consumerValUpdates[i]) - suite.Require().True(bytes.Equal(addr1, addr2), "validator mismatch") - } - - // move both chains to the next block - suite.providerChain.NextBlock() - suite.consumerChain.NextBlock() - - // create consumer client on provider chain and set as consumer client for consumer chainID in provider keeper. - err := suite.providerChain.App.(*appProvider.App).ProviderKeeper.CreateConsumerClient( - suite.providerCtx(), - suite.consumerChain.ChainID, - suite.consumerChain.LastHeader.GetHeight().(clienttypes.Height), - false, - ) - suite.Require().NoError(err) - // move provider to next block to commit the state - suite.providerChain.NextBlock() - - // initialize the consumer chain with the genesis state stored on the provider - consumerGenesis, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerGenesis( - suite.providerCtx(), - suite.consumerChain.ChainID, - ) - suite.Require().True(found, "consumer genesis not found") - suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.InitGenesis(suite.consumerChain.GetContext(), &consumerGenesis) - - // create path for the CCV channel - suite.path = ibctesting.NewPath(suite.consumerChain, suite.providerChain) - - // update CCV path with correct info - // - set provider endpoint's clientID - consumerClient, found := suite.providerChain.App.(*appProvider.App).ProviderKeeper.GetConsumerClientId( - suite.providerCtx(), - suite.consumerChain.ChainID, - ) - suite.Require().True(found, "consumer client not found") - suite.path.EndpointB.ClientID = consumerClient - // - set consumer endpoint's clientID - providerClient, found := suite.consumerChain.App.(*appConsumer.App).ConsumerKeeper.GetProviderClientID(suite.consumerChain.GetContext()) - suite.Require().True(found, "provider client not found") - suite.path.EndpointA.ClientID = providerClient - // - client config - providerUnbondingPeriod := suite.providerChain.App.(*appProvider.App).GetStakingKeeper().UnbondingTime(suite.providerCtx()) - suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = providerUnbondingPeriod - suite.path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = providerUnbondingPeriod / utils.TrustingPeriodFraction - consumerUnbondingPeriod := utils.ComputeConsumerUnbondingPeriod(providerUnbondingPeriod) - suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = consumerUnbondingPeriod - suite.path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = consumerUnbondingPeriod / utils.TrustingPeriodFraction - // - channel config - suite.path.EndpointA.ChannelConfig.PortID = consumertypes.PortID - suite.path.EndpointB.ChannelConfig.PortID = providertypes.PortID - suite.path.EndpointA.ChannelConfig.Version = types.Version - suite.path.EndpointB.ChannelConfig.Version = types.Version - suite.path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED - suite.path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED - - // set chains sender account number - // TODO: to be fixed in #151 - err = suite.path.EndpointB.Chain.SenderAccount.SetAccountNumber(6) - suite.Require().NoError(err) - err = suite.path.EndpointA.Chain.SenderAccount.SetAccountNumber(1) - suite.Require().NoError(err) - - // create path for the transfer channel - suite.transferPath = ibctesting.NewPath(suite.consumerChain, suite.providerChain) - suite.transferPath.EndpointA.ChannelConfig.PortID = transfertypes.PortID - suite.transferPath.EndpointB.ChannelConfig.PortID = transfertypes.PortID - suite.transferPath.EndpointA.ChannelConfig.Version = transfertypes.Version - suite.transferPath.EndpointB.ChannelConfig.Version = transfertypes.Version -} - -func (suite *ProviderTestSuite) SetupCCVChannel() { - suite.StartSetupCCVChannel() - suite.CompleteSetupCCVChannel() - suite.SetupTransferChannel() -} - -func (suite *ProviderTestSuite) StartSetupCCVChannel() { - suite.coordinator.CreateConnections(suite.path) - - err := suite.path.EndpointA.ChanOpenInit() - suite.Require().NoError(err) - - err = suite.path.EndpointB.ChanOpenTry() - suite.Require().NoError(err) -} - -func (suite *ProviderTestSuite) CompleteSetupCCVChannel() { - err := suite.path.EndpointA.ChanOpenAck() - suite.Require().NoError(err) - - err = suite.path.EndpointB.ChanOpenConfirm() - suite.Require().NoError(err) - - // ensure counterparty is up to date - err = suite.path.EndpointA.UpdateClient() - suite.Require().NoError(err) -} - -func (suite *ProviderTestSuite) SetupTransferChannel() { - // transfer path will use the same connection as ccv path - - suite.transferPath.EndpointA.ClientID = suite.path.EndpointA.ClientID - suite.transferPath.EndpointA.ConnectionID = suite.path.EndpointA.ConnectionID - suite.transferPath.EndpointB.ClientID = suite.path.EndpointB.ClientID - suite.transferPath.EndpointB.ConnectionID = suite.path.EndpointB.ConnectionID - - // CCV channel handshake will automatically initiate transfer channel handshake on ACK - // so transfer channel will be on stage INIT when CompleteSetupCCVChannel returns. - suite.transferPath.EndpointA.ChannelID = suite.consumerChain.App.(*appConsumer.App). - ConsumerKeeper.GetDistributionTransmissionChannel(suite.consumerChain.GetContext()) - - // Complete TRY, ACK, CONFIRM for transfer path - err := suite.transferPath.EndpointB.ChanOpenTry() - suite.Require().NoError(err) - - err = suite.transferPath.EndpointA.ChanOpenAck() - suite.Require().NoError(err) - - err = suite.transferPath.EndpointB.ChanOpenConfirm() - suite.Require().NoError(err) - - // ensure counterparty is up to date - err = suite.transferPath.EndpointA.UpdateClient() - suite.Require().NoError(err) -} - -func TestProviderTestSuite(t *testing.T) { - suite.Run(t, new(ProviderTestSuite)) -} - -func (s *ProviderTestSuite) TestPacketRoundtrip() { - s.SetupCCVChannel() - - // Bond some tokens on provider to change validator powers - bondAmt := sdk.NewInt(1000000) - delAddr := s.providerChain.SenderAccount.GetAddress() - delegate(s, delAddr, bondAmt) - - // Send CCV packet to consumer - s.providerChain.NextBlock() - - // Relay 1 VSC packet from provider to consumer - relayAllCommittedPackets(s, s.providerChain, s.path, providertypes.PortID, s.path.EndpointB.ChannelID, 1) - - // Increment time so that the unbonding period ends on the provider - incrementTimeByUnbondingPeriod(s, Provider) - - // Relay 1 VSCMatured packet from consumer to provider - relayAllCommittedPackets(s, s.consumerChain, s.path, consumertypes.PortID, s.path.EndpointA.ChannelID, 1) -} - -func (s *ProviderTestSuite) providerCtx() sdk.Context { - return s.providerChain.GetContext() -} - -func (s *ProviderTestSuite) consumerCtx() sdk.Context { - return s.consumerChain.GetContext() -} - -func (s *ProviderTestSuite) providerBondDenom() string { - return s.providerChain.App.(*appProvider.App).StakingKeeper.BondDenom(s.providerCtx()) -} - -// TestSendDowntimePacket tests consumer initiated slashing -func (s *ProviderTestSuite) TestSendSlashPacketDowntime() { - s.SetupCCVChannel() - validatorsPerChain := len(s.consumerChain.Vals.Validators) - - providerStakingKeeper := s.providerChain.App.(*appProvider.App).StakingKeeper - providerSlashingKeeper := s.providerChain.App.(*appProvider.App).SlashingKeeper - - consumerKeeper := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper - - // 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(types.GetTimeoutTimestamp(oldBlockTime).UnixNano()) - packet := channeltypes.NewPacket(packetData.GetBytes(), 1, consumertypes.PortID, s.path.EndpointA.ChannelID, - providertypes.PortID, 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 - s.consumerChain.App.(*appConsumer.App).ConsumerKeeper.SetOutstandingDowntime(s.consumerCtx(), consAddr) - - // save next VSC packet info - oldBlockTime = s.providerCtx().BlockTime() - timeout = uint64(types.GetTimeoutTimestamp(oldBlockTime).UnixNano()) - valsetUpdateID := s.providerChain.App.(*appProvider.App).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) - - // reconstruct VSC packet - valUpdates := []abci.ValidatorUpdate{ - { - PubKey: val.GetPubKey(), - Power: int64(0), - }, - } - packetData2 := ccv.NewValidatorSetChangePacketData(valUpdates, valsetUpdateID, []string{consAddr.String()}) - packet2 := channeltypes.NewPacket(packetData2.GetBytes(), 1, providertypes.PortID, s.path.EndpointB.ChannelID, - consumertypes.PortID, 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 := s.providerChain.App.(*appProvider.App).StakingKeeper.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 := sdk.NewDec(1).QuoInt64(slashFraction).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 the outstanding slashing flag is reset on the consumer - pFlag := s.consumerChain.App.(*appConsumer.App).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) -} - -func (s *ProviderTestSuite) TestSendSlashPacketDoubleSign() { - s.SetupCCVChannel() - validatorsPerChain := len(s.consumerChain.Vals.Validators) - - providerStakingKeeper := s.providerChain.App.(*appProvider.App).StakingKeeper - providerSlashingKeeper := s.providerChain.App.(*appProvider.App).SlashingKeeper - consumerKeeper := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper - - // 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() - packetData := types.NewSlashPacketData(validator, valsetUpdateId, stakingtypes.DoubleSign) - - timeout := uint64(types.GetTimeoutTimestamp(oldBlockTime).UnixNano()) - packet := channeltypes.NewPacket(packetData.GetBytes(), 1, consumertypes.PortID, s.path.EndpointA.ChannelID, - providertypes.PortID, s.path.EndpointB.ChannelID, clienttypes.Height{}, timeout) - - // Send the downtime packet through CCV - err = s.path.EndpointA.SendPacket(packet) - s.Require().NoError(err) - - // save next VSC packet info - oldBlockTime = s.providerCtx().BlockTime() - timeout = uint64(types.GetTimeoutTimestamp(oldBlockTime).UnixNano()) - valsetUpdateID := s.providerChain.App.(*appProvider.App).ProviderKeeper.GetValidatorSetUpdateId(s.providerCtx()) - - // 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 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) - - // reconstruct VSC packet - valUpdates := []abci.ValidatorUpdate{ - { - PubKey: val.GetPubKey(), - Power: int64(0), - }, - } - packetData2 := ccv.NewValidatorSetChangePacketData(valUpdates, valsetUpdateID, []string{}) - packet2 := channeltypes.NewPacket(packetData2.GetBytes(), 1, providertypes.PortID, s.path.EndpointB.ChannelID, - consumertypes.PortID, 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 := s.providerChain.App.(*appProvider.App).StakingKeeper.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 *ProviderTestSuite) getVal(index int) (validator stakingtypes.Validator, valAddr sdk.ValAddress) { - // Choose a validator, and get its address and data structure into the correct types - tmValidator := s.providerChain.Vals.Validators[index] - valAddr, err := sdk.ValAddressFromHex(tmValidator.Address.String()) - s.Require().NoError(err) - validator, found := s.providerChain.App.(*appProvider.App).StakingKeeper.GetValidator(s.providerCtx(), valAddr) - s.Require().True(found) - - return validator, valAddr -} - -func (s *ProviderTestSuite) TestSlashPacketAcknowldgement() { - providerKeeper := s.providerChain.App.(*appProvider.App).ProviderKeeper - consumerKeeper := s.consumerChain.App.(*appConsumer.App).ConsumerKeeper - - packet := channeltypes.NewPacket([]byte{}, 1, consumertypes.PortID, s.path.EndpointA.ChannelID, - providertypes.PortID, "wrongchannel", clienttypes.Height{}, 0) - - ack := providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, ccv.SlashPacketData{}) - s.Require().NotNil(ack) - - err := consumerKeeper.OnAcknowledgementPacket(s.consumerCtx(), packet, channeltypes.NewResultAcknowledgement(ack.Acknowledgement())) - s.Require().NoError(err) - - err = consumerKeeper.OnAcknowledgementPacket(s.consumerCtx(), packet, channeltypes.NewErrorAcknowledgement("another error")) - s.Require().Error(err) -}