Skip to content

Commit

Permalink
Add misbehaviour monitoring tests (#485)
Browse files Browse the repository at this point in the history
* Refactor tests to use deterministic seed

* Update docker files

* Add misbehaviour monitor tests and address few comments

* Fix lint issues

* Modify gaia dockerfile

* Fix docker run issue

* Modify misbehaviour test

* Modify akash tests

* Revert akash docker file changes

* use mock PV

* Update trusted height in tests

* Modify misbehaviour monitoring tests

* Update header height in tests

* Update params

* Add log

* Update revision number

* Modify trusted height

* Revert revision number change

* fix chainID and validator set usage

* Update response code check

* misbehaviour tests working 🎉

* minor fix in tests

Co-authored-by: Colin Axnér <25233464+colin-axner@users.noreply.github.com>
  • Loading branch information
akhilkumarpilli and colin-axner committed Apr 9, 2021
1 parent 0ca5082 commit 6c13a43
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ chain-code/
two-chains/ibc-*
two-chains/.relayer
two-chains/*.log
test/setup/valkeys/*.json
4 changes: 2 additions & 2 deletions relayer/misbehaviour.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var (
// against the associated light client. If the headers do not match, the emitted
// header and a reconstructed header are used in misbehaviour submission to
// the IBC client on the source chain.
func checkAndSubmitMisbehaviour(src *Chain, events map[string][]string) error {
func checkAndSubmitMisbehaviour(src, counterparty *Chain, events map[string][]string) error {
hdrs, ok := events[fmt.Sprintf("%s.%s", updateCliTag, headerTag)]
if !ok {
return nil
Expand Down Expand Up @@ -52,7 +52,7 @@ func checkAndSubmitMisbehaviour(src *Chain, events map[string][]string) error {
return fmt.Errorf("emitted header is not tendermint type")
}

trustedHeader, err := src.GetLightSignedHeaderAtHeight(emittedHeader.Header.Height)
trustedHeader, err := counterparty.GetLightSignedHeaderAtHeight(emittedHeader.Header.Height)
if err != nil {
return err
}
Expand Down
4 changes: 3 additions & 1 deletion relayer/naive-strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,9 @@ func (nrs *NaiveStrategy) UnrelayedAcknowledgements(src, dst *Chain) (*RelaySequ
// HandleEvents defines how the relayer will handle block and transaction events as they are emitted
func (nrs *NaiveStrategy) HandleEvents(src, dst *Chain, events map[string][]string) {
// check for misbehaviour and submit if found
err := checkAndSubmitMisbehaviour(src, events)
// events came from dst chain, use that chain as the source
// the chain messages are submitted to
err := checkAndSubmitMisbehaviour(dst, src, events)
if err != nil {
src.Error(err)
}
Expand Down
4 changes: 2 additions & 2 deletions test/relayer_akash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

var (
akashChains = []testChain{
{"ibc-0", gaiaTestConfig},
{"ibc-1", akashTestConfig},
{"ibc-0", 0, gaiaTestConfig},
{"ibc-1", 1, akashTestConfig},
}
)

Expand Down
175 changes: 173 additions & 2 deletions test/relayer_gaia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@ package test

import (
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/07-tendermint/types"
ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing"
ibctestingmock "github.com/cosmos/cosmos-sdk/x/ibc/testing/mock"
"github.com/cosmos/relayer/relayer"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/tmhash"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmprotoversion "github.com/tendermint/tendermint/proto/tendermint/version"
tmtypes "github.com/tendermint/tendermint/types"
tmversion "github.com/tendermint/tendermint/version"
)

var (
gaiaChains = []testChain{
{"ibc-0", gaiaTestConfig},
{"ibc-1", gaiaTestConfig},
{"ibc-0", 0, gaiaTestConfig},
{"ibc-1", 1, gaiaTestConfig},
}
)

Expand Down Expand Up @@ -148,3 +158,164 @@ func TestGaiaReuseIdentifiers(t *testing.T) {
require.Equal(t, expectedSrc, src)
require.Equal(t, expectedDst, dst)
}

func TestGaiaMisbehaviourMonitoring(t *testing.T) {
chains := spinUpTestChains(t, gaiaChains...)

var (
src = chains.MustGet("ibc-0")
dst = chains.MustGet("ibc-1")
)

path, err := genTestPathAndSet(src, dst, "transfer", "transfer")
require.NoError(t, err)

// create path
_, err = src.CreateClients(dst)
require.NoError(t, err)
testClientPair(t, src, dst)

_, err = src.CreateOpenConnections(dst, 3, src.GetTimeout())
require.NoError(t, err)
testConnectionPair(t, src, dst)

_, err = src.CreateOpenChannels(dst, 3, src.GetTimeout())
require.NoError(t, err)
testChannelPair(t, src, dst)

// start the relayer process in it's own goroutine
rlyDone, err := relayer.RunStrategy(src, dst, path.MustGetStrategy())
require.NoError(t, err)

// Wait for relay message inclusion in both chains
require.NoError(t, src.WaitForNBlocks(1))
require.NoError(t, dst.WaitForNBlocks(1))

latestHeight, err := dst.QueryLatestHeight()
require.NoError(t, err)

header, err := dst.QueryHeaderAtHeight(latestHeight)
require.NoError(t, err)

clientStateRes, err := src.QueryClientState(latestHeight)
require.NoError(t, err)

// unpack any into ibc tendermint client state
clientStateExported, err := clienttypes.UnpackClientState(clientStateRes.ClientState)
require.NoError(t, err)

// cast from interface to concrete type
clientState, ok := clientStateExported.(*ibctmtypes.ClientState)
require.True(t, ok, "error when casting exported clientstate")

height := clientState.GetLatestHeight().(clienttypes.Height)
heightPlus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight+1)

// setup validator for signing duplicate header
// use key for dst
privKey := getSDKPrivKey(1)
privVal := ibctestingmock.PV{
PrivKey: privKey,
}
pubKey, err := privVal.GetPubKey()
require.NoError(t, err)
validator := tmtypes.NewValidator(pubKey, header.ValidatorSet.Proposer.VotingPower)
valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator})
signers := []tmtypes.PrivValidator{privVal}

// creating duplicate header
newHeader := createTMClientHeader(t, dst.ChainID, int64(heightPlus1.RevisionHeight), height,
header.GetTime().Add(time.Minute), valSet, valSet, signers, header)

// update client with duplicate header
updateMsg, err := clienttypes.NewMsgUpdateClient(src.PathEnd.ClientID, newHeader, src.MustGetAddress())
require.NoError(t, err)

res, success, err := src.SendMsg(updateMsg)
require.NoError(t, err)
require.True(t, success)
require.Equal(t, uint32(0), res.Code)

// wait for packet processing
require.NoError(t, dst.WaitForNBlocks(6))

// kill relayer routine
rlyDone()

clientStateRes, err = src.QueryClientState(0)
require.NoError(t, err)

// unpack any into ibc tendermint client state
clientStateExported, err = clienttypes.UnpackClientState(clientStateRes.ClientState)
require.NoError(t, err)

// cast from interface to concrete type
clientState, ok = clientStateExported.(*ibctmtypes.ClientState)
require.True(t, ok, "error when casting exported clientstate")

// clientstate should be frozen
require.True(t, clientState.IsFrozen())
}

func createTMClientHeader(t *testing.T, chainID string, blockHeight int64, trustedHeight clienttypes.Height,
timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator,
oldHeader *ibctmtypes.Header) *ibctmtypes.Header {
var (
valSet *tmproto.ValidatorSet
trustedVals *tmproto.ValidatorSet
)
require.NotNil(t, tmValSet)

vsetHash := tmValSet.Hash()

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.Header.LastCommitHash,
DataHash: tmhash.Sum([]byte("data_hash")),
ValidatorsHash: vsetHash,
NextValidatorsHash: vsetHash,
ConsensusHash: tmhash.Sum([]byte("consensus_hash")),
AppHash: tmhash.Sum([]byte("app_hash")),
LastResultsHash: tmhash.Sum([]byte("last_results_hash")),
EvidenceHash: tmhash.Sum([]byte("evidence_hash")),
ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck
}
hhash := tmHeader.Hash()
blockID := ibctesting.MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set")))
voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet)

commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp)
require.NoError(t, err)

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

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

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

// The trusted fields may be nil. They may be filled before relaying messages to a client.
// The relayer is responsible for querying client and injecting appropriate trusted fields.
return &ibctmtypes.Header{
SignedHeader: signedHeader,
ValidatorSet: valSet,
TrustedHeight: trustedHeight,
TrustedValidators: trustedVals,
}
}
4 changes: 3 additions & 1 deletion test/setup/Dockerfile.akashtest
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ USER root

COPY ./akash-setup.sh .

COPY ./valkeys ./setup/valkeys

EXPOSE 26657

ENTRYPOINT [ "./akash-setup.sh" ]
# NOTE: to run this image, docker run -d -p 26657:26657 ./single-node.sh {{chain_id}} {{genesis_account}}
# NOTE: to run this image, docker run -d -p 26657:26657 ./akash-setup.sh {{chain_id}} {{genesis_account}} {{seeds}} {{priv_validator_key_path}}
10 changes: 9 additions & 1 deletion test/setup/Dockerfile.gaiatest
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ WORKDIR /gaia

COPY ./gaia-setup.sh .

COPY ./valkeys ./setup/valkeys

USER root

RUN chmod -R 777 ./setup

USER gaia

EXPOSE 26657

ENTRYPOINT [ "./gaia-setup.sh" ]
# NOTE: to run this image, docker run -d -p 26657:26657 ./single-node.sh {{chain_id}} {{genesis_account}}
# NOTE: to run this image, docker run -d -p 26657:26657 ./gaia-setup.sh {{chain_id}} {{genesis_account}} {{seeds}} {{priv_validator_key_path}}
7 changes: 7 additions & 0 deletions test/setup/akash-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -o errexit -o nounset

CHAINID=$1
GENACCT=$2
PRIVPATH=$3

if [ -z "$1" ]; then
echo "Need to input chain id..."
Expand All @@ -15,12 +16,18 @@ if [ -z "$2" ]; then
exit 1
fi

if [ -z "$3" ]; then
echo "Need to input path of priv_validator_key json file"
exit 1
fi

# Build genesis file incl account for passed address
coins="10000000000stake,100000000000samoleans"
akash init --chain-id $CHAINID $CHAINID
akash keys add validator --keyring-backend="test"
akash add-genesis-account $(akash keys show validator -a --keyring-backend="test") $coins
akash add-genesis-account $GENACCT $coins
cp $PRIVPATH ~/.akash/config/priv_validator_key.json
akash gentx validator 5000000000stake --keyring-backend="test" --chain-id $CHAINID
akash collect-gentxs

Expand Down
7 changes: 7 additions & 0 deletions test/setup/gaia-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -o errexit -o nounset

CHAINID=$1
GENACCT=$2
PRIVPATH=$3

if [ -z "$1" ]; then
echo "Need to input chain id..."
Expand All @@ -15,12 +16,18 @@ if [ -z "$2" ]; then
exit 1
fi

if [ -z "$3" ]; then
echo "Need to input path of priv_validator_key json file"
exit 1
fi

# Build genesis file incl account for passed address
coins="10000000000stake,100000000000samoleans"
gaiad init --chain-id $CHAINID $CHAINID
gaiad keys add validator --keyring-backend="test"
gaiad add-genesis-account $(gaiad keys show validator -a --keyring-backend="test") $coins
gaiad add-genesis-account $GENACCT $coins
cp $PRIVPATH ~/.gaia/config/priv_validator_key.json
gaiad gentx validator 5000000000stake --keyring-backend="test" --chain-id $CHAINID
gaiad collect-gentxs

Expand Down
Empty file added test/setup/valkeys/.gitkeep
Empty file.
12 changes: 12 additions & 0 deletions test/test_chains.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ import (
ry "github.com/cosmos/relayer/relayer"
)

const (
// SEED1 is a mnenomic
//nolint:lll
SEED1 = "cake blossom buzz suspect image view round utility meat muffin humble club model latin similar glow draw useless kiwi snow laugh gossip roof public"
// SEED2 is a mnemonic
//nolint:lll
SEED2 = "near little movie lady moon fuel abandon gasp click element muscle elbow taste indoor soft soccer like occur legend coin near random normal adapt"
)

var (
// GAIA BLOCK TIMEOUTS are located in the gaia setup script in the
// setup directory.
Expand All @@ -37,13 +46,16 @@ var (
accountPrefix: "akash",
trustingPeriod: "330h",
}

seeds = []string{SEED1, SEED2}
)

type (
// testChain represents the different configuration options for spinning up a test
// cosmos-sdk based blockchain
testChain struct {
chainID string
seed int
t testChainConfig
}

Expand Down
Loading

0 comments on commit 6c13a43

Please sign in to comment.