Skip to content

Commit

Permalink
ci: Added tests to provider-consumer suite (#2456)
Browse files Browse the repository at this point in the history
* Add provider-consumer interchain tests

* remove interchain framework logs - leave only status

* add description how to run single interchain test

* triger interchain tests

* triger interchain tests

* remove log filtering

* set preccv to true for changeover genesis
  • Loading branch information
stana-miric authored Jan 9, 2025
1 parent 9563c21 commit 56d788c
Show file tree
Hide file tree
Showing 13 changed files with 478 additions and 152 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,29 @@ jobs:
if: env.GIT_DIFF
run: |
make verify-models
test-interchain:
runs-on: Gaia-Runner-medium
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
check-latest: true
cache: true
cache-dependency-path: go.sum
- uses: technote-space/get-diff-action@v6.1.2
id: git_diff
with:
PATTERNS: |
**/*.go
go.mod
go.sum
**/go.mod
**/go.sum
**/Makefile
Makefile
- name: interchain tests
if: env.GIT_DIFF
run: |
make test-interchain
15 changes: 8 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,18 @@ test-integration-cov:
go test ./tests/integration/... -timeout 30m -coverpkg=./... -coverprofile=integration-profile.out -covermode=atomic

# run interchain tests
# we can use PROVIDER_IMAGE_TAG, PROVIDER_IMAGE_NAME, SOUVEREIGN_IMAGE_TAG, and SOUVEREIGN_IMAGE_NAME to run tests with desired docker images,
# including locally built ones that, for example, contain some of our changes that are not yet on the main branch.
# if not provided, default value for PROVIDER_IMAGE_TAG and SOUVEREIGN_IMAGE_TAG is "latest" and for PROVIDER_IMAGE_NAME
# and SOUVEREIGN_IMAGE_NAME is "ghcr.io/cosmos/interchain-security"
# we can use PROVIDER_IMAGE_TAG, PROVIDER_IMAGE_NAME, CONSUMER_IMAGE_TAG, CONSUMER_IMAGE_NAME, SOVEREIGN_IMAGE_TAG, and SOVEREIGN_IMAGE_NAME to run
# tests with desired docker images, including locally built ones that, for example, contain some of our changes that are not yet on the main branch.
# if not provided, default value for image tag is "latest" and for image name is "ghcr.io/cosmos/interchain-security"
test-interchain:
cd tests/interchain && \
PROVIDER_IMAGE_NAME=$(PROVIDER_IMAGE_NAME) \
PROVIDER_IMAGE_TAG=$(PROVIDER_IMAGE_TAG) \
SOUVEREIGN_IMAGE_NAME=$(SOUVEREIGN_IMAGE_NAME) \
SOUVEREIGN_IMAGE_TAG=$(SOUVEREIGN_IMAGE_TAG) \
go test ./... -timeout 30m
SOVEREIGN_IMAGE_NAME=$(SOVEREIGN_IMAGE_NAME) \
SOVEREIGN_IMAGE_TAG=$(SOVEREIGN_IMAGE_TAG) \
CONSUMER_IMAGE_NAME=$(CONSUMER_IMAGE_NAME) \
CONSUMER_IMAGE_TAG=$(CONSUMER_IMAGE_TAG) \
go test ./... -timeout 30m -v

# run mbt tests
test-mbt:
Expand Down
4 changes: 3 additions & 1 deletion TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ make sim-full-no-inactive-vals
#run interchain tests (running with the latest image ghcr.io/cosmos/interchain-security:latest)
make test-interchain
# run interchain tests with specific image(e.g. test-image:local)
make test-interchain PROVIDER_IMAGE_NAME=test-image PROVIDER_IMAGE_TAG=local SOUVEREIGN_IMAGE_NAME=test-image SOUVEREIGN_IMAGE_TAG=local
make test-interchain PROVIDER_IMAGE_NAME=test-image PROVIDER_IMAGE_TAG=local SOVEREIGN_IMAGE_NAME=test-image SOVEREIGN_IMAGE_TAG=local
# to run single interchain test, first navigate to /tests/interchain directory and run the command for desired test e.g.
# go test -run ^TestMultiValidatorProviderSuite/TestOptInChainCanOnlyStartIfActiveValidatorOptedIn$ ./...
```

Alternatively you can run tests using `go test`:
Expand Down
116 changes: 114 additions & 2 deletions tests/interchain/chainsuite/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type Chain struct {
ValidatorWallets []ValidatorWallet
RelayerWallet ibc.Wallet
TestWallets []ibc.Wallet
walletMtx sync.Mutex
walletsInUse map[int]bool
}

type ValidatorWallet struct {
Expand All @@ -49,6 +51,7 @@ func chainFromCosmosChain(cosmos *cosmos.CosmosChain, relayerWallet ibc.Wallet,
c.ValidatorWallets = wallets
c.RelayerWallet = relayerWallet
c.TestWallets = testWallets
c.walletsInUse = make(map[int]bool)
return c, nil
}

Expand Down Expand Up @@ -88,7 +91,7 @@ func CreateChain(ctx context.Context, testName interchaintest.TestName, spec *in
}

// build test wallets
testWallets, err := setupTestWallets(ctx, cosmosChain, TestWalletsNumber)
testWallets, err := SetupTestWallets(ctx, cosmosChain, TestWalletsNumber)
if err != nil {
return nil, err
}
Expand All @@ -100,7 +103,7 @@ func CreateChain(ctx context.Context, testName interchaintest.TestName, spec *in
return chain, nil
}

func setupTestWallets(ctx context.Context, cosmosChain *cosmos.CosmosChain, walletCount int) ([]ibc.Wallet, error) {
func SetupTestWallets(ctx context.Context, cosmosChain *cosmos.CosmosChain, walletCount int) ([]ibc.Wallet, error) {
wallets := make([]ibc.Wallet, walletCount)
eg := new(errgroup.Group)
for i := 0; i < walletCount; i++ {
Expand Down Expand Up @@ -159,6 +162,85 @@ func getValidatorWallets(ctx context.Context, chain *Chain) ([]ValidatorWallet,
return wallets, nil
}

func (p *Chain) AddConsumerChain(ctx context.Context, relayer *Relayer, spec *interchaintest.ChainSpec) (*Chain, error) {
dockerClient, dockerNetwork := GetDockerContext(ctx)

cf := interchaintest.NewBuiltinChainFactory(
GetLogger(ctx),
[]*interchaintest.ChainSpec{spec},
)

chains, err := cf.Chains(p.GetNode().TestName)
if err != nil {
return nil, err
}
consumer := chains[0].(*cosmos.CosmosChain)

// We can't use AddProviderConsumerLink here because the provider chain is already built; we'll have to do everything by hand.
p.Consumers = append(p.Consumers, consumer)
consumer.Provider = p.CosmosChain
relayerWallet, err := consumer.BuildRelayerWallet(ctx, "relayer-"+consumer.Config().ChainID)
if err != nil {
return nil, err
}
wallets := make([]ibc.Wallet, len(p.Validators)+1)
wallets[0] = relayerWallet
// This is a hack, but we need to create wallets for the validators that have the right moniker.
for i := 1; i <= len(p.Validators); i++ {
wallets[i], err = consumer.BuildRelayerWallet(ctx, ValidatorMoniker)
if err != nil {
return nil, err
}
}
walletAmounts := make([]ibc.WalletAmount, len(wallets))
for i, wallet := range wallets {
walletAmounts[i] = ibc.WalletAmount{
Address: wallet.FormattedAddress(),
Denom: consumer.Config().Denom,
Amount: sdkmath.NewInt(TotalValidatorFunds),
}
}

ic := interchaintest.NewInterchain().
AddChain(consumer, walletAmounts...).
AddRelayer(relayer, "relayer")

if err := ic.Build(ctx, GetRelayerExecReporter(ctx), interchaintest.InterchainBuildOptions{
Client: dockerClient,
NetworkID: dockerNetwork,
TestName: p.GetNode().TestName,
}); err != nil {
return nil, err
}

for i, val := range consumer.Validators {
if err := val.RecoverKey(ctx, ValidatorMoniker, wallets[i+1].Mnemonic()); err != nil {
return nil, err
}
}
consumerChain, err := chainFromCosmosChain(consumer, relayerWallet, p.TestWallets)
if err != nil {
return nil, err
}

return consumerChain, nil
}

// GetUnusedTestingAddresss retrieves an unused wallet address and its key name safely
func (p *Chain) GetUnusedTestingAddresss() (formattedAddress string, keyName string, err error) {
p.walletMtx.Lock()
defer p.walletMtx.Unlock()

for i, wallet := range p.TestWallets {
if !p.walletsInUse[i] {
p.walletsInUse[i] = true
return wallet.FormattedAddress(), wallet.KeyName(), nil
}
}

return "", "", fmt.Errorf("no unused wallets available")
}

// UpdateAndVerifyStakeChange updates the staking amount on the provider chain and verifies that the change is reflected on the consumer side
func (p *Chain) UpdateAndVerifyStakeChange(ctx context.Context, consumer *Chain, relayer *Relayer, amount, valIdx int) error {

Expand Down Expand Up @@ -552,6 +634,36 @@ func (c *Chain) GetCcvConsumerParams(ctx context.Context) (ConsumerParamsRespons
return queryResponse, nil
}

func (c *Chain) GetProviderInfo(ctx context.Context) (ProviderInfoResponse, error) {
queryRes, _, err := c.GetNode().ExecQuery(
ctx,
"ccvconsumer", "provider-info",
)
if err != nil {
return ProviderInfoResponse{}, err
}

var queryResponse ProviderInfoResponse
err = json.Unmarshal([]byte(queryRes), &queryResponse)
if err != nil {
return ProviderInfoResponse{}, err
}

return queryResponse, nil
}

func (c *Chain) QueryJSON(ctx context.Context, jsonPath string, query ...string) (gjson.Result, error) {
stdout, _, err := c.GetNode().ExecQuery(ctx, query...)
if err != nil {
return gjson.Result{}, err
}
retval := gjson.GetBytes(stdout, jsonPath)
if !retval.Exists() {
return gjson.Result{}, fmt.Errorf("json path %s not found in query result %s", jsonPath, stdout)
}
return retval, nil
}

func getEvtAttribute(events []abci.Event, evtType string, key string) (string, bool) {
for _, evt := range events {
if evt.GetType() == evtType {
Expand Down
103 changes: 103 additions & 0 deletions tests/interchain/chainsuite/chain_spec_consumer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package chainsuite

import (
"context"
"errors"
"strconv"
"time"

providertypes "github.com/cosmos/interchain-security/v6/x/ccv/provider/types"
"github.com/strangelove-ventures/interchaintest/v8"
"github.com/strangelove-ventures/interchaintest/v8/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v8/ibc"
"github.com/strangelove-ventures/interchaintest/v8/testutil"
)

func GetConsumerSpec(ctx context.Context, providerChain *Chain, proposalMsg *providertypes.MsgCreateConsumer) *interchaintest.ChainSpec {
fullNodes := FullNodeCount
validators := 1

return &interchaintest.ChainSpec{
ChainName: ConsumerChainID,
NumFullNodes: &fullNodes,
NumValidators: &validators,
ChainConfig: ibc.ChainConfig{
ChainID: ConsumerChainID,
Bin: ConsumerBin,
Denom: Stake,
Type: CosmosChainType,
GasPrices: GasPrices + Stake,
GasAdjustment: 2.0,
TrustingPeriod: "336h",
CoinType: "118",
Images: []ibc.DockerImage{
{
Repository: ConsumerImageName(),
Version: ConsumerImageVersion(),
UIDGID: "1025:1025",
},
},
ConfigFileOverrides: map[string]any{
"config/config.toml": DefaultConfigToml(),
},
PreGenesis: func(consumer ibc.Chain) error {
// note that if Top_N>0 proposal will be rejected. If there is a need to support this in the future,
// it is necessary to first submit a create message with Top_N=0 and then an update message with Top_N>0
consumerID, err := providerChain.CreateConsumer(ctx, proposalMsg, ValidatorMoniker)
if err != nil {
return err
}

for index := 0; index < len(providerChain.Validators); index++ {
if err := providerChain.OptIn(ctx, consumerID, index); err != nil {
return err
}
}

// speed up chain launch by submitting update msg with current time as a spawn time
proposalMsg.InitializationParameters.SpawnTime = time.Now()
updateMsg := &providertypes.MsgUpdateConsumer{
ConsumerId: consumerID,
Owner: providerChain.ValidatorWallets[0].Address,
NewOwnerAddress: providerChain.ValidatorWallets[0].Address,
InitializationParameters: proposalMsg.InitializationParameters,
PowerShapingParameters: proposalMsg.PowerShapingParameters,
}
if err := providerChain.UpdateConsumer(ctx, updateMsg, ValidatorMoniker); err != nil {
return err
}

if err := testutil.WaitForBlocks(ctx, 2, providerChain); err != nil {
return err
}

consumerChain, err := providerChain.GetConsumerChainByChainId(ctx, proposalMsg.ChainId)
if err != nil {
return err
}

if consumerChain.Phase != providertypes.CONSUMER_PHASE_LAUNCHED.String() {
return errors.New("consumer chain is not launched")
}

return nil
},
Bech32Prefix: Bech32PrefixConsumer,
ModifyGenesisAmounts: DefaultGenesisAmounts(Stake),
ModifyGenesis: cosmos.ModifyGenesis(consumerModifiedGenesis()),
InterchainSecurityConfig: ibc.ICSConfig{
ConsumerCopyProviderKey: func(int) bool { return true },
},
},
}
}

func consumerModifiedGenesis() []cosmos.GenesisKV {
return []cosmos.GenesisKV{
cosmos.NewGenesisKV("app_state.slashing.params.signed_blocks_window", strconv.Itoa(SlashingWindowConsumer)),
cosmos.NewGenesisKV("consensus.params.block.max_gas", "50000000"),
cosmos.NewGenesisKV("app_state.ccvconsumer.params.soft_opt_out_threshold", "0.0"),
cosmos.NewGenesisKV("app_state.ccvconsumer.params.blocks_per_distribution_transmission", BlocksPerDistribution),
cosmos.NewGenesisKV("app_state.ccvconsumer.params.reward_denoms", []string{Stake}),
}
}
Loading

0 comments on commit 56d788c

Please sign in to comment.