Skip to content

Commit

Permalink
Merge PR #5666: Use Tendermint lite client verification
Browse files Browse the repository at this point in the history
  • Loading branch information
AdityaSripal authored Feb 21, 2020
1 parent e2d5d83 commit c3bb696
Show file tree
Hide file tree
Showing 32 changed files with 1,895 additions and 1,208 deletions.
61 changes: 44 additions & 17 deletions x/ibc/02-client/keeper/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper_test
import (
"bytes"
"fmt"
"time"

tmtypes "github.com/tendermint/tendermint/types"

Expand Down Expand Up @@ -40,12 +41,12 @@ func (suite *KeeperTestSuite) TestCreateClient() {

if tc.expPanic {
suite.Require().Panics(func() {
clientState, err := ibctmtypes.Initialize(tc.clientID, tc.clientID, suite.consensusState, trustingPeriod, ubdPeriod)
clientState, err := ibctmtypes.Initialize(tc.clientID, trustingPeriod, ubdPeriod, suite.header)
suite.Require().NoError(err, "err on client state initialization")
suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)
}, "Msg %d didn't panic: %s", i, tc.msg)
} else {
clientState, err := ibctmtypes.Initialize(tc.clientID, tc.clientID, suite.consensusState, trustingPeriod, ubdPeriod)
clientState, err := ibctmtypes.Initialize(tc.clientID, trustingPeriod, ubdPeriod, suite.header)
if tc.expPass {
suite.Require().NoError(err, "errored on initialization")
suite.Require().NotNil(clientState, "valid test case %d failed: %s", i, tc.msg)
Expand All @@ -65,46 +66,67 @@ func (suite *KeeperTestSuite) TestCreateClient() {
}

func (suite *KeeperTestSuite) TestUpdateClient() {
// Must create header creation functions since suite.header gets recreated on each test case
createValidUpdateFn := func(s *KeeperTestSuite) ibctmtypes.Header {
return ibctmtypes.CreateTestHeader(testClientID, suite.header.Height+1, suite.header.Time.Add(time.Minute),
suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal})
}
createInvalidUpdateFn := func(s *KeeperTestSuite) ibctmtypes.Header {
return ibctmtypes.CreateTestHeader(testClientID, suite.header.Height-3, suite.header.Time.Add(time.Minute),
suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal})
}
var updateHeader ibctmtypes.Header

cases := []struct {
name string
malleate func() error
expPass bool
}{
{"valid update", func() error {
clientState, err := ibctmtypes.Initialize(testClientID, testClientID, suite.consensusState, trustingPeriod, ubdPeriod)
clientState, err := ibctmtypes.Initialize(testClientID, trustingPeriod, ubdPeriod, suite.header)
if err != nil {
return err
}
_, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)
updateHeader = createValidUpdateFn(suite)
return err
}, true},
{"client type not found", func() error {
updateHeader = createValidUpdateFn(suite)

return nil
}, false},
{"client type and header type mismatch", func() error {
suite.keeper.SetClientType(suite.ctx, testClientID, invalidClientType)
updateHeader = createValidUpdateFn(suite)

return nil
}, false},
{"client state not found", func() error {
suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint)
updateHeader = createValidUpdateFn(suite)

return nil
}, false},
{"frozen client", func() error {
clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LatestHeight: 10}
clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LastHeader: suite.header}
suite.keeper.SetClientState(suite.ctx, clientState)
suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint)
updateHeader = createValidUpdateFn(suite)

return nil
}, false},
{"invalid header", func() error {
clientState, err := ibctmtypes.Initialize(testClientID, testClientID, suite.consensusState, trustingPeriod, ubdPeriod)
clientState, err := ibctmtypes.Initialize(testClientID, trustingPeriod, ubdPeriod, suite.header)
if err != nil {
return err
}
_, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)
if err != nil {
return err
}
suite.header.Height = suite.ctx.BlockHeight() - 1
updateHeader = createInvalidUpdateFn(suite)

return nil
}, false},
}
Expand All @@ -117,27 +139,32 @@ func (suite *KeeperTestSuite) TestUpdateClient() {
err := tc.malleate()
suite.Require().NoError(err)

err = suite.keeper.UpdateClient(suite.ctx, testClientID, suite.header)
suite.ctx = suite.ctx.WithBlockTime(updateHeader.Time.Add(time.Minute))

err = suite.keeper.UpdateClient(suite.ctx, testClientID, updateHeader)

if tc.expPass {
suite.Require().NoError(err, err)

expConsensusState := ibctmtypes.ConsensusState{
Timestamp: suite.header.Time,
Root: commitment.NewRoot(suite.header.AppHash),
ValidatorSet: suite.header.ValidatorSet,
Height: uint64(updateHeader.Height),
Timestamp: updateHeader.Time,
Root: commitment.NewRoot(updateHeader.AppHash),
ValidatorSet: updateHeader.ValidatorSet,
}

clientState, found := suite.keeper.GetClientState(suite.ctx, testClientID)
suite.Require().True(found, "valid test case %d failed: %s", i, tc.name)

consensusState, found := suite.keeper.GetClientConsensusState(suite.ctx, testClientID, uint64(suite.header.GetHeight()))
consensusState, found := suite.keeper.GetClientConsensusState(suite.ctx, testClientID, uint64(updateHeader.GetHeight()))
suite.Require().True(found, "valid test case %d failed: %s", i, tc.name)
tmConsState, ok := consensusState.(ibctmtypes.ConsensusState)
suite.Require().True(ok, "consensus state is not a tendermint consensus state")
// recalculate cached totalVotingPower field for equality check
tmConsState.ValidatorSet.TotalVotingPower()

suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
suite.Require().Equal(suite.header.GetHeight(), clientState.GetLatestHeight(), "client state height not updated correctly on case %s", tc.name)
suite.Require().Equal(updateHeader.GetHeight(), clientState.GetLatestHeight(), "client state height not updated correctly on case %s", tc.name)
suite.Require().Equal(expConsensusState, consensusState, "consensus state should have been updated on case %s", tc.name)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
Expand Down Expand Up @@ -181,7 +208,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() {
},
func() error {
suite.consensusState.ValidatorSet = bothValSet
clientState, err := ibctmtypes.Initialize(testClientID, testClientID, suite.consensusState, trustingPeriod, ubdPeriod)
clientState, err := ibctmtypes.Initialize(testClientID, trustingPeriod, ubdPeriod, suite.header)
if err != nil {
return err
}
Expand All @@ -201,7 +228,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() {
},
func() error {
suite.consensusState.ValidatorSet = bothValSet
clientState, err := ibctmtypes.Initialize(testClientID, testClientID, suite.consensusState, trustingPeriod, ubdPeriod)
clientState, err := ibctmtypes.Initialize(testClientID, trustingPeriod, ubdPeriod, suite.header)
if err != nil {
return err
}
Expand All @@ -226,7 +253,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() {
ClientID: testClientID,
},
func() error {
clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LatestHeight: 10}
clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LastHeader: suite.header}
suite.keeper.SetClientState(suite.ctx, clientState)
return nil
},
Expand All @@ -241,7 +268,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() {
ClientID: testClientID,
},
func() error {
clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LatestHeight: 10}
clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LastHeader: suite.header}
suite.keeper.SetClientState(suite.ctx, clientState)
return nil
},
Expand All @@ -256,7 +283,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() {
ClientID: testClientID,
},
func() error {
clientState, err := ibctmtypes.Initialize(testClientID, testClientID, suite.consensusState, trustingPeriod, ubdPeriod)
clientState, err := ibctmtypes.Initialize(testClientID, trustingPeriod, ubdPeriod, suite.header)
if err != nil {
return err
}
Expand Down
16 changes: 9 additions & 7 deletions x/ibc/02-client/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.privVal = tmtypes.NewMockPV()
validator := tmtypes.NewValidator(suite.privVal.GetPubKey(), 1)
suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{validator})
suite.header = ibctmtypes.CreateTestHeader(testClientID, testClientHeight+2, now2, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal})
suite.header = ibctmtypes.CreateTestHeader(testClientID, testClientHeight, now2, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal})
suite.consensusState = ibctmtypes.ConsensusState{
Height: testClientHeight,
Timestamp: suite.now,
Expand All @@ -82,7 +82,7 @@ func TestKeeperTestSuite(t *testing.T) {
}

func (suite *KeeperTestSuite) TestSetClientState() {
clientState := ibctmtypes.NewClientState(testClientID, testClientID, trustingPeriod, ubdPeriod, testClientHeight, suite.now)
clientState := ibctmtypes.NewClientState(testClientID, trustingPeriod, ubdPeriod, ibctmtypes.Header{})
suite.keeper.SetClientState(suite.ctx, clientState)

retrievedState, found := suite.keeper.GetClientState(suite.ctx, testClientID)
Expand Down Expand Up @@ -113,9 +113,9 @@ func (suite *KeeperTestSuite) TestSetClientConsensusState() {

func (suite KeeperTestSuite) TestGetAllClients() {
expClients := []exported.ClientState{
ibctmtypes.NewClientState(testClientID2, testClientID, trustingPeriod, ubdPeriod, testClientHeight, suite.now),
ibctmtypes.NewClientState(testClientID3, testClientID, trustingPeriod, ubdPeriod, testClientHeight, suite.now),
ibctmtypes.NewClientState(testClientID, testClientID, trustingPeriod, ubdPeriod, testClientHeight, suite.now),
ibctmtypes.NewClientState(testClientID2, trustingPeriod, ubdPeriod, ibctmtypes.Header{}),
ibctmtypes.NewClientState(testClientID3, trustingPeriod, ubdPeriod, ibctmtypes.Header{}),
ibctmtypes.NewClientState(testClientID, trustingPeriod, ubdPeriod, ibctmtypes.Header{}),
}

for i := range expClients {
Expand Down Expand Up @@ -155,7 +155,7 @@ func (suite KeeperTestSuite) TestGetConsensusState() {

func (suite KeeperTestSuite) TestConsensusStateHelpers() {
// initial setup
clientState, _ := ibctmtypes.Initialize(testClientID, testClientID, suite.consensusState, trustingPeriod, ubdPeriod)
clientState, _ := ibctmtypes.Initialize(testClientID, trustingPeriod, ubdPeriod, suite.header)
suite.keeper.SetClientState(suite.ctx, clientState)
suite.keeper.SetClientConsensusState(suite.ctx, testClientID, testClientHeight, suite.consensusState)

Expand All @@ -166,9 +166,11 @@ func (suite KeeperTestSuite) TestConsensusStateHelpers() {
ValidatorSet: suite.valSet,
}

header := ibctmtypes.CreateTestHeader(testClientID, testClientHeight+5, suite.header.Time.Add(time.Minute), suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal})

// mock update functionality
clientState.LastHeader = header
suite.keeper.SetClientConsensusState(suite.ctx, testClientID, testClientHeight+5, nextState)
clientState.LatestHeight += 5
suite.keeper.SetClientState(suite.ctx, clientState)

latest, ok := suite.keeper.GetLatestClientConsensusState(suite.ctx, testClientID)
Expand Down
45 changes: 32 additions & 13 deletions x/ibc/03-connection/keeper/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ func (k Keeper) ConnOpenTry(
ctx sdk.Context,
connectionID string, // desiredIdentifier
counterparty types.Counterparty, // counterpartyConnectionIdentifier, counterpartyPrefix and counterpartyClientIdentifier
clientID string,
counterpartyVersions []string,
proofInit commitment.ProofI,
proofConsensus commitment.ProofI,
proofHeight uint64,
consensusHeight uint64,
clientID string, // clientID of chainA
counterpartyVersions []string, // supported versions of chain A
proofInit commitment.ProofI, // proof that chainA stored connectionEnd in state (on ConnOpenInit)
proofConsensus commitment.ProofI, // proof that chainA stored chainB's consensus state at consensus height
proofHeight uint64, // height at which relayer constructs proof of A storing connectionEnd in state
consensusHeight uint64, // latest height of chain B which chain A has stored in its chain B client
) error {
if consensusHeight > uint64(ctx.BlockHeight()) {
return sdkerrors.Wrap(ibctypes.ErrInvalidHeight, "invalid consensus height")
Expand All @@ -78,19 +78,24 @@ func (k Keeper) ConnOpenTry(
// connection defines chain B's ConnectionEnd
connection := types.NewConnectionEnd(exported.UNINITIALIZED, clientID, counterparty, []string{version})

// Check that ChainA committed expectedConnectionEnd to its state
if err := k.VerifyConnectionState(
ctx, connection, proofHeight, proofInit, counterparty.ConnectionID,
expectedConnection,
); err != nil {
return err
}

// Check that ChainA stored the correct ConsensusState of chainB at the given consensusHeight
if err := k.VerifyClientConsensusState(
ctx, connection, consensusHeight, proofConsensus, expectedConsensusState,
); err != nil {
return err
}

// If connection already exists for connectionID, ensure that the existing connection's counterparty
// is chainA and connection is on INIT stage
// Check that existing connection version is on desired version of current handshake
previousConnection, found := k.GetConnection(ctx, connectionID)
if found && !(previousConnection.State == exported.INIT &&
previousConnection.Counterparty.ConnectionID == counterparty.ConnectionID &&
Expand All @@ -101,6 +106,7 @@ func (k Keeper) ConnOpenTry(
return sdkerrors.Wrap(types.ErrInvalidConnection, "cannot relay connection attempt")
}

// Set connection state to TRYOPEN and store in chainB state
connection.State = exported.TRYOPEN
if err := k.addConnectionToClient(ctx, clientID, connectionID); err != nil {
return sdkerrors.Wrap(err, "cannot relay connection attempt")
Expand All @@ -118,34 +124,40 @@ func (k Keeper) ConnOpenTry(
func (k Keeper) ConnOpenAck(
ctx sdk.Context,
connectionID string,
version string,
proofTry commitment.ProofI,
proofConsensus commitment.ProofI,
proofHeight uint64,
consensusHeight uint64,
version string, // version that ChainB chose in ConnOpenTry
proofTry commitment.ProofI, // proof that connectionEnd was added to ChainB state in ConnOpenTry
proofConsensus commitment.ProofI, // proof that chainB has stored ConsensusState of chainA on its client
proofHeight uint64, // height that relayer constructed proofTry
consensusHeight uint64, // latest height of chainA that chainB has stored on its chainA client
) error {
// Check that chainB client hasn't stored invalid height
if consensusHeight > uint64(ctx.BlockHeight()) {
return sdkerrors.Wrap(ibctypes.ErrInvalidHeight, "invalid consensus height")
}

// Retrieve connection
connection, found := k.GetConnection(ctx, connectionID)
if !found {
return sdkerrors.Wrap(types.ErrConnectionNotFound, "cannot relay ACK of open attempt")
}

// Check connection on ChainA is on correct state: INIT
if connection.State != exported.INIT {
return sdkerrors.Wrapf(
types.ErrInvalidConnectionState,
"connection state is not INIT (got %s)", connection.State.String(),
)
}

// Check that ChainB's proposed version is one of chainA's accepted versions
if types.LatestVersion(connection.Versions) != version {
return sdkerrors.Wrapf(
ibctypes.ErrInvalidVersion,
"connection version does't match provided one (%s ≠ %s)", types.LatestVersion(connection.Versions), version,
)
}

// Retrieve chainA's consensus state at consensusheight
expectedConsensusState, found := k.clientKeeper.GetSelfConsensusState(ctx, consensusHeight)
if !found {
return clienttypes.ErrConsensusStateNotFound
Expand All @@ -155,19 +167,22 @@ func (k Keeper) ConnOpenAck(
expectedCounterparty := types.NewCounterparty(connection.ClientID, connectionID, prefix)
expectedConnection := types.NewConnectionEnd(exported.TRYOPEN, connection.Counterparty.ClientID, expectedCounterparty, []string{version})

// Ensure that ChainB stored expected connectionEnd in its state during ConnOpenTry
if err := k.VerifyConnectionState(
ctx, connection, proofHeight, proofTry, connection.Counterparty.ConnectionID,
expectedConnection,
); err != nil {
return err
}

// Ensure that ChainB has stored the correct ConsensusState for chainA at the consensusHeight
if err := k.VerifyClientConsensusState(
ctx, connection, consensusHeight, proofConsensus, expectedConsensusState,
); err != nil {
return err
}

// Update connection state to Open
connection.State = exported.OPEN
connection.Versions = []string{version}
k.SetConnection(ctx, connectionID, connection)
Expand All @@ -182,14 +197,16 @@ func (k Keeper) ConnOpenAck(
func (k Keeper) ConnOpenConfirm(
ctx sdk.Context,
connectionID string,
proofAck commitment.ProofI,
proofHeight uint64,
proofAck commitment.ProofI, // proof that connection opened on ChainA during ConnOpenAck
proofHeight uint64, // height that relayer constructed proofAck
) error {
// Retrieve connection
connection, found := k.GetConnection(ctx, connectionID)
if !found {
return sdkerrors.Wrap(types.ErrConnectionNotFound, "cannot relay ACK of open attempt")
}

// Check that connection state on ChainB is on state: TRYOPEN
if connection.State != exported.TRYOPEN {
return sdkerrors.Wrapf(
types.ErrInvalidConnectionState,
Expand All @@ -201,13 +218,15 @@ func (k Keeper) ConnOpenConfirm(
expectedCounterparty := types.NewCounterparty(connection.ClientID, connectionID, prefix)
expectedConnection := types.NewConnectionEnd(exported.OPEN, connection.Counterparty.ClientID, expectedCounterparty, connection.Versions)

// Check that connection on ChainA is open
if err := k.VerifyConnectionState(
ctx, connection, proofHeight, proofAck, connection.Counterparty.ConnectionID,
expectedConnection,
); err != nil {
return err
}

// Update ChainB's connection to Open
connection.State = exported.OPEN
k.SetConnection(ctx, connectionID, connection)
k.Logger(ctx).Info(fmt.Sprintf("connection %s state updated: TRYOPEN -> OPEN ", connectionID))
Expand Down
Loading

0 comments on commit c3bb696

Please sign in to comment.