Skip to content

Commit

Permalink
Add misbehaviour submission e2e test (#3245)
Browse files Browse the repository at this point in the history
  • Loading branch information
chatton authored Mar 10, 2023
1 parent 4037e14 commit afdc278
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"chain-b": ["release-v6.1.x", "v6.1.0", "v5.2.0", "v4.3.0", "v4.2.0", "v4.1.1", "v3.4.0", "v3.3.1"],
"entrypoint": ["TestClientTestSuite"],
"test": [
"TestClientUpdateProposal_Succeeds"
"TestClientUpdateProposal_Succeeds",
"TestClient_Update_Misbehaviour"
],
"relayer-type": ["rly"],
"chain-binary": ["simd"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"chain-b": ["release-v6.1.x"],
"entrypoint": ["TestClientTestSuite"],
"test": [
"TestClientUpdateProposal_Succeeds"
"TestClientUpdateProposal_Succeeds",
"TestClient_Update_Misbehaviour"
],
"relayer-type": ["rly"],
"chain-binary": ["simd"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"chain-b": ["release-v7.0.x", "v6.1.0", "v5.2.0", "v4.3.0", "v4.2.0", "v4.1.1", "v3.4.0", "v3.3.1"],
"entrypoint": ["TestClientTestSuite"],
"test": [
"TestClientUpdateProposal_Succeeds"
"TestClientUpdateProposal_Succeeds",
"TestClient_Update_Misbehaviour"
],
"relayer-type": ["rly"],
"chain-binary": ["simd"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"chain-b": ["release-v7.0.x"],
"entrypoint": ["TestClientTestSuite"],
"test": [
"TestClientUpdateProposal_Succeeds"
"TestClientUpdateProposal_Succeeds",
"TestClient_Update_Misbehaviour"
],
"relayer-type": ["rly"],
"chain-binary": ["simd"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"chain-b": ["release-v7.1.x", "v7.0.0-rc1", "v6.1.0", "v5.2.0", "v4.3.0", "v4.2.0", "v4.1.1", "v3.4.0", "v3.3.1"],
"entrypoint": ["TestClientTestSuite"],
"test": [
"TestClientUpdateProposal_Succeeds"
"TestClientUpdateProposal_Succeeds",
"TestClient_Update_Misbehaviour"
],
"relayer-type": ["rly"],
"chain-binary": ["simd"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"chain-b": ["release-v7.1.x"],
"entrypoint": ["TestClientTestSuite"],
"test": [
"TestClientUpdateProposal_Succeeds"
"TestClientUpdateProposal_Succeeds",
"TestClient_Update_Misbehaviour"
],
"relayer-type": ["rly"],
"chain-binary": ["simd"],
Expand Down
221 changes: 221 additions & 0 deletions e2e/tests/core/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,38 @@ package e2e
import (
"context"
"fmt"
"sort"
"strings"
"testing"
"time"

"github.com/cometbft/cometbft/crypto/tmhash"
tmjson "github.com/cometbft/cometbft/libs/json"
"github.com/cometbft/cometbft/privval"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
tmprotoversion "github.com/cometbft/cometbft/proto/tendermint/version"
tmtypes "github.com/cometbft/cometbft/types"
tmversion "github.com/cometbft/cometbft/version"
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
"github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v7/ibc"
test "github.com/strangelove-ventures/interchaintest/v7/testutil"
"github.com/stretchr/testify/suite"

"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"

"github.com/cosmos/ibc-go/e2e/dockerutil"
"github.com/cosmos/ibc-go/e2e/testsuite"
"github.com/cosmos/ibc-go/e2e/testvalues"
clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint"
ibctesting "github.com/cosmos/ibc-go/v7/testing"
ibcmock "github.com/cosmos/ibc-go/v7/testing/mock"
)

const (
invalidHashValue = "invalid_hash"
)

func TestClientTestSuite(t *testing.T) {
Expand Down Expand Up @@ -119,3 +138,205 @@ func (s *ClientTestSuite) TestClientUpdateProposal_Succeeds() {
})
})
}

func (s *ClientTestSuite) TestClient_Update_Misbehaviour() {
t := s.T()
ctx := context.TODO()

var (
trustedHeight clienttypes.Height
latestHeight clienttypes.Height
clientState ibcexported.ClientState
block *tmproto.Block
signers []tmtypes.PrivValidator
validatorSet []*tmtypes.Validator
maliciousHeader *ibctm.Header
err error
)

relayer, _ := s.SetupChainsRelayerAndChannel(ctx)
chainA, chainB := s.GetChains()

s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB))

t.Run("update clients", func(t *testing.T) {
err := relayer.UpdateClients(ctx, s.GetRelayerExecReporter(), s.GetPathName(0))
s.Require().NoError(err)

clientState, err = s.QueryClientState(ctx, chainA, ibctesting.FirstClientID)
s.Require().NoError(err)
})

t.Run("fetch trusted height", func(t *testing.T) {
tmClientState, ok := clientState.(*ibctm.ClientState)
s.Require().True(ok)

trustedHeight, ok = tmClientState.GetLatestHeight().(clienttypes.Height)
s.Require().True(ok)
})

t.Run("update clients", func(t *testing.T) {
err := relayer.UpdateClients(ctx, s.GetRelayerExecReporter(), s.GetPathName(0))
s.Require().NoError(err)

clientState, err = s.QueryClientState(ctx, chainA, ibctesting.FirstClientID)
s.Require().NoError(err)
})

t.Run("fetch client state latest height", func(t *testing.T) {
tmClientState, ok := clientState.(*ibctm.ClientState)
s.Require().True(ok)

latestHeight, ok = tmClientState.GetLatestHeight().(clienttypes.Height)
s.Require().True(ok)
})

t.Run("create validator set", func(t *testing.T) {
var validators []*tmservice.Validator

t.Run("fetch block at latest client state height", func(t *testing.T) {
block, err = s.GetBlockByHeight(ctx, chainB, latestHeight.GetRevisionHeight())
s.Require().NoError(err)
})

t.Run("get validators at latest height", func(t *testing.T) {
validators, err = s.GetValidatorSetByHeight(ctx, chainB, latestHeight.GetRevisionHeight())
s.Require().NoError(err)
})

t.Run("extract validator private keys", func(t *testing.T) {
privateKeys := s.extractChainPrivateKeys(ctx, chainB)
for i, pv := range privateKeys {
pubKey, err := pv.GetPubKey()
s.Require().NoError(err)

validator := tmtypes.NewValidator(pubKey, validators[i].VotingPower)

validatorSet = append(validatorSet, validator)
signers = append(signers, pv)
}
})
})

t.Run("create malicious header", func(t *testing.T) {
valSet := tmtypes.NewValidatorSet(validatorSet)
maliciousHeader, err = createMaliciousTMHeader(chainB.Config().ChainID, int64(latestHeight.GetRevisionHeight()), trustedHeight,
block.Header.GetTime(), valSet, valSet, signers, &block.Header)
s.Require().NoError(err)
})

t.Run("update client with duplicate misbehaviour header", func(t *testing.T) {
rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
msgUpdateClient, err := clienttypes.NewMsgUpdateClient(ibctesting.FirstClientID, maliciousHeader, rlyWallet.FormattedAddress())
s.Require().NoError(err)

txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgUpdateClient)
s.Require().NoError(err)
s.AssertValidTxResponse(txResp)
})

t.Run("ensure client status is frozen", func(t *testing.T) {
status, err := s.QueryClientStatus(ctx, chainA, ibctesting.FirstClientID)
s.Require().NoError(err)
s.Require().Equal(ibcexported.Frozen.String(), status)
})
}

// extractChainPrivateKeys returns a slice of tmtypes.PrivValidator which hold the private keys for all validator
// nodes for a given chain.
func (s *ClientTestSuite) extractChainPrivateKeys(ctx context.Context, chain *cosmos.CosmosChain) []tmtypes.PrivValidator {
testContainers, err := dockerutil.GetTestContainers(s.T(), ctx, s.DockerClient)
s.Require().NoError(err)

var filePvs []privval.FilePVKey
var pvs []tmtypes.PrivValidator
for _, container := range testContainers {
isNodeForDifferentChain := !strings.Contains(container.Names[0], chain.Config().ChainID)
isFullNode := strings.Contains(container.Names[0], fmt.Sprintf("%s-fn", chain.Config().ChainID))
if isNodeForDifferentChain || isFullNode {
continue
}

validatorPrivKey := fmt.Sprintf("/var/cosmos-chain/%s/config/priv_validator_key.json", chain.Config().Name)
privKeyFileContents, err := dockerutil.GetFileContentsFromContainer(ctx, s.DockerClient, container.ID, validatorPrivKey)
s.Require().NoError(err)

var filePV privval.FilePVKey
err = tmjson.Unmarshal(privKeyFileContents, &filePV)
s.Require().NoError(err)
filePvs = append(filePvs, filePV)
}

// We sort by address as GetValidatorSetByHeight also sorts by address. When iterating over them, the index
// will correspond to the correct ibcmock.PV.
sort.SliceStable(filePvs, func(i, j int) bool {
return filePvs[i].Address.String() < filePvs[j].Address.String()
})

for _, filePV := range filePvs {
pvs = append(pvs, &ibcmock.PV{
PrivKey: &ed25519.PrivKey{Key: filePV.PrivKey.Bytes()},
})
}

return pvs
}

// createMaliciousTMHeader creates a header with the provided trusted height with an invalid app hash.
func createMaliciousTMHeader(
chainID string,
blockHeight int64,
trustedHeight clienttypes.Height,
timestamp time.Time,
tmValSet, tmTrustedVals *tmtypes.ValidatorSet,
signers []tmtypes.PrivValidator,
oldHeader *tmproto.Header,
) (*ibctm.Header, error) {
tmHeader := tmtypes.Header{
Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: 2},
ChainID: chainID,
Height: blockHeight,
Time: timestamp,
LastBlockID: ibctesting.MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)),
LastCommitHash: oldHeader.LastCommitHash,
ValidatorsHash: tmValSet.Hash(),
NextValidatorsHash: tmValSet.Hash(),
DataHash: tmhash.Sum([]byte(invalidHashValue)),
ConsensusHash: tmhash.Sum([]byte(invalidHashValue)),
AppHash: tmhash.Sum([]byte(invalidHashValue)),
LastResultsHash: tmhash.Sum([]byte(invalidHashValue)),
EvidenceHash: tmhash.Sum([]byte(invalidHashValue)),
ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck
}

hhash := tmHeader.Hash()
blockID := ibctesting.MakeBlockID(hhash, 3, tmhash.Sum([]byte(invalidHashValue)))
voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet)

commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp)
if err != nil {
return nil, err
}

signedHeader := &tmproto.SignedHeader{
Header: tmHeader.ToProto(),
Commit: commit.ToProto(),
}

valSet, err := tmValSet.ToProto()
if err != nil {
return nil, err
}

trustedVals, err := tmTrustedVals.ToProto()
if err != nil {
return nil, err
}

return &ibctm.Header{
SignedHeader: signedHeader,
ValidatorSet: valSet,
TrustedHeight: trustedHeight,
TrustedValidators: trustedVals,
}, nil
}
4 changes: 2 additions & 2 deletions e2e/testsuite/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"

"github.com/cosmos/cosmos-sdk/codec"
sdkcodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/authz"
Expand Down Expand Up @@ -62,7 +62,7 @@ func codecAndEncodingConfig() (*codec.ProtoCodec, simappparams.EncodingConfig) {
govv1beta1.RegisterInterfaces(cfg.InterfaceRegistry)
govv1.RegisterInterfaces(cfg.InterfaceRegistry)
authtypes.RegisterInterfaces(cfg.InterfaceRegistry)
sdkcodec.RegisterInterfaces(cfg.InterfaceRegistry)
cryptocodec.RegisterInterfaces(cfg.InterfaceRegistry)
grouptypes.RegisterInterfaces(cfg.InterfaceRegistry)
proposaltypes.RegisterInterfaces(cfg.InterfaceRegistry)
authz.RegisterInterfaces(cfg.InterfaceRegistry)
Expand Down
40 changes: 38 additions & 2 deletions e2e/testsuite/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package testsuite

import (
"context"
"sort"

"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
govtypesbeta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
intertxtypes "github.com/cosmos/interchain-accounts/x/inter-tx/types"
"github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v7/ibc"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"

controllertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types"
feetypes "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types"
Expand All @@ -27,8 +30,9 @@ func (s *E2ETestSuite) QueryClientState(ctx context.Context, chain ibc.Chain, cl
return nil, err
}

clientState, err := clienttypes.UnpackClientState(res.ClientState)
if err != nil {
cfg := EncodingConfig()
var clientState ibcexported.ClientState
if err := cfg.InterfaceRegistry.UnpackAny(res.ClientState, &clientState); err != nil {
return nil, err
}

Expand Down Expand Up @@ -171,3 +175,35 @@ func (s *E2ETestSuite) QueryProposalV1(ctx context.Context, chain ibc.Chain, pro

return *res.Proposal, nil
}

// GetBlockByHeight fetches the block at a given height. Note: we are explicitly using the res.Block type which has been
// deprecated instead of res.SdkBlock to support backwards compatibility tests.
func (s *E2ETestSuite) GetBlockByHeight(ctx context.Context, chain ibc.Chain, height uint64) (*tmproto.Block, error) {
tmService := s.GetChainGRCPClients(chain).ConsensusServiceClient
res, err := tmService.GetBlockByHeight(ctx, &tmservice.GetBlockByHeightRequest{
Height: int64(height),
})
if err != nil {
return nil, err
}

return res.Block, nil
}

// GetValidatorSetByHeight returns the validators of the given chain at the specified height. The returned validators
// are sorted by address.
func (s *E2ETestSuite) GetValidatorSetByHeight(ctx context.Context, chain ibc.Chain, height uint64) ([]*tmservice.Validator, error) {
tmService := s.GetChainGRCPClients(chain).ConsensusServiceClient
res, err := tmService.GetValidatorSetByHeight(ctx, &tmservice.GetValidatorSetByHeightRequest{
Height: int64(height),
})
if err != nil {
return nil, err
}

sort.SliceStable(res.Validators, func(i, j int) bool {
return res.Validators[i].Address < res.Validators[j].Address
})

return res.Validators, nil
}
Loading

0 comments on commit afdc278

Please sign in to comment.