diff --git a/x/ibc/02-client/client/utils/utils.go b/x/ibc/02-client/client/utils/utils.go index 7e453f0a5..70b0167ea 100644 --- a/x/ibc/02-client/client/utils/utils.go +++ b/x/ibc/02-client/client/utils/utils.go @@ -115,7 +115,6 @@ func QueryConsensusStateABCI( return nil, err } - // TODO: retrieve epoch-number from chain-id return types.NewQueryConsensusStateResponse(clientID, anyConsensusState, proofBz, proofHeight), nil } diff --git a/x/ibc/02-client/genesis.go b/x/ibc/02-client/genesis.go index 09cf5471c..be77662a8 100644 --- a/x/ibc/02-client/genesis.go +++ b/x/ibc/02-client/genesis.go @@ -45,10 +45,9 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, gs types.GenesisState) { } // client id is always "localhost" - // Hardcode 0 as epoch number for now - // TODO: Retrieve epoch from chain-id + epoch := types.ParseChainID(ctx.ChainID()) clientState := localhosttypes.NewClientState( - ctx.ChainID(), types.NewHeight(0, uint64(ctx.BlockHeight())), + ctx.ChainID(), types.NewHeight(epoch, uint64(ctx.BlockHeight())), ) _, err := k.CreateClient(ctx, exported.ClientTypeLocalHost, clientState, nil) diff --git a/x/ibc/02-client/keeper/client_test.go b/x/ibc/02-client/keeper/client_test.go index f4105fcd3..2c92ebe02 100644 --- a/x/ibc/02-client/keeper/client_test.go +++ b/x/ibc/02-client/keeper/client_test.go @@ -7,6 +7,7 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" @@ -60,11 +61,17 @@ func (suite *KeeperTestSuite) TestCreateClient() { func (suite *KeeperTestSuite) TestUpdateClientTendermint() { // Must create header creation functions since suite.header gets recreated on each test case createFutureUpdateFn := func(s *KeeperTestSuite) *ibctmtypes.Header { - return ibctmtypes.CreateTestHeader(testChainID, int64(suite.header.GetHeight().GetEpochHeight()+3), int64(suite.header.GetHeight().GetEpochHeight()), suite.header.Header.Time.Add(time.Hour), + heightPlus3 := clienttypes.NewHeight(suite.header.GetHeight().GetEpochNumber(), suite.header.GetHeight().GetEpochHeight()+3) + height := suite.header.GetHeight().(clienttypes.Height) + + return ibctmtypes.CreateTestHeader(testChainID, heightPlus3, height, suite.header.Header.Time.Add(time.Hour), suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) } createPastUpdateFn := func(s *KeeperTestSuite) *ibctmtypes.Header { - return ibctmtypes.CreateTestHeader(testChainID, int64(suite.header.GetHeight().GetEpochHeight()-2), int64(suite.header.GetHeight().GetEpochHeight())-4, suite.header.Header.Time, + heightMinus2 := clienttypes.NewHeight(suite.header.GetHeight().GetEpochNumber(), suite.header.GetHeight().GetEpochHeight()-2) + heightMinus4 := clienttypes.NewHeight(suite.header.GetHeight().GetEpochNumber(), suite.header.GetHeight().GetEpochHeight()-4) + + return ibctmtypes.CreateTestHeader(testChainID, heightMinus2, heightMinus4, suite.header.Header.Time, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) } var ( @@ -270,6 +277,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { altTime := suite.ctx.BlockTime().Add(time.Minute) heightPlus3 := types.NewHeight(0, height+3) + heightPlus5 := types.NewHeight(0, height+5) testCases := []struct { name string @@ -280,8 +288,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "trusting period misbehavior should pass", &ibctmtypes.Misbehaviour{ - Header1: ibctmtypes.CreateTestHeader(testChainID, height, height, altTime, bothValSet, bothValSet, bothSigners), - Header2: ibctmtypes.CreateTestHeader(testChainID, height, height, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: ibctmtypes.CreateTestHeader(testChainID, testClientHeight, testClientHeight, altTime, bothValSet, bothValSet, bothSigners), + Header2: ibctmtypes.CreateTestHeader(testChainID, testClientHeight, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ChainId: testChainID, ClientId: testClientID, }, @@ -297,8 +305,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "misbehavior at later height should pass", &ibctmtypes.Misbehaviour{ - Header1: ibctmtypes.CreateTestHeader(testChainID, height+5, height, altTime, bothValSet, valSet, bothSigners), - Header2: ibctmtypes.CreateTestHeader(testChainID, height+5, height, suite.ctx.BlockTime(), bothValSet, valSet, bothSigners), + Header1: ibctmtypes.CreateTestHeader(testChainID, heightPlus5, testClientHeight, altTime, bothValSet, valSet, bothSigners), + Header2: ibctmtypes.CreateTestHeader(testChainID, heightPlus5, testClientHeight, suite.ctx.BlockTime(), bothValSet, valSet, bothSigners), ChainId: testChainID, ClientId: testClientID, }, @@ -324,8 +332,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "misbehavior at later height with different trusted heights should pass", &ibctmtypes.Misbehaviour{ - Header1: ibctmtypes.CreateTestHeader(testChainID, height+5, height, altTime, bothValSet, valSet, bothSigners), - Header2: ibctmtypes.CreateTestHeader(testChainID, height+5, height+3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: ibctmtypes.CreateTestHeader(testChainID, heightPlus5, testClientHeight, altTime, bothValSet, valSet, bothSigners), + Header2: ibctmtypes.CreateTestHeader(testChainID, heightPlus5, heightPlus3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ChainId: testChainID, ClientId: testClientID, }, @@ -351,8 +359,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "trusted ConsensusState1 not found", &ibctmtypes.Misbehaviour{ - Header1: ibctmtypes.CreateTestHeader(testChainID, height+5, height+3, altTime, bothValSet, bothValSet, bothSigners), - Header2: ibctmtypes.CreateTestHeader(testChainID, height+5, height, suite.ctx.BlockTime(), bothValSet, valSet, bothSigners), + Header1: ibctmtypes.CreateTestHeader(testChainID, heightPlus5, heightPlus3, altTime, bothValSet, bothValSet, bothSigners), + Header2: ibctmtypes.CreateTestHeader(testChainID, heightPlus5, testClientHeight, suite.ctx.BlockTime(), bothValSet, valSet, bothSigners), ChainId: testChainID, ClientId: testClientID, }, @@ -368,8 +376,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "trusted ConsensusState2 not found", &ibctmtypes.Misbehaviour{ - Header1: ibctmtypes.CreateTestHeader(testChainID, height+5, height, altTime, bothValSet, valSet, bothSigners), - Header2: ibctmtypes.CreateTestHeader(testChainID, height+5, height+3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: ibctmtypes.CreateTestHeader(testChainID, heightPlus5, testClientHeight, altTime, bothValSet, valSet, bothSigners), + Header2: ibctmtypes.CreateTestHeader(testChainID, heightPlus5, heightPlus3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ChainId: testChainID, ClientId: testClientID, }, @@ -391,8 +399,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "client already frozen at earlier height", &ibctmtypes.Misbehaviour{ - Header1: ibctmtypes.CreateTestHeader(testChainID, height, height, altTime, bothValSet, bothValSet, bothSigners), - Header2: ibctmtypes.CreateTestHeader(testChainID, height, height, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: ibctmtypes.CreateTestHeader(testChainID, testClientHeight, testClientHeight, altTime, bothValSet, bothValSet, bothSigners), + Header2: ibctmtypes.CreateTestHeader(testChainID, testClientHeight, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ChainId: testChainID, ClientId: testClientID, }, @@ -411,8 +419,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "misbehaviour check failed", &ibctmtypes.Misbehaviour{ - Header1: ibctmtypes.CreateTestHeader(testChainID, height, height, altTime, bothValSet, bothValSet, bothSigners), - Header2: ibctmtypes.CreateTestHeader(testChainID, height, height, suite.ctx.BlockTime(), altValSet, bothValSet, altSigners), + Header1: ibctmtypes.CreateTestHeader(testChainID, testClientHeight, testClientHeight, altTime, bothValSet, bothValSet, bothSigners), + Header2: ibctmtypes.CreateTestHeader(testChainID, testClientHeight, testClientHeight, suite.ctx.BlockTime(), altValSet, bothValSet, altSigners), ChainId: testChainID, ClientId: testClientID, }, diff --git a/x/ibc/02-client/keeper/keeper.go b/x/ibc/02-client/keeper/keeper.go index d31899171..1e47e68a4 100644 --- a/x/ibc/02-client/keeper/keeper.go +++ b/x/ibc/02-client/keeper/keeper.go @@ -189,14 +189,15 @@ func (k Keeper) GetClientConsensusStateLTE(ctx sdk.Context, clientID string, max // GetSelfConsensusState introspects the (self) past historical info at a given height // and returns the expected consensus state at that height. -// TODO: Replace height with *clienttypes.Height once interfaces change +// For now, can only retrieve self consensus states for the current epoch func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) (exported.ConsensusState, bool) { - // TODO: check self chain-id against epoch number selfHeight, ok := height.(types.Height) if !ok { return nil, false } - if selfHeight.EpochNumber != 0 { + // check that height epoch matches chainID epoch + epoch := types.ParseChainID(ctx.ChainID()) + if epoch != height.GetEpochNumber() { return nil, false } histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, int64(selfHeight.EpochHeight)) @@ -214,6 +215,7 @@ func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) ( // ValidateSelfClient validates the client parameters for a client of the running chain // This function is only used to validate the client state the counterparty stores for this chain +// Client must be in same epoch as the executing chain func (k Keeper) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error { tmClient, ok := clientState.(*ibctmtypes.ClientState) if !ok { @@ -230,9 +232,15 @@ func (k Keeper) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientS ctx.ChainID(), tmClient.ChainId) } - // For now, assume epoch number is zero - // TODO: Retrieve epoch number from chain-id - selfHeight := types.NewHeight(0, uint64(ctx.BlockHeight())) + epoch := types.ParseChainID(ctx.ChainID()) + + // client must be in the same epoch as executing chain + if tmClient.LatestHeight.EpochNumber != epoch { + return sdkerrors.Wrapf(types.ErrInvalidClient, "client is not in the same epoch as the chain. expected epoch: %d, got: %d", + tmClient.LatestHeight.EpochNumber, epoch) + } + + selfHeight := types.NewHeight(epoch, uint64(ctx.BlockHeight())) if tmClient.LatestHeight.GT(selfHeight) { return sdkerrors.Wrapf(types.ErrInvalidClient, "client has LatestHeight %d greater than chain height %d", tmClient.LatestHeight, ctx.BlockHeight()) diff --git a/x/ibc/02-client/keeper/keeper_test.go b/x/ibc/02-client/keeper/keeper_test.go index 374e143c4..1297a6500 100644 --- a/x/ibc/02-client/keeper/keeper_test.go +++ b/x/ibc/02-client/keeper/keeper_test.go @@ -26,7 +26,8 @@ import ( ) const ( - testChainID = "gaiahub" + testChainID = "gaiahub-0" + testChainIDEpoch1 = "gaiahub-1" testClientID = "gaiachain" testClientID2 = "ethbridge" @@ -40,6 +41,7 @@ const ( ) var testClientHeight = types.NewHeight(0, 5) +var testClientHeightEpoch1 = types.NewHeight(1, 5) type KeeperTestSuite struct { suite.Suite @@ -83,10 +85,12 @@ func (suite *KeeperTestSuite) SetupTest() { pubKey, err := suite.privVal.GetPubKey() suite.Require().NoError(err) + testClientHeightMinus1 := types.NewHeight(0, height-1) + validator := tmtypes.NewValidator(pubKey, 1) suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) suite.valSetHash = suite.valSet.Hash() - suite.header = ibctmtypes.CreateTestHeader(testChainID, height, height-1, now2, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + suite.header = ibctmtypes.CreateTestHeader(testChainID, testClientHeight, testClientHeightMinus1, now2, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) suite.consensusState = ibctmtypes.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot([]byte("hash")), suite.valSetHash) var validators stakingtypes.Validators @@ -170,6 +174,11 @@ func (suite *KeeperTestSuite) TestValidateSelfClient() { ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.NewHeight(0, testClientHeight.EpochHeight+10), commitmenttypes.GetSDKSpecs(), false, false), false, }, + { + "invalid client epoch", + ibctmtypes.NewClientState(testChainIDEpoch1, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeightEpoch1, commitmenttypes.GetSDKSpecs(), false, false), + false, + }, { "invalid proof specs", ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, nil, false, false), @@ -291,7 +300,9 @@ func (suite KeeperTestSuite) TestConsensusStateHelpers() { nextState := ibctmtypes.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot([]byte("next")), suite.valSetHash) - header := ibctmtypes.CreateTestHeader(testClientID, height+5, height, suite.header.Header.Time.Add(time.Minute), + testClientHeightPlus5 := types.NewHeight(0, height+5) + + header := ibctmtypes.CreateTestHeader(testClientID, testClientHeightPlus5, testClientHeight, suite.header.Header.Time.Add(time.Minute), suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) // mock update functionality diff --git a/x/ibc/02-client/types/genesis_test.go b/x/ibc/02-client/types/genesis_test.go index e2df1ce17..29af455a9 100644 --- a/x/ibc/02-client/types/genesis_test.go +++ b/x/ibc/02-client/types/genesis_test.go @@ -53,7 +53,8 @@ func TestValidateGenesis(t *testing.T) { val := tmtypes.NewValidator(pubKey, 10) valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) - header := ibctmtypes.CreateTestHeader(chainID, height, height-1, now, valSet, valSet, []tmtypes.PrivValidator{privVal}) + heightMinus1 := types.NewHeight(0, height-1) + header := ibctmtypes.CreateTestHeader(chainID, clientHeight, heightMinus1, now, valSet, valSet, []tmtypes.PrivValidator{privVal}) testCases := []struct { name string diff --git a/x/ibc/02-client/types/height.go b/x/ibc/02-client/types/height.go index a5629c3b8..953859a64 100644 --- a/x/ibc/02-client/types/height.go +++ b/x/ibc/02-client/types/height.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "regexp" "strconv" "strings" @@ -12,6 +13,11 @@ import ( var _ exported.Height = (*Height)(nil) +// IsEpochFormat checks if a chainID is in the format required for parsing epochs +// The chainID must be in the form: `{chainID}-{version} +// 24-host may enforce stricter checks on chainID +var IsEpochFormat = regexp.MustCompile(`^.+[^-]-{1}[1-9][0-9]*$`).MatchString + // ZeroHeight is a helper function which returns an uninitialized height. func ZeroHeight() Height { return Height{} @@ -35,7 +41,7 @@ func (h Height) GetEpochHeight() uint64 { return h.EpochHeight } -/// Compare implements a method to compare two heights. When comparing two heights a, b +// Compare implements a method to compare two heights. When comparing two heights a, b // we can call a.Compare(b) which will return // -1 if a < b // 0 if a = b @@ -143,8 +149,42 @@ func ParseHeight(heightStr string) (Height, error) { return NewHeight(epochNumber, epochHeight), nil } -// GetSelfHeight is a utility function that returns self height given context -// TODO: Retrieve epoch-number from chain-id -func GetSelfHeight(ctx sdk.Context) Height { - return NewHeight(0, uint64(ctx.BlockHeight())) +// SetEpochNumber takes a chainID in valid epoch format and swaps the epoch number +// in the chainID with the given epoch number. +func SetEpochNumber(chainID string, epoch uint64) (string, error) { + if !IsEpochFormat(chainID) { + return "", sdkerrors.Wrapf( + sdkerrors.ErrInvalidChainID, "chainID is not in epoch format: %s", chainID, + ) + } + + splitStr := strings.Split(chainID, "-") + // swap out epoch number with given epoch + splitStr[len(splitStr)-1] = strconv.Itoa(int(epoch)) + return strings.Join(splitStr, "-"), nil +} + +// ParseChainID is a utility function that returns an epoch number from the given ChainID. +// ParseChainID attempts to parse a chain id in the format: `{chainID}-{version}` +// and return the epochnumber as a uint64. +// If the chainID is not in the expected format, a default epoch value of 0 is returned. +func ParseChainID(chainID string) uint64 { + if !IsEpochFormat(chainID) { + // chainID is not in epoch format, return 0 as default + return 0 + } + splitStr := strings.Split(chainID, "-") + epoch, err := strconv.ParseUint(splitStr[len(splitStr)-1], 10, 64) + // sanity check: error should always be nil since regex only allows numbers in last element + if err != nil { + panic(fmt.Sprintf("regex allowed non-number value as last split element for chainID: %s", chainID)) + } + return epoch +} + +// GetSelfHeight is a utility function that returns self height given context +// Epoch number is retrieved from ctx.ChainID() +func GetSelfHeight(ctx sdk.Context) Height { + epoch := ParseChainID(ctx.ChainID()) + return NewHeight(epoch, uint64(ctx.BlockHeight())) } diff --git a/x/ibc/02-client/types/height_test.go b/x/ibc/02-client/types/height_test.go index 146149b82..29e68bd21 100644 --- a/x/ibc/02-client/types/height_test.go +++ b/x/ibc/02-client/types/height_test.go @@ -93,3 +93,56 @@ func (suite *TypesTestSuite) TestMustParseHeight() { types.MustParseHeight("0-0") }) } + +func TestParseChainID(t *testing.T) { + cases := []struct { + chainID string + epoch uint64 + formatted bool + }{ + {"gaiamainnet-3", 3, true}, + {"gaia-mainnet-40", 40, true}, + {"gaiamainnet-3-39", 39, true}, + {"gaiamainnet--", 0, false}, + {"gaiamainnet-03", 0, false}, + {"gaiamainnet--4", 0, false}, + {"gaiamainnet-3.4", 0, false}, + {"gaiamainnet", 0, false}, + } + + for i, tc := range cases { + require.Equal(t, tc.formatted, types.IsEpochFormat(tc.chainID), "case %d does not match expected format", i) + + epoch := types.ParseChainID(tc.chainID) + require.Equal(t, tc.epoch, epoch, "case %d returns incorrect epoch", i) + } + +} + +func TestSetEpochNumber(t *testing.T) { + // Test SetEpochNumber + chainID, err := types.SetEpochNumber("gaiamainnet", 3) + require.Error(t, err, "invalid epoch format passed SetEpochNumber") + require.Equal(t, "", chainID, "invalid epoch format returned non-empty string on SetEpochNumber") + chainID = "gaiamainnet-3" + + chainID, err = types.SetEpochNumber(chainID, 4) + require.NoError(t, err, "valid epoch format failed SetEpochNumber") + require.Equal(t, "gaiamainnet-4", chainID, "valid epoch format returned incorrect string on SetEpochNumber") +} + +func (suite *TypesTestSuite) TestSelfHeight() { + ctx := suite.chainA.GetContext() + + // Test default epoch + ctx = ctx.WithChainID("gaiamainnet") + ctx = ctx.WithBlockHeight(10) + height := types.GetSelfHeight(ctx) + suite.Require().Equal(types.NewHeight(0, 10), height, "default self height failed") + + // Test successful epoch format + ctx = ctx.WithChainID("gaiamainnet-3") + ctx = ctx.WithBlockHeight(18) + height = types.GetSelfHeight(ctx) + suite.Require().Equal(types.NewHeight(3, 18), height, "valid self height failed") +} diff --git a/x/ibc/02-client/types/msgs_test.go b/x/ibc/02-client/types/msgs_test.go index f2c77f425..053a5a1ee 100644 --- a/x/ibc/02-client/types/msgs_test.go +++ b/x/ibc/02-client/types/msgs_test.go @@ -354,8 +354,10 @@ func (suite *TypesTestSuite) TestMarshalMsgSubmitMisbehaviour() { }, { "tendermint client", func() { - header1 := ibctmtypes.CreateTestHeader(suite.chainA.ChainID, suite.chainA.CurrentHeader.Height, suite.chainA.CurrentHeader.Height-1, suite.chainA.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) - header2 := ibctmtypes.CreateTestHeader(suite.chainA.ChainID, suite.chainA.CurrentHeader.Height, suite.chainA.CurrentHeader.Height-1, suite.chainA.CurrentHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + height := types.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height)) + heightMinus1 := types.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height)-1) + header1 := ibctmtypes.CreateTestHeader(suite.chainA.ChainID, height, heightMinus1, suite.chainA.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + header2 := ibctmtypes.CreateTestHeader(suite.chainA.ChainID, height, heightMinus1, suite.chainA.CurrentHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) misbehaviour := ibctmtypes.NewMisbehaviour("tendermint", suite.chainA.ChainID, header1, header2) msg, err = types.NewMsgSubmitMisbehaviour("tendermint", misbehaviour, suite.chainA.SenderAccount.GetAddress()) @@ -410,8 +412,10 @@ func (suite *TypesTestSuite) TestMsgSubmitMisbehaviour_ValidateBasic() { { "valid - tendermint misbehaviour", func() { - header1 := ibctmtypes.CreateTestHeader(suite.chainA.ChainID, suite.chainA.CurrentHeader.Height, suite.chainA.CurrentHeader.Height-1, suite.chainA.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) - header2 := ibctmtypes.CreateTestHeader(suite.chainA.ChainID, suite.chainA.CurrentHeader.Height, suite.chainA.CurrentHeader.Height-1, suite.chainA.CurrentHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + height := types.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height)) + heightMinus1 := types.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height)-1) + header1 := ibctmtypes.CreateTestHeader(suite.chainA.ChainID, height, heightMinus1, suite.chainA.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + header2 := ibctmtypes.CreateTestHeader(suite.chainA.ChainID, height, heightMinus1, suite.chainA.CurrentHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) misbehaviour := ibctmtypes.NewMisbehaviour("tendermint", suite.chainA.ChainID, header1, header2) msg, err = types.NewMsgSubmitMisbehaviour("tendermint", misbehaviour, suite.chainA.SenderAccount.GetAddress()) diff --git a/x/ibc/04-channel/client/utils/utils.go b/x/ibc/04-channel/client/utils/utils.go index 582924d29..975b72350 100644 --- a/x/ibc/04-channel/client/utils/utils.go +++ b/x/ibc/04-channel/client/utils/utils.go @@ -51,7 +51,6 @@ func queryPacketCommitmentABCI( return nil, sdkerrors.Wrapf(types.ErrPacketCommitmentNotFound, "portID (%s), channelID (%s), sequence (%d)", portID, channelID, sequence) } - // TODO: retrieve epoch number from chain-id return types.NewQueryPacketCommitmentResponse(portID, channelID, sequence, value, proofBz, proofHeight), nil } @@ -230,6 +229,5 @@ func queryNextSequenceRecvABCI(clientCtx client.Context, portID, channelID strin sequence := binary.BigEndian.Uint64(value) - // TODO: retrieve epoch number from chain-id return types.NewQueryNextSequenceReceiveResponse(portID, channelID, sequence, proofBz, proofHeight), nil } diff --git a/x/ibc/07-tendermint/types/header.go b/x/ibc/07-tendermint/types/header.go index 09481e245..cf8275d76 100644 --- a/x/ibc/07-tendermint/types/header.go +++ b/x/ibc/07-tendermint/types/header.go @@ -30,16 +30,12 @@ func (h Header) ConsensusState() *ConsensusState { // GetHeight returns the current height. It returns 0 if the tendermint // header is nil. -// -// TODO: return clienttypes.Height once interface changes func (h Header) GetHeight() exported.Height { if h.Header == nil { return clienttypes.ZeroHeight() } - - // Enforce clienttypes.Height to use 0 epoch number - // TODO: Retrieve epoch number from chain-id - return clienttypes.NewHeight(0, uint64(h.Header.Height)) + epoch := clienttypes.ParseChainID(h.Header.ChainID) + return clienttypes.NewHeight(epoch, uint64(h.Header.Height)) } // GetTime returns the current block timestamp. It returns a zero time if diff --git a/x/ibc/07-tendermint/types/misbehaviour_handle.go b/x/ibc/07-tendermint/types/misbehaviour_handle.go index 4890275c9..b6b3af3e0 100644 --- a/x/ibc/07-tendermint/types/misbehaviour_handle.go +++ b/x/ibc/07-tendermint/types/misbehaviour_handle.go @@ -62,7 +62,7 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( infractionHeight := tmMisbehaviour.GetHeight().GetEpochHeight() ageBlocks = int64(cs.LatestHeight.EpochHeight - infractionHeight) } else { - // if the misbehaviour is from a previous epoch, then the epoch-height + // if the misbehaviour is from a different epoch, then the epoch-height // of misbehaviour has no correlation with the current epoch-height // so we disable the block check by setting ageBlocks to 0 and only // rely on the time expiry check with ageDuration @@ -136,15 +136,22 @@ func checkMisbehaviourHeader( if currentTimestamp.Sub(consState.Timestamp) >= clientState.UnbondingPeriod { return sdkerrors.Wrapf( ErrUnbondingPeriodExpired, - "current timestamp minus the latest consensus state timestamp is greater than or equal to the unbonding period (%s >= %s)", + "current timestamp minus the latest consensus state timestamp is greater than or equal to the unbonding period (%d >= %d)", currentTimestamp.Sub(consState.Timestamp), clientState.UnbondingPeriod, ) } + chainID := clientState.GetChainID() + // If chainID is in epoch format, then set epoch number of chainID with the epoch number + // of the misbehaviour header + if clienttypes.IsEpochFormat(chainID) { + chainID, _ = clienttypes.SetEpochNumber(chainID, header.GetHeight().GetEpochNumber()) + } + // - ValidatorSet must have 2/3 similarity with trusted FromValidatorSet // - ValidatorSets on both headers are valid given the last trusted ValidatorSet if err := tmTrustedValset.VerifyCommitLightTrusting( - clientState.GetChainID(), tmCommit, clientState.TrustLevel.ToTendermint(), + chainID, tmCommit, clientState.TrustLevel.ToTendermint(), ); err != nil { return sdkerrors.Wrapf(clienttypes.ErrInvalidMisbehaviour, "validator set in header has too much change from trusted validator set: %v", err) } diff --git a/x/ibc/07-tendermint/types/misbehaviour_handle_test.go b/x/ibc/07-tendermint/types/misbehaviour_handle_test.go index 004df16a2..dde65cfbb 100644 --- a/x/ibc/07-tendermint/types/misbehaviour_handle_test.go +++ b/x/ibc/07-tendermint/types/misbehaviour_handle_test.go @@ -58,8 +58,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -74,8 +74,88 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ChainId: chainID, + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour with different trusted heights", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ChainId: chainID, + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour at a previous epoch", + types.NewClientState(chainIDEpoch1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: types.CreateTestHeader(chainIDEpoch0, height, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainIDEpoch0, height, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ChainId: chainIDEpoch0, + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour at a future epoch", + types.NewClientState(chainIDEpoch0, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: types.CreateTestHeader(chainIDEpoch0, clienttypes.NewHeight(1, 3), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainIDEpoch0, clienttypes.NewHeight(1, 3), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ChainId: chainIDEpoch0, + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour with trusted heights at a previous epoch", + types.NewClientState(chainIDEpoch1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: types.CreateTestHeader(chainIDEpoch1, clienttypes.NewHeight(1, 1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainIDEpoch1, clienttypes.NewHeight(1, 1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ChainId: chainIDEpoch1, + ClientId: chainID, + }, + suite.now, + true, + }, + { + "consensus state's valset hash different from misbehaviour should still pass", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + height, + &types.Misbehaviour{ + Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -90,46 +170,14 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader("ethermint", int64(height.EpochHeight), int64(height.EpochHeight), suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader("ethermint", int64(height.EpochHeight), int64(height.EpochHeight), suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader("ethermint", height, height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader("ethermint", height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: "ethermint", ClientId: chainID, }, suite.now, false, }, - { - "valid misbehavior misbehaviour with different trusted heights", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - heightMinus1, - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), - heightMinus3, - &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), - ChainId: chainID, - ClientId: chainID, - }, - suite.now, - true, - }, - { - "consensus state's valset hash different from misbehaviour should still pass", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), - height, - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), - height, - &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, bothValSet, suite.valSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), - ChainId: chainID, - ClientId: chainID, - }, - suite.now, - true, - }, { "invalid misbehavior misbehaviour with trusted height different from trusted consensus state", types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), @@ -138,8 +186,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -154,8 +202,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-3, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -170,8 +218,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -186,8 +234,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -213,8 +261,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -229,8 +277,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -245,8 +293,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -261,8 +309,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -277,8 +325,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, bothValSet, suite.valSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -293,8 +341,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, altValSet, bothValSet, altSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, height, suite.now, altValSet, bothValSet, altSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ChainId: chainID, ClientId: chainID, }, @@ -309,8 +357,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, bothValSet, bothValSet, bothSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), + Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), ChainId: chainID, ClientId: chainID, }, @@ -325,8 +373,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now, altValSet, bothValSet, altSigners), - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), + Header1: types.CreateTestHeader(chainID, height, height, suite.now, altValSet, bothValSet, altSigners), + Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), ChainId: chainID, ClientId: chainID, }, diff --git a/x/ibc/07-tendermint/types/misbehaviour_test.go b/x/ibc/07-tendermint/types/misbehaviour_test.go index e3ae74184..661b825ad 100644 --- a/x/ibc/07-tendermint/types/misbehaviour_test.go +++ b/x/ibc/07-tendermint/types/misbehaviour_test.go @@ -7,6 +7,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtypes "github.com/tendermint/tendermint/types" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" "github.com/cosmos/cosmos-sdk/x/ibc/exported" ibctestingmock "github.com/cosmos/cosmos-sdk/x/ibc/testing/mock" @@ -14,11 +15,11 @@ import ( func (suite *TendermintTestSuite) TestMisbehaviour() { signers := []tmtypes.PrivValidator{suite.privVal} - epochHeight := int64(height.EpochHeight) + heightMinus1 := clienttypes.NewHeight(0, height.EpochHeight-1) misbehaviour := &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, suite.valSet, suite.valSet, signers), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ChainId: chainID, ClientId: clientID, } @@ -50,6 +51,8 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { altSigners := []tmtypes.PrivValidator{altPrivVal} + heightMinus1 := clienttypes.NewHeight(0, height.EpochHeight-1) + testCases := []struct { name string misbehaviour *types.Misbehaviour @@ -60,7 +63,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "valid misbehaviour", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), ChainId: chainID, ClientId: clientID, }, @@ -83,7 +86,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "valid misbehaviour with different trusted headers", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-3, suite.now.Add(time.Minute), suite.valSet, bothValSet, signers), + Header2: types.CreateTestHeader(chainID, height, clienttypes.NewHeight(0, height.EpochHeight-3), suite.now.Add(time.Minute), suite.valSet, bothValSet, signers), ChainId: chainID, ClientId: clientID, }, @@ -93,7 +96,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { { "trusted height is 0 in Header1", &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, 0, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header1: types.CreateTestHeader(chainID, height, clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), Header2: suite.header, ChainId: chainID, ClientId: clientID, @@ -105,7 +108,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "trusted height is 0 in Header2", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, epochHeight, 0, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header2: types.CreateTestHeader(chainID, height, clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), ChainId: chainID, ClientId: clientID, }, @@ -115,7 +118,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { { "trusted valset is nil in Header1", &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), Header2: suite.header, ChainId: chainID, ClientId: clientID, @@ -127,7 +130,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "trusted valset is nil in Header2", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), ChainId: chainID, ClientId: clientID, }, @@ -138,7 +141,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "invalid client ID ", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, suite.valSet, suite.valSet, signers), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ChainId: chainID, ClientId: "GAIA", }, @@ -149,7 +152,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "wrong chainID on header1", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader("ethermint", epochHeight, epochHeight-1, suite.now, suite.valSet, suite.valSet, signers), + Header2: types.CreateTestHeader("ethermint", height, heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ChainId: "ethermint", ClientId: clientID, }, @@ -160,7 +163,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "wrong chainID on header2", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader("ethermint", epochHeight, epochHeight-1, suite.now, suite.valSet, suite.valSet, signers), + Header2: types.CreateTestHeader("ethermint", height, heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ChainId: chainID, ClientId: clientID, }, @@ -171,7 +174,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "wrong chainID in misbehaviour", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, int64(height.EpochHeight), int64(height.EpochHeight-1), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), ChainId: "ethermint", ClientId: clientID, }, @@ -182,7 +185,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "mismatched heights", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, 6, 4, suite.now, suite.valSet, suite.valSet, signers), + Header2: types.CreateTestHeader(chainID, clienttypes.NewHeight(0, 6), clienttypes.NewHeight(0, 4), suite.now, suite.valSet, suite.valSet, signers), ChainId: chainID, ClientId: clientID, }, @@ -203,7 +206,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { { "header 1 doesn't have 2/3 majority", &types.Misbehaviour{ - Header1: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, suite.valSet, bothSigners), + Header1: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), Header2: suite.header, ChainId: chainID, ClientId: clientID, @@ -226,7 +229,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "header 2 doesn't have 2/3 majority", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), ChainId: chainID, ClientId: clientID, }, @@ -248,7 +251,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "validators sign off on wrong commit", &types.Misbehaviour{ Header1: suite.header, - Header2: types.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: types.CreateTestHeader(chainID, height, heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), ChainId: chainID, ClientId: clientID, }, diff --git a/x/ibc/07-tendermint/types/tendermint_test.go b/x/ibc/07-tendermint/types/tendermint_test.go index 9f12c504b..fedb59663 100644 --- a/x/ibc/07-tendermint/types/tendermint_test.go +++ b/x/ibc/07-tendermint/types/tendermint_test.go @@ -20,6 +20,8 @@ import ( const ( chainID = "gaia" + chainIDEpoch0 = "gaia-epoch-0" + chainIDEpoch1 = "gaia-epoch-1" clientID = "gaiamainnet" trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 @@ -72,12 +74,12 @@ func (suite *TendermintTestSuite) SetupTest() { pubKey, err := suite.privVal.GetPubKey() suite.Require().NoError(err) - epochHeight := int64(height.EpochHeight) + heightMinus1 := clienttypes.NewHeight(0, height.EpochHeight-1) val := tmtypes.NewValidator(pubKey, 10) suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) suite.valsHash = suite.valSet.Hash() - suite.header = ibctmtypes.CreateTestHeader(chainID, epochHeight, epochHeight-1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + suite.header = ibctmtypes.CreateTestHeader(chainID, height, heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, Time: suite.now}) } diff --git a/x/ibc/07-tendermint/types/test_utils.go b/x/ibc/07-tendermint/types/test_utils.go index 3bab7794e..523327549 100644 --- a/x/ibc/07-tendermint/types/test_utils.go +++ b/x/ibc/07-tendermint/types/test_utils.go @@ -24,16 +24,17 @@ func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.Bl } // CreateTestHeader creates a mock header for testing only. -func CreateTestHeader(chainID string, height, trustedHeight int64, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *Header { +func CreateTestHeader(chainID string, height, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *Header { var ( valSet *tmproto.ValidatorSet trustedVals *tmproto.ValidatorSet ) vsetHash := tmValSet.Hash() + blockHeight := int64(height.EpochHeight) tmHeader := tmtypes.Header{ Version: version.Consensus{Block: 2, App: 2}, ChainID: chainID, - Height: height, + Height: blockHeight, Time: timestamp, LastBlockID: MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), @@ -49,8 +50,8 @@ func CreateTestHeader(chainID string, height, trustedHeight int64, timestamp tim hhash := tmHeader.Hash() blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) - voteSet := tmtypes.NewVoteSet(chainID, height, 1, tmproto.PrecommitType, tmValSet) - commit, err := tmtypes.MakeCommit(blockID, height, 1, voteSet, signers, timestamp) + voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) + commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) if err != nil { panic(err) } @@ -77,7 +78,7 @@ func CreateTestHeader(chainID string, height, trustedHeight int64, timestamp tim return &Header{ SignedHeader: &signedHeader, ValidatorSet: valSet, - TrustedHeight: clienttypes.NewHeight(0, uint64(trustedHeight)), + TrustedHeight: trustedHeight, TrustedValidators: trustedVals, } } diff --git a/x/ibc/07-tendermint/types/update.go b/x/ibc/07-tendermint/types/update.go index f3cc91185..f8c9482e3 100644 --- a/x/ibc/07-tendermint/types/update.go +++ b/x/ibc/07-tendermint/types/update.go @@ -21,7 +21,8 @@ import ( // It returns an error if: // - the client or header provided are not parseable to tendermint types // - the header is invalid -// - header height is less than or equal to the consensus state height +// - header height is less than or equal to the trusted header height +// - header epoch is not equal to trusted header epoch // - header valset commit verification fails // - header timestamp is past the trusting period in relation to the consensus state // - header timestamp is less than or equal to the consensus state timestamp @@ -32,6 +33,8 @@ import ( // If we are updating to a past height, a consensus state is created for that height to be persisted in client store // If we are updating to a future height, the consensus state is created and the client state is updated to reflect // the new latest height +// UpdateClient must only be used to update within a single epoch, thus header epoch number and trusted height's epoch +// number must be the same. To update to a new epoch, use a separate upgrade path // Tendermint client validity checking uses the bisection algorithm described // in the [Tendermint spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md). func (cs ClientState) CheckHeaderAndUpdateState( @@ -91,6 +94,16 @@ func checkValidity( return err } + // UpdateClient only accepts updates with a header at the same epoch + // as the trusted consensus state + if header.GetHeight().GetEpochNumber() != header.TrustedHeight.EpochNumber { + return sdkerrors.Wrapf( + ErrInvalidHeaderHeight, + "header height epoch %d does not match trusted header epoch %d", + header.GetHeight().GetEpochNumber(), header.TrustedHeight.EpochNumber, + ) + } + tmTrustedValidators, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) if err != nil { return sdkerrors.Wrap(err, "trusted validator set in not tendermint validator set type") @@ -110,7 +123,7 @@ func checkValidity( if header.GetHeight().LTE(header.TrustedHeight) { return sdkerrors.Wrapf( clienttypes.ErrInvalidHeader, - "header height ≤ consensus state height (%d ≤ %d)", header.GetHeight(), header.TrustedHeight, + "header height ≤ consensus state height (%s ≤ %s)", header.GetHeight(), header.TrustedHeight, ) } @@ -125,13 +138,24 @@ func checkValidity( Header: &trustedHeader, } + chainID := clientState.GetChainID() + // If chainID is in epoch format, then set epoch number of chainID with the epoch number + // of the header we are verifying + // This is useful if the update is at a previous epoch rather than an update to the latest epoch + // of the client. + // The chainID must be set correctly for the previous epoch before attempting verification. + // Updates for previous epochs are not supported if the chainID is not in epoch format. + if clienttypes.IsEpochFormat(chainID) { + chainID, _ = clienttypes.SetEpochNumber(chainID, header.GetHeight().GetEpochNumber()) + } + // Verify next header with the passed-in trustedVals // - asserts trusting period not passed // - assert header timestamp is not past the trusting period // - assert header timestamp is past latest stored consensus state timestamp // - assert that a TrustLevel proportion of TrustedValidators signed new Commit err = light.Verify( - clientState.GetChainID(), &signedHeader, + chainID, &signedHeader, tmTrustedValidators, tmSignedHeader, tmValidatorSet, clientState.TrustingPeriod, currentTimestamp, clientState.MaxClockDrift, clientState.TrustLevel.ToTendermint(), ) diff --git a/x/ibc/07-tendermint/types/update_test.go b/x/ibc/07-tendermint/types/update_test.go index 16d084de7..26717971f 100644 --- a/x/ibc/07-tendermint/types/update_test.go +++ b/x/ibc/07-tendermint/types/update_test.go @@ -26,6 +26,10 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { suite.Require().NoError(err) epochHeight := int64(height.EpochHeight) + + // create modified heights to use for test-cases + heightPlus1 := clienttypes.NewHeight(height.EpochNumber, height.EpochHeight+1) + heightMinus1 := clienttypes.NewHeight(height.EpochNumber, height.EpochHeight-1) heightMinus3 := clienttypes.NewHeight(height.EpochNumber, height.EpochHeight-3) heightPlus5 := clienttypes.NewHeight(height.EpochNumber, height.EpochHeight+5) @@ -54,7 +58,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = types.CreateTestHeader(chainID, heightPlus1, height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expPass: true, @@ -64,7 +68,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+5, epochHeight, suite.headerTime, bothValSet, suite.valSet, bothSigners) + newHeader = types.CreateTestHeader(chainID, heightPlus5, height, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now }, expPass: true, @@ -74,7 +78,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) - newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, bothValSet, bothValSet, bothSigners) + newHeader = types.CreateTestHeader(chainID, heightPlus1, height, suite.headerTime, bothValSet, bothValSet, bothSigners) currentTime = suite.now }, expPass: true, @@ -85,7 +89,17 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) consStateHeight = heightMinus3 - newHeader = types.CreateTestHeader(chainID, epochHeight-1, epochHeight-3, suite.headerTime, bothValSet, suite.valSet, bothSigners) + newHeader = types.CreateTestHeader(chainID, heightMinus1, heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) + currentTime = suite.now + }, + expPass: true, + }, + { + name: "successful update for a previous epoch", + setup: func() { + clientState = types.NewClientState(chainIDEpoch1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = types.CreateTestHeader(chainIDEpoch0, height, heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now }, expPass: true, @@ -95,7 +109,27 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader("ethermint", int64(height.EpochHeight+1), int64(height.EpochHeight), suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = types.CreateTestHeader("ethermint", heightPlus1, height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expPass: false, + }, + { + name: "unsuccessful update to a future epoch", + setup: func() { + clientState = types.NewClientState(chainIDEpoch0, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = types.CreateTestHeader(chainIDEpoch1, clienttypes.NewHeight(1, 1), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expPass: false, + }, + { + name: "unsuccessful update: header height epoch and trusted height epoch mismatch", + setup: func() { + clientState = types.NewClientState(chainIDEpoch1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = types.CreateTestHeader(chainIDEpoch1, clienttypes.NewHeight(1, 3), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expPass: false, @@ -105,7 +139,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, bothValSet, suite.valSet, bothSigners) + newHeader = types.CreateTestHeader(chainID, heightPlus1, height, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now }, expPass: false, @@ -115,7 +149,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) - newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, suite.valSet, bothValSet, signers) + newHeader = types.CreateTestHeader(chainID, heightPlus1, height, suite.headerTime, suite.valSet, bothValSet, signers) currentTime = suite.now }, expPass: false, @@ -125,7 +159,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+5, epochHeight, suite.headerTime, altValSet, suite.valSet, altSigners) + newHeader = types.CreateTestHeader(chainID, heightPlus5, height, suite.headerTime, altValSet, suite.valSet, altSigners) currentTime = suite.now }, expPass: false, @@ -135,7 +169,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+5, epochHeight, suite.headerTime, bothValSet, bothValSet, bothSigners) + newHeader = types.CreateTestHeader(chainID, heightPlus5, height, suite.headerTime, bothValSet, bothValSet, bothSigners) currentTime = suite.now }, expPass: false, @@ -145,7 +179,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = types.CreateTestHeader(chainID, heightPlus1, height, suite.headerTime, suite.valSet, suite.valSet, signers) // make current time pass trusting period from last timestamp on clientstate currentTime = suite.now.Add(trustingPeriod) }, @@ -156,7 +190,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers) + newHeader = types.CreateTestHeader(chainID, heightPlus1, height, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers) currentTime = suite.now }, expPass: false, @@ -166,7 +200,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.clientTime, suite.valSet, suite.valSet, signers) + newHeader = types.CreateTestHeader(chainID, heightPlus1, height, suite.clientTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expPass: false, @@ -176,7 +210,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = types.CreateTestHeader(chainID, heightPlus1, height, suite.headerTime, suite.valSet, suite.valSet, signers) // cause new header to fail validatebasic by changing commit height to mismatch header height newHeader.SignedHeader.Commit.Height = epochHeight - 1 currentTime = suite.now @@ -189,7 +223,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus5, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) // Make new header at height less than latest client state - newHeader = types.CreateTestHeader(chainID, epochHeight-1, epochHeight, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = types.CreateTestHeader(chainID, heightMinus1, height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expPass: false, diff --git a/x/ibc/09-localhost/types/client_state.go b/x/ibc/09-localhost/types/client_state.go index 1bbe60f53..709dba02a 100644 --- a/x/ibc/09-localhost/types/client_state.go +++ b/x/ibc/09-localhost/types/client_state.go @@ -75,9 +75,8 @@ func (cs *ClientState) CheckHeaderAndUpdateState( ) (exported.ClientState, exported.ConsensusState, error) { // use the chain ID from context since the localhost client is from the running chain (i.e self). cs.ChainId = ctx.ChainID() - // Hardcode 0 for epoch number for now - // TODO: Retrieve epoch number from chain-id - cs.Height = clienttypes.NewHeight(0, uint64(ctx.BlockHeight())) + epoch := clienttypes.ParseChainID(cs.ChainId) + cs.Height = clienttypes.NewHeight(epoch, uint64(ctx.BlockHeight())) return cs, nil, nil } diff --git a/x/ibc/client/query.go b/x/ibc/client/query.go index f0fdfc89b..c55d1e287 100644 --- a/x/ibc/client/query.go +++ b/x/ibc/client/query.go @@ -40,6 +40,6 @@ func QueryTendermintProof(clientCtx client.Context, key []byte) ([]byte, []byte, return nil, nil, clienttypes.Height{}, err } - // TODO: retrieve epoch number from chain-id - return res.Value, proofBz, clienttypes.NewHeight(0, uint64(res.Height)+1), nil + epoch := clienttypes.ParseChainID(clientCtx.ChainID) + return res.Value, proofBz, clienttypes.NewHeight(epoch, uint64(res.Height)+1), nil } diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index 3206fd6a5..225e07009 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -63,7 +63,9 @@ func (suite *IBCTestSuite) SetupTest() { val := tmtypes.NewValidator(pubKey, 10) valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) - suite.header = ibctmtypes.CreateTestHeader(chainID, height, height-1, now, valSet, valSet, []tmtypes.PrivValidator{privVal}) + clientHeightMinus1 := clienttypes.NewHeight(0, height-1) + + suite.header = ibctmtypes.CreateTestHeader(chainID, clientHeight, clientHeightMinus1, now, valSet, valSet, []tmtypes.PrivValidator{privVal}) suite.ctx = suite.app.BaseApp.NewContext(isCheckTx, tmproto.Header{}) } diff --git a/x/ibc/spec/01_concepts.md b/x/ibc/spec/01_concepts.md index d5b400230..5a6c8b380 100644 --- a/x/ibc/spec/01_concepts.md +++ b/x/ibc/spec/01_concepts.md @@ -38,7 +38,13 @@ given by the chain's chainID, and the epoch height given by the Tendermint block and resets its block-height, it is responsible for updating its chain-id to increment the epoch number. IBC Tendermint clients then verifies the epoch number against their `ChainId` and treat the `EpochHeight` as the Tendermint block-height. -TODO: Explain how to structure chain-id to make epoch-number parsable +Tendermint chains wishing to use epochs to maintain persistent IBC connections even across height-resetting upgrades must format their chain-ids +in the following manner: `{chainID}-{version}`. On any height-resetting upgrade, the chainID **MUST** be updated with a higher epoch number +than the previous value. + +Ex: +Before upgrade ChainID: `gaiamainnet-3` +After upgrade ChainID: `gaiamainnet-4` Clients that do not require epochs, such as the solo-machine client, simply hardcode `0` into the epoch number whenever they need to return an IBC height when implementing IBC interfaces and use the `EpochHeight` exclusively. diff --git a/x/ibc/testing/chain.go b/x/ibc/testing/chain.go index 452b2cdb8..72c2b527f 100644 --- a/x/ibc/testing/chain.go +++ b/x/ibc/testing/chain.go @@ -181,10 +181,12 @@ func (chain *TestChain) QueryProof(key []byte) ([]byte, clienttypes.Height) { proof, err := chain.App.AppCodec().MarshalBinaryBare(&merkleProof) require.NoError(chain.t, err) + epoch := clienttypes.ParseChainID(chain.ChainID) + // proof height + 1 is returned as the proof created corresponds to the height the proof // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it // have heights 1 above the IAVL tree. Thus we return proof height + 1 - return proof, clienttypes.NewHeight(0, uint64(res.Height)+1) + return proof, clienttypes.NewHeight(epoch, uint64(res.Height)+1) } // QueryClientStateProof performs and abci query for a client state