From 8de42f02935eeb4d41365b6cda675ab87023da8f Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Wed, 3 Apr 2024 19:27:07 +0300 Subject: [PATCH] tests: refactor solomachine tests to use light client module as entrypoint. (#6038) * tests: refactor tests to use light client module as entrypoint. * Rename solomachine clients identifiers. * ref: clean up previous tests, add two more for LatestHeight, UpdateStatus. * ref: add verifying funcs. * rm: old tests. * nits: test names * Apply suggestions from code review Co-authored-by: Carlos Rodriguez * Update modules/light-clients/06-solomachine/solomachine_test.go Co-authored-by: Carlos Rodriguez * chore: rm one of the consts * review: address feedback. --------- Co-authored-by: Charly Co-authored-by: Carlos Rodriguez --- .../06-solomachine/client_state_test.go | 721 +------ .../light_client_module_test.go | 1690 ++++++++++++++++- modules/light-clients/06-solomachine/proof.go | 2 +- .../06-solomachine/proposal_handle_test.go | 83 - .../06-solomachine/solomachine_test.go | 4 +- .../06-solomachine/update_test.go | 546 ------ 6 files changed, 1604 insertions(+), 1442 deletions(-) delete mode 100644 modules/light-clients/06-solomachine/proposal_handle_test.go delete mode 100644 modules/light-clients/06-solomachine/update_test.go diff --git a/modules/light-clients/06-solomachine/client_state_test.go b/modules/light-clients/06-solomachine/client_state_test.go index 8bd1fe93891..66f025e2786 100644 --- a/modules/light-clients/06-solomachine/client_state_test.go +++ b/modules/light-clients/06-solomachine/client_state_test.go @@ -3,15 +3,8 @@ package solomachine_test import ( "bytes" - sdk "github.com/cosmos/cosmos-sdk/types" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v8/modules/core/exported" solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" ibctesting "github.com/cosmos/ibc-go/v8/testing" - ibcmock "github.com/cosmos/ibc-go/v8/testing/mock" ) const ( @@ -21,19 +14,7 @@ const ( testPortID = "testportid" ) -func (suite *SoloMachineTestSuite) TestStatus() { - clientState := suite.solomachine.ClientState() - // solo machine discards arguments - status := clientState.Status(suite.chainA.GetContext(), nil, nil) - suite.Require().Equal(exported.Active, status) - - // freeze solo machine - clientState.IsFrozen = true - status = clientState.Status(suite.chainA.GetContext(), nil, nil) - suite.Require().Equal(exported.Frozen, status) -} - -func (suite *SoloMachineTestSuite) TestClientStateValidateBasic() { +func (suite *SoloMachineTestSuite) TestClientStateValidate() { // test singlesig and multisig public keys for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { @@ -90,464 +71,6 @@ func (suite *SoloMachineTestSuite) TestClientStateValidateBasic() { } } -func (suite *SoloMachineTestSuite) TestVerifyMembership() { - // test singlesig and multisig public keys - for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - - var ( - clientState *solomachine.ClientState - path exported.Path - proof []byte - testingPath *ibctesting.Path - signBytes solomachine.SignBytes - err error - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", - func() {}, - true, - }, - { - "success: client state verification", - func() { - clientState = sm.ClientState() - clientStateBz, err := suite.chainA.Codec.Marshal(clientState) - suite.Require().NoError(err) - - path = sm.GetClientStatePath(counterpartyClientIdentifier) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.GetHeight().GetRevisionHeight(), - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: clientStateBz, - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "success: consensus state verification", - func() { - clientState = sm.ClientState() - consensusState := clientState.ConsensusState - consensusStateBz, err := suite.chainA.Codec.Marshal(consensusState) - suite.Require().NoError(err) - - path = sm.GetConsensusStatePath(counterpartyClientIdentifier, clienttypes.NewHeight(0, 1)) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.Sequence, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: consensusStateBz, - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "success: connection state verification", - func() { - testingPath.SetupConnections() - - connectionEnd, found := suite.chainA.GetSimApp().IBCKeeper.ConnectionKeeper.GetConnection(suite.chainA.GetContext(), ibctesting.FirstConnectionID) - suite.Require().True(found) - - connectionEndBz, err := suite.chainA.Codec.Marshal(&connectionEnd) - suite.Require().NoError(err) - - path = sm.GetConnectionStatePath(ibctesting.FirstConnectionID) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.Sequence, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: connectionEndBz, - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "success: channel state verification", - func() { - testingPath.SetupConnections() - suite.coordinator.CreateMockChannels(testingPath) - - channelEnd, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetChannel(suite.chainA.GetContext(), ibctesting.MockPort, ibctesting.FirstChannelID) - suite.Require().True(found) - - channelEndBz, err := suite.chainA.Codec.Marshal(&channelEnd) - suite.Require().NoError(err) - - path = sm.GetChannelStatePath(ibctesting.MockPort, ibctesting.FirstChannelID) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.Sequence, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: channelEndBz, - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "success: next sequence recv verification", - func() { - testingPath.SetupConnections() - suite.coordinator.CreateMockChannels(testingPath) - - nextSeqRecv, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetNextSequenceRecv(suite.chainA.GetContext(), ibctesting.MockPort, ibctesting.FirstChannelID) - suite.Require().True(found) - - path = sm.GetNextSequenceRecvPath(ibctesting.MockPort, ibctesting.FirstChannelID) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.Sequence, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: sdk.Uint64ToBigEndian(nextSeqRecv), - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "success: packet commitment verification", - func() { - packet := channeltypes.NewPacket( - ibctesting.MockPacketData, - 1, - ibctesting.MockPort, - ibctesting.FirstChannelID, - ibctesting.MockPort, - ibctesting.FirstChannelID, - clienttypes.NewHeight(0, 10), - 0, - ) - - commitmentBz := channeltypes.CommitPacket(suite.chainA.Codec, packet) - path = sm.GetPacketCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.Sequence, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: commitmentBz, - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "success: packet acknowledgement verification", - func() { - path = sm.GetPacketAcknowledgementPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.Sequence, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: ibctesting.MockAcknowledgement, - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "success: packet receipt verification", - func() { - path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.Sequence, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: []byte{byte(1)}, // packet receipt is stored as a single byte - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "invalid path type - empty", - func() { - path = ibcmock.KeyPath{} - }, - false, - }, - { - "malformed proof fails to unmarshal", - func() { - path = sm.GetClientStatePath(counterpartyClientIdentifier) - proof = []byte("invalid proof") - }, - false, - }, - { - "consensus state timestamp is greater than signature", - func() { - consensusState := &solomachine.ConsensusState{ - Timestamp: sm.Time + 1, - PublicKey: sm.ConsensusState().PublicKey, - } - - clientState = solomachine.NewClientState(sm.Sequence, consensusState) - }, - false, - }, - { - "signature data is nil", - func() { - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: nil, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - false, - }, - { - "consensus state public key is nil", - func() { - clientState.ConsensusState.PublicKey = nil - }, - false, - }, - { - "malformed signature data fails to unmarshal", - func() { - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: []byte("invalid signature data"), - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - false, - }, - { - "proof is nil", - func() { - proof = nil - }, - false, - }, - { - "proof verification failed", - func() { - signBytes.Data = []byte("invalid membership data value") - }, - false, - }, - { - "empty path", - func() { - path = commitmenttypes.MerklePath{} - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - testingPath = ibctesting.NewPath(suite.chainA, suite.chainB) - - clientState = sm.ClientState() - - path = commitmenttypes.NewMerklePath("ibc", "solomachine") - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.GetHeight().GetRevisionHeight(), - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: []byte("solomachine"), - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - - tc.malleate() - - var expSeq uint64 - if clientState.ConsensusState != nil { - expSeq = clientState.Sequence + 1 - } - - err = clientState.VerifyMembership( - suite.chainA.GetContext(), suite.store, suite.chainA.Codec, - clienttypes.ZeroHeight(), 0, 0, // solomachine does not check delay periods - proof, path, signBytes.Data, - ) - - if tc.expPass { - suite.Require().NoError(err) - suite.Require().Equal(expSeq, clientState.Sequence) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %s", suite.GetSequenceFromStore(), tc.name) - } else { - suite.Require().Error(err) - } - }) - } - } -} - func (suite *SoloMachineTestSuite) TestSignBytesMarshalling() { sm := suite.solomachine path := []byte("solomachine") @@ -575,245 +98,3 @@ func (suite *SoloMachineTestSuite) TestSignBytesMarshalling() { suite.Require().True(bytes.Equal(signBzNil, signBzEmptyArray)) } - -func (suite *SoloMachineTestSuite) TestVerifyNonMembership() { - // test singlesig and multisig public keys - for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - - var ( - clientState *solomachine.ClientState - path exported.Path - proof []byte - signBytes solomachine.SignBytes - err error - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", - func() {}, - true, - }, - { - "success: packet receipt absence verification", - func() { - path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.GetHeight().GetRevisionHeight(), - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: nil, - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - true, - }, - { - "invalid path type", - func() { - path = ibcmock.KeyPath{} - }, - false, - }, - { - "malformed proof fails to unmarshal", - func() { - path = sm.GetClientStatePath(counterpartyClientIdentifier) - proof = []byte("invalid proof") - }, - false, - }, - { - "consensus state timestamp is greater than signature", - func() { - consensusState := &solomachine.ConsensusState{ - Timestamp: sm.Time + 1, - PublicKey: sm.ConsensusState().PublicKey, - } - - clientState = solomachine.NewClientState(sm.Sequence, consensusState) - }, - false, - }, - { - "signature data is nil", - func() { - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: nil, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - false, - }, - { - "consensus state public key is nil", - func() { - clientState.ConsensusState.PublicKey = nil - }, - false, - }, - { - "malformed signature data fails to unmarshal", - func() { - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: []byte("invalid signature data"), - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - false, - }, - { - "proof is nil", - func() { - proof = nil - }, - false, - }, - { - "proof verification failed", - func() { - signBytes.Data = []byte("invalid non-membership data value") - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - clientState = sm.ClientState() - - path = commitmenttypes.NewMerklePath("ibc", "solomachine") - merklePath, ok := path.(commitmenttypes.MerklePath) - suite.Require().True(ok) - key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store - suite.Require().NoError(err) - signBytes = solomachine.SignBytes{ - Sequence: sm.GetHeight().GetRevisionHeight(), - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: key, - Data: nil, - } - - signBz, err := suite.chainA.Codec.Marshal(&signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - - signatureDoc := &solomachine.TimestampedSignatureData{ - SignatureData: sig, - Timestamp: sm.Time, - } - - proof, err = suite.chainA.Codec.Marshal(signatureDoc) - suite.Require().NoError(err) - - tc.malleate() - - var expSeq uint64 - if clientState.ConsensusState != nil { - expSeq = clientState.Sequence + 1 - } - - err = clientState.VerifyNonMembership( - suite.chainA.GetContext(), suite.store, suite.chainA.Codec, - clienttypes.ZeroHeight(), 0, 0, // solomachine does not check delay periods - proof, path, - ) - - if tc.expPass { - suite.Require().NoError(err) - suite.Require().Equal(expSeq, clientState.Sequence) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %s", suite.GetSequenceFromStore(), tc.name) - } else { - suite.Require().Error(err) - } - }) - } - } -} - -func (suite *SoloMachineTestSuite) TestGetTimestampAtHeight() { - tmPath := ibctesting.NewPath(suite.chainA, suite.chainB) - tmPath.SetupClients() - // Single setup for all test cases. - suite.SetupTest() - - testCases := []struct { - name string - clientState *solomachine.ClientState - height exported.Height - expValue uint64 - expPass bool - }{ - { - name: "get timestamp at height exists", - clientState: suite.solomachine.ClientState(), - height: clienttypes.NewHeight(0, suite.solomachine.ClientState().Sequence), - expValue: suite.solomachine.ClientState().ConsensusState.Timestamp, - expPass: true, - }, - } - - for i, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - ctx := suite.chainA.GetContext() - - ts, err := tc.clientState.GetTimestampAtHeight( - ctx, suite.store, suite.chainA.Codec, tc.height, - ) - - suite.Require().Equal(tc.expValue, ts) - - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - }) - } -} diff --git a/modules/light-clients/06-solomachine/light_client_module_test.go b/modules/light-clients/06-solomachine/light_client_module_test.go index 8db932a3579..2c3777e01e7 100644 --- a/modules/light-clients/06-solomachine/light_client_module_test.go +++ b/modules/light-clients/06-solomachine/light_client_module_test.go @@ -1,21 +1,141 @@ package solomachine_test import ( - fmt "fmt" + "fmt" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" "github.com/cosmos/ibc-go/v8/modules/core/exported" solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ibctesting "github.com/cosmos/ibc-go/v8/testing" + ibcmock "github.com/cosmos/ibc-go/v8/testing/mock" ) const ( - smClientID = "06-solomachine-100" - wasmClientID = "08-wasm-0" + unusedSmClientID = "06-solomachine-999" + wasmClientID = "08-wasm-0" ) +func (suite *SoloMachineTestSuite) TestStatus() { + var ( + clientState *solomachine.ClientState + clientID string + ) + + testCases := []struct { + name string + malleate func() + expStatus exported.Status + }{ + { + "client is active", + func() {}, + exported.Active, + }, + { + "client is frozen", + func() { + clientState = solomachine.NewClientState(0, &solomachine.ConsensusState{}) + clientState.IsFrozen = true + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + exported.Frozen, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + exported.Unknown, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + clientID = suite.solomachine.ClientID + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, suite.solomachine.ClientState()) + + tc.malleate() + + status := lightClientModule.Status(suite.chainA.GetContext(), clientID) + suite.Require().Equal(tc.expStatus, status) + }) + } +} + +func (suite *SoloMachineTestSuite) TestGetTimestampAtHeight() { + var ( + clientID string + height exported.Height + ) + + testCases := []struct { + name string + malleate func() + expValue uint64 + expErr error + }{ + { + "success: get timestamp at height exists", + func() {}, + suite.solomachine.ClientState().ConsensusState.Timestamp, + nil, + }, + { + "success: modified height", + func() { + height = clienttypes.ZeroHeight() + }, + // Timestamp should be the same. + suite.solomachine.ClientState().ConsensusState.Timestamp, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + 0, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + clientID = suite.solomachine.ClientID + clientState := suite.solomachine.ClientState() + height = clienttypes.NewHeight(0, suite.solomachine.ClientState().Sequence) + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() + + ts, err := lightClientModule.TimestampAtHeight(suite.chainA.GetContext(), clientID, height) + + suite.Require().Equal(tc.expValue, ts) + suite.Require().ErrorIs(err, tc.expErr) + }) + } +} + func (suite *SoloMachineTestSuite) TestInitialize() { // test singlesig and multisig public keys for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { @@ -29,37 +149,37 @@ func (suite *SoloMachineTestSuite) TestInitialize() { expErr error }{ { - "valid consensus state", + "success: valid consensus state", sm.ConsensusState(), sm.ClientState(), nil, }, { - "nil consensus state", + "failure: nil consensus state", nil, sm.ClientState(), clienttypes.ErrInvalidConsensus, }, { - "invalid consensus state: Tendermint consensus state", + "failure: invalid consensus state: Tendermint consensus state", &ibctm.ConsensusState{}, sm.ClientState(), fmt.Errorf("proto: wrong wireType = 0 for field TypeUrl"), }, { - "invalid consensus state: consensus state does not match consensus state in client", + "failure: invalid consensus state: consensus state does not match consensus state in client", malleatedConsensus, sm.ClientState(), clienttypes.ErrInvalidConsensus, }, { - "invalid client state: sequence is zero", + "failure: invalid client state: sequence is zero", sm.ConsensusState(), solomachine.NewClientState(0, sm.ConsensusState()), clienttypes.ErrInvalidClient, }, { - "invalid client state: Tendermint client state", + "failure: invalid client state: Tendermint client state", sm.ConsensusState(), &ibctm.ClientState{}, fmt.Errorf("proto: wrong wireType = 2 for field IsFrozen"), @@ -71,12 +191,11 @@ func (suite *SoloMachineTestSuite) TestInitialize() { suite.Run(tc.name, func() { suite.SetupTest() + clientID := sm.ClientID clientStateBz := suite.chainA.Codec.MustMarshal(tc.clientState) consStateBz := suite.chainA.Codec.MustMarshal(tc.consState) - clientID := suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(suite.chainA.GetContext(), exported.Solomachine) - lcm, found := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.Route(clientID) suite.Require().True(found) @@ -85,7 +204,7 @@ func (suite *SoloMachineTestSuite) TestInitialize() { expPass := tc.expErr == nil if expPass { - suite.Require().NoError(err, "valid testcase: %s failed", tc.name) + suite.Require().NoError(err) suite.Require().True(store.Has(host.ClientStateKey())) } else { suite.Require().ErrorContains(err, tc.expErr.Error()) @@ -96,110 +215,1501 @@ func (suite *SoloMachineTestSuite) TestInitialize() { } } -func (suite *SoloMachineTestSuite) TestRecoverClient() { +func (suite *SoloMachineTestSuite) TestVerifyMembership() { var ( - subjectClientID, substituteClientID string - subjectClientState, substituteClientState *solomachine.ClientState + clientState *solomachine.ClientState + path exported.Path + proof []byte + testingPath *ibctesting.Path + signBytes solomachine.SignBytes + err error + clientID string ) - testCases := []struct { - name string - malleate func() - expErr error - }{ - { - "success", - func() { + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, }, - nil, - }, - { - "cannot parse malformed substitute client ID", - func() { - substituteClientID = ibctesting.InvalidID + { + "success: client state verification", + func() { + clientState = sm.ClientState() + clientStateBz, err := suite.chainA.Codec.MarshalInterface(clientState) + suite.Require().NoError(err) + + path = sm.GetClientStatePath(counterpartyClientIdentifier) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: clientStateBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, }, - host.ErrInvalidID, - }, - { - "substitute client ID does not contain 06-solomachine prefix", - func() { - substituteClientID = wasmClientID + { + "success: consensus state verification", + func() { + clientState = sm.ClientState() + consensusState := clientState.ConsensusState + consensusStateBz, err := suite.chainA.Codec.MarshalInterface(consensusState) + suite.Require().NoError(err) + + path = sm.GetConsensusStatePath(counterpartyClientIdentifier, clienttypes.NewHeight(0, 1)) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: consensusStateBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, }, - clienttypes.ErrInvalidClientType, - }, - { - "cannot find subject client state", - func() { - subjectClientID = smClientID + { + "success: connection state verification", + func() { + testingPath.SetupConnections() + + connectionEnd, found := suite.chainA.GetSimApp().IBCKeeper.ConnectionKeeper.GetConnection(suite.chainA.GetContext(), ibctesting.FirstConnectionID) + suite.Require().True(found) + + connectionEndBz, err := suite.chainA.Codec.Marshal(&connectionEnd) + suite.Require().NoError(err) + + path = sm.GetConnectionStatePath(ibctesting.FirstConnectionID) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: connectionEndBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, }, - clienttypes.ErrClientNotFound, - }, - { - "cannot find substitute client state", - func() { - substituteClientID = smClientID + { + "success: channel state verification", + func() { + testingPath.SetupConnections() + suite.coordinator.CreateMockChannels(testingPath) + + channelEnd, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetChannel(suite.chainA.GetContext(), ibctesting.MockPort, ibctesting.FirstChannelID) + suite.Require().True(found) + + channelEndBz, err := suite.chainA.Codec.Marshal(&channelEnd) + suite.Require().NoError(err) + + path = sm.GetChannelStatePath(ibctesting.MockPort, ibctesting.FirstChannelID) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: channelEndBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, }, - clienttypes.ErrClientNotFound, - }, - } + { + "success: next sequence recv verification", + func() { + testingPath.SetupConnections() + suite.coordinator.CreateMockChannels(testingPath) - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupTest() // reset - cdc := suite.chainA.Codec - ctx := suite.chainA.GetContext() + nextSeqRecv, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetNextSequenceRecv(suite.chainA.GetContext(), ibctesting.MockPort, ibctesting.FirstChannelID) + suite.Require().True(found) - subjectClientID = suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(ctx, exported.Solomachine) - subject := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, substituteClientID, "testing", 1) - subjectClientState = subject.ClientState() + path = sm.GetNextSequenceRecvPath(ibctesting.MockPort, ibctesting.FirstChannelID) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: sdk.Uint64ToBigEndian(nextSeqRecv), + } - substituteClientID = suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(ctx, exported.Solomachine) - substitute := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, substituteClientID, "testing", 1) - substitute.Sequence++ // increase sequence so that latest height of substitute is > than subject's latest height - substituteClientState = substitute.ClientState() + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, substituteClientID) - clientStore.Get(host.ClientStateKey()) - bz := clienttypes.MustMarshalClientState(cdc, substituteClientState) - clientStore.Set(host.ClientStateKey(), bz) + sig := sm.GenerateSignature(signBz) - subjectClientState.IsFrozen = true - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(ctx, subjectClientID, subjectClientState) + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } - lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(subjectClientID) - suite.Require().True(found) + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: packet commitment verification", + func() { + packet := channeltypes.NewPacket( + ibctesting.MockPacketData, + 1, + ibctesting.MockPort, + ibctesting.FirstChannelID, + ibctesting.MockPort, + ibctesting.FirstChannelID, + clienttypes.NewHeight(0, 10), + 0, + ) - tc.malleate() + commitmentBz := channeltypes.CommitPacket(suite.chainA.Codec, packet) + path = sm.GetPacketCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: commitmentBz, + } - err := lightClientModule.RecoverClient(ctx, subjectClientID, substituteClientID) + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) - expPass := tc.expErr == nil - if expPass { + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: packet acknowledgement verification", + func() { + path = sm.GetPacketAcknowledgementPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: ibctesting.MockAcknowledgement, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: packet receipt verification", + func() { + path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: []byte{byte(1)}, // packet receipt is stored as a single byte + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: invalid path type - empty", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: malformed proof fails to unmarshal", + func() { + path = sm.GetClientStatePath(counterpartyClientIdentifier) + proof = []byte("invalid proof") + }, + fmt.Errorf("failed to unmarshal proof into type"), + }, + { + "failure: consensus state timestamp is greater than signature", + func() { + consensusState := &solomachine.ConsensusState{ + Timestamp: sm.Time + 1, + PublicKey: sm.ConsensusState().PublicKey, + } + + clientState = solomachine.NewClientState(sm.Sequence, consensusState) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("the consensus state timestamp is greater than the signature timestamp (11 >= 10): %s", solomachine.ErrInvalidProof), + }, + { + "failure: signature data is nil", + func() { + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: nil, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + fmt.Errorf("signature data cannot be empty: %s", solomachine.ErrInvalidProof), + }, + { + "failure: consensus state public key is nil", + func() { + clientState.ConsensusState.PublicKey = nil + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("consensus state PublicKey cannot be nil: %s", clienttypes.ErrInvalidConsensus), + }, + { + "failure: malformed signature data fails to unmarshal", + func() { + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: []byte("invalid signature data"), + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + fmt.Errorf("failed to unmarshal proof into type"), + }, + { + "failure: proof is nil", + func() { + proof = nil + }, + fmt.Errorf("proof cannot be empty: %s", solomachine.ErrInvalidProof), + }, + { + "failure: proof verification failed", + func() { + signBytes.Data = []byte("invalid membership data value") + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: empty path", + func() { + path = commitmenttypes.MerklePath{} + }, + fmt.Errorf("path must be of length 2: []: %s", host.ErrInvalidPath), + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + testingPath = ibctesting.NewPath(suite.chainA, suite.chainB) + + clientID = sm.ClientID + clientState = sm.ClientState() + + path = commitmenttypes.NewMerklePath("ibc", "solomachine") + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: []byte("solomachine"), + } - // assert that status of subject client is now Active - clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, subjectClientID) - bz = clientStore.Get(host.ClientStateKey()) - smClientState := clienttypes.MustUnmarshalClientState(cdc, bz).(*solomachine.ClientState) + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) - suite.Require().Equal(substituteClientState.ConsensusState, smClientState.ConsensusState) - suite.Require().Equal(substituteClientState.Sequence, smClientState.Sequence) - suite.Require().Equal(exported.Active, lightClientModule.Status(ctx, subjectClientID)) - } else { - suite.Require().Error(err) - suite.Require().ErrorIs(err, tc.expErr) - } - }) + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + // Set the client state in the store for light client call to find. + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() + + var expSeq uint64 + if clientState.ConsensusState != nil { + expSeq = clientState.Sequence + 1 + } + + // Verify the membership proof + err = lightClientModule.VerifyMembership( + suite.chainA.GetContext(), clientID, clienttypes.ZeroHeight(), + 0, 0, proof, path, signBytes.Data, + ) + + expPass := tc.expErr == nil + if expPass { + // Grab fresh client state after updates. + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), clientID) + suite.Require().True(found) + clientState = cs.(*solomachine.ClientState) + + suite.Require().NoError(err) + // clientState.Sequence is the most recent view of state. + suite.Require().Equal(expSeq, clientState.Sequence) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } } } -func (suite *SoloMachineTestSuite) TestVerifyUpgradeAndUpdateState() { - clientID := suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(suite.chainA.GetContext(), exported.Solomachine) +func (suite *SoloMachineTestSuite) TestVerifyNonMembership() { + var ( + clientState *solomachine.ClientState + path exported.Path + proof []byte + signBytes solomachine.SignBytes + err error + clientID string + ) - lightClientModule, found := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.Route(clientID) + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success: packet receipt absence verification", + func() { + path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: nil, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: invalid path type", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: malformed proof fails to unmarshal", + func() { + path = sm.GetClientStatePath(counterpartyClientIdentifier) + proof = []byte("invalid proof") + }, + fmt.Errorf("failed to unmarshal proof into type"), + }, + { + "failure: consensus state timestamp is greater than signature", + func() { + consensusState := &solomachine.ConsensusState{ + Timestamp: sm.Time + 1, + PublicKey: sm.ConsensusState().PublicKey, + } + + clientState = solomachine.NewClientState(sm.Sequence, consensusState) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("the consensus state timestamp is greater than the signature timestamp (11 >= 10): %s", solomachine.ErrInvalidProof), + }, + { + "failure: signature data is nil", + func() { + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: nil, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + fmt.Errorf("signature data cannot be empty: %s", solomachine.ErrInvalidProof), + }, + { + "failure: consensus state public key is nil", + func() { + clientState.ConsensusState.PublicKey = nil + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("consensus state PublicKey cannot be nil: %s", clienttypes.ErrInvalidConsensus), + }, + { + "failure: malformed signature data fails to unmarshal", + func() { + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: []byte("invalid signature data"), + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + fmt.Errorf("failed to unmarshal proof into type"), + }, + { + "failure: proof is nil", + func() { + proof = nil + }, + fmt.Errorf("proof cannot be empty: %s", solomachine.ErrInvalidProof), + }, + { + "failure: proof verification failed", + func() { + signBytes.Data = []byte("invalid non-membership data value") + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + solomachine.ErrSignatureVerificationFailed, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + + clientState = sm.ClientState() + clientID = sm.ClientID + + path = commitmenttypes.NewMerklePath("ibc", "solomachine") + merklePath, ok := path.(commitmenttypes.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: nil, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + // Set the client state in the store for light client call to find. + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() + + var expSeq uint64 + if clientState.ConsensusState != nil { + expSeq = clientState.Sequence + 1 + } + + // Verify the membership proof + err = lightClientModule.VerifyNonMembership( + suite.chainA.GetContext(), clientID, clienttypes.ZeroHeight(), + 0, 0, proof, path, + ) + + expPass := tc.expErr == nil + if expPass { + // Grab fresh client state after updates. + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), clientID) + suite.Require().True(found) + clientState = cs.(*solomachine.ClientState) + + suite.Require().NoError(err) + suite.Require().Equal(expSeq, clientState.Sequence) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestRecoverClient() { + var ( + subjectClientID, substituteClientID string + subjectClientState, substituteClientState *solomachine.ClientState + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + }, + nil, + }, + { + "failure: cannot parse malformed substitute client ID", + func() { + substituteClientID = ibctesting.InvalidID + }, + host.ErrInvalidID, + }, + { + "failure: substitute client ID does not contain 06-solomachine prefix", + func() { + substituteClientID = wasmClientID + }, + clienttypes.ErrInvalidClientType, + }, + { + "failure: cannot find subject client state", + func() { + subjectClientID = unusedSmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: cannot find substitute client state", + func() { + substituteClientID = unusedSmClientID + }, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + ctx := suite.chainA.GetContext() + + subjectClientID = suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(ctx, exported.Solomachine) + subject := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, substituteClientID, "testing", 1) + subjectClientState = subject.ClientState() + + substituteClientID = suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(ctx, exported.Solomachine) + substitute := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, substituteClientID, "testing", 1) + substitute.Sequence++ // increase sequence so that latest height of substitute is > than subject's latest height + substituteClientState = substitute.ClientState() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, substituteClientID) + clientStore.Get(host.ClientStateKey()) + bz := clienttypes.MustMarshalClientState(suite.chainA.Codec, substituteClientState) + clientStore.Set(host.ClientStateKey(), bz) + + subjectClientState.IsFrozen = true + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(ctx, subjectClientID, subjectClientState) + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(subjectClientID) + suite.Require().True(found) + + tc.malleate() + + err := lightClientModule.RecoverClient(ctx, subjectClientID, substituteClientID) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + + // assert that status of subject client is now Active + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, subjectClientID) + bz = clientStore.Get(host.ClientStateKey()) + smClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, bz).(*solomachine.ClientState) + + suite.Require().Equal(substituteClientState.ConsensusState, smClientState.ConsensusState) + suite.Require().Equal(substituteClientState.Sequence, smClientState.Sequence) + suite.Require().Equal(exported.Active, lightClientModule.Status(ctx, subjectClientID)) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *SoloMachineTestSuite) TestUpdateState() { + var ( + clientState *solomachine.ClientState + clientMsg exported.ClientMessage + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleate func() + expPanic error + }{ + { + "successful update", + func() {}, + nil, + }, + { + "failure: invalid type misbehaviour", + func() { + clientState = sm.ClientState() + clientMsg = sm.CreateMisbehaviour() + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("unsupported ClientMessage: %T", sm.CreateMisbehaviour()), + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + + clientID = sm.ClientID + clientState = sm.ClientState() + clientMsg = sm.CreateHeader(sm.Diversifier) + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() // setup test + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + var consensusHeights []exported.Height + updateStateFunc := func() { + consensusHeights = lightClientModule.UpdateState(suite.chainA.GetContext(), clientID, clientMsg) + } + + expPass := tc.expPanic == nil + if expPass { + updateStateFunc() + + clientStateBz := store.Get(host.ClientStateKey()) + suite.Require().NotEmpty(clientStateBz) + + newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz) + + suite.Require().Len(consensusHeights, 1) + suite.Require().Equal(uint64(0), consensusHeights[0].GetRevisionNumber()) + suite.Require().Equal(newClientState.(*solomachine.ClientState).Sequence, consensusHeights[0].GetRevisionHeight()) + + suite.Require().False(newClientState.(*solomachine.ClientState).IsFrozen) + suite.Require().Equal(clientMsg.(*solomachine.Header).NewPublicKey, newClientState.(*solomachine.ClientState).ConsensusState.PublicKey) + suite.Require().Equal(clientMsg.(*solomachine.Header).NewDiversifier, newClientState.(*solomachine.ClientState).ConsensusState.Diversifier) + suite.Require().Equal(clientMsg.(*solomachine.Header).Timestamp, newClientState.(*solomachine.ClientState).ConsensusState.Timestamp) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), updateStateFunc) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestCheckForMisbehaviour() { + var ( + clientMsg exported.ClientMessage + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + testCases := []struct { + name string + malleate func() + foundMisbehaviour bool + expPanic error + }{ + { + "success", + func() { + clientMsg = sm.CreateMisbehaviour() + }, + true, + nil, + }, + { + "failure: normal header returns false", + func() { + clientMsg = sm.CreateHeader(sm.Diversifier) + }, + false, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + false, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + + clientID = sm.ClientID + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + + tc.malleate() + + var foundMisbehaviour bool + foundMisbehaviourFunc := func() { + foundMisbehaviour = lightClientModule.CheckForMisbehaviour(suite.chainA.GetContext(), clientID, clientMsg) + } + + expPass := tc.expPanic == nil + if expPass { + foundMisbehaviourFunc() + + suite.Require().Equal(tc.foundMisbehaviour, foundMisbehaviour) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), foundMisbehaviourFunc) + suite.Require().False(foundMisbehaviour) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestUpdateStateOnMisbehaviour() { + var clientID string + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + testCases := []struct { + name string + malleate func() + expPanic error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + clientID = sm.ClientID + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + + tc.malleate() + + updateOnMisbehaviourFunc := func() { + lightClientModule.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), clientID, nil) + } + + expPass := tc.expPanic == nil + if expPass { + updateOnMisbehaviourFunc() + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + clientStateBz := store.Get(host.ClientStateKey()) + suite.Require().NotEmpty(clientStateBz) + + newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz) + + suite.Require().True(newClientState.(*solomachine.ClientState).IsFrozen) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), updateOnMisbehaviourFunc) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestVerifyClientMessageHeader() { + var ( + clientMsg exported.ClientMessage + clientState *solomachine.ClientState + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: successful header", + func() { + clientMsg = sm.CreateHeader(sm.Diversifier) + }, + nil, + }, + { + "success: successful header with new diversifier", + func() { + clientMsg = sm.CreateHeader(sm.Diversifier + "0") + }, + nil, + }, + { + "success: successful misbehaviour", + func() { + clientMsg = sm.CreateMisbehaviour() + }, + nil, + }, + { + "failure: invalid client message type", + func() { + clientMsg = &ibctm.Header{} + }, + clienttypes.ErrInvalidClientType, + }, + { + "failure: invalid header Signature", + func() { + h := sm.CreateHeader(sm.Diversifier) + h.Signature = suite.GetInvalidProof() + clientMsg = h + }, fmt.Errorf("proto: wrong wireType = 0 for field Multi"), + }, + { + "failure: invalid timestamp in header", + func() { + h := sm.CreateHeader(sm.Diversifier) + h.Timestamp-- + clientMsg = h + }, clienttypes.ErrInvalidHeader, + }, + { + "failure: signature uses wrong sequence", + func() { + sm.Sequence++ + clientMsg = sm.CreateHeader(sm.Diversifier) + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "signature uses new pubkey to sign", + func() { + // store in temp before assigning to interface type + cs := sm.ClientState() + h := sm.CreateHeader(sm.Diversifier) + + publicKey, err := codectypes.NewAnyWithValue(sm.PublicKey) + suite.NoError(err) + + data := &solomachine.HeaderData{ + NewPubKey: publicKey, + NewDiversifier: h.NewDiversifier, + } + + dataBz, err := suite.chainA.Codec.Marshal(data) + suite.Require().NoError(err) + + // generate invalid signature + signBytes := &solomachine.SignBytes{ + Sequence: cs.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: dataBz, + } + + signBz, err := suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + suite.Require().NoError(err) + h.Signature = sig + + clientState = cs + clientMsg = h + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: signature signs over old pubkey", + func() { + // store in temp before assigning to interface type + cs := sm.ClientState() + oldPubKey := sm.PublicKey + h := sm.CreateHeader(sm.Diversifier) + + // generate invalid signature + data := append(sdk.Uint64ToBigEndian(cs.Sequence), oldPubKey.Bytes()...) + sig := sm.GenerateSignature(data) + h.Signature = sig + + clientState = cs + clientMsg = h + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + }, + // TODO(jim): Doesn't fail on VerifySignature + clienttypes.ErrInvalidHeader, + }, + { + "failure: consensus state public key is nil - header", + func() { + clientState.ConsensusState.PublicKey = nil + clientMsg = sm.CreateHeader(sm.Diversifier) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + }, + // TODO(jim): Doesn't fail on VerifySignature + clienttypes.ErrInvalidHeader, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + clientID = sm.ClientID + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + + tc.malleate() + + err := lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), clientID, clientMsg) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestVerifyClientMessageMisbehaviour() { + var ( + clientMsg exported.ClientMessage + clientState *solomachine.ClientState + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: successful misbehaviour", + func() { + clientMsg = sm.CreateMisbehaviour() + }, + nil, + }, + { + "success: old misbehaviour is successful (timestamp is less than current consensus state)", + func() { + clientState = sm.ClientState() + sm.Time -= 5 + clientMsg = sm.CreateMisbehaviour() + }, nil, + }, + { + "failure: invalid client message type", + func() { + clientMsg = &ibctm.Header{} + }, + clienttypes.ErrInvalidClientType, + }, + { + "failure: consensus state pubkey is nil", + func() { + clientState.ConsensusState.PublicKey = nil + clientMsg = sm.CreateMisbehaviour() + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + clienttypes.ErrInvalidConsensus, + }, + { + "failure: invalid SignatureOne SignatureData", + func() { + m := sm.CreateMisbehaviour() + + m.SignatureOne.Signature = suite.GetInvalidProof() + clientMsg = m + }, fmt.Errorf("proto: wrong wireType = 0 for field Multi"), + }, + { + "failure: invalid SignatureTwo SignatureData", + func() { + m := sm.CreateMisbehaviour() + + m.SignatureTwo.Signature = suite.GetInvalidProof() + clientMsg = m + }, fmt.Errorf("proto: wrong wireType = 0 for field Multi"), + }, + { + "failure: invalid SignatureOne timestamp", + func() { + m := sm.CreateMisbehaviour() + + m.SignatureOne.Timestamp = 1000000000000 + clientMsg = m + }, solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: invalid SignatureTwo timestamp", + func() { + m := sm.CreateMisbehaviour() + + m.SignatureTwo.Timestamp = 1000000000000 + clientMsg = m + }, solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: invalid first signature data", + func() { + // store in temp before assigning to interface type + m := sm.CreateMisbehaviour() + + msg := []byte("DATA ONE") + signBytes := &solomachine.SignBytes{ + Sequence: sm.Sequence + 1, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: msg, + } + + data, err := suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(data) + + m.SignatureOne.Signature = sig + m.SignatureOne.Data = msg + clientMsg = m + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: invalid second signature data", + func() { + // store in temp before assigning to interface type + m := sm.CreateMisbehaviour() + + msg := []byte("DATA TWO") + signBytes := &solomachine.SignBytes{ + Sequence: sm.Sequence + 1, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: msg, + } + + data, err := suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(data) + + m.SignatureTwo.Signature = sig + m.SignatureTwo.Data = msg + clientMsg = m + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: wrong pubkey generates first signature", + func() { + badMisbehaviour := sm.CreateMisbehaviour() + + // update public key to a new one + sm.CreateHeader(sm.Diversifier) + m := sm.CreateMisbehaviour() + + // set SignatureOne to use the wrong signature + m.SignatureOne = badMisbehaviour.SignatureOne + clientMsg = m + }, solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: wrong pubkey generates second signature", + func() { + badMisbehaviour := sm.CreateMisbehaviour() + + // update public key to a new one + sm.CreateHeader(sm.Diversifier) + m := sm.CreateMisbehaviour() + + // set SignatureTwo to use the wrong signature + m.SignatureTwo = badMisbehaviour.SignatureTwo + clientMsg = m + }, solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: signatures sign over different sequence", + func() { + // store in temp before assigning to interface type + m := sm.CreateMisbehaviour() + + // Signature One + msg := []byte("DATA ONE") + // sequence used is plus 1 + signBytes := &solomachine.SignBytes{ + Sequence: sm.Sequence + 1, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: msg, + } + + data, err := suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(data) + + m.SignatureOne.Signature = sig + m.SignatureOne.Data = msg + + // Signature Two + msg = []byte("DATA TWO") + // sequence used is minus 1 + + signBytes = &solomachine.SignBytes{ + Sequence: sm.Sequence - 1, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: msg, + } + data, err = suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig = sm.GenerateSignature(data) + + m.SignatureTwo.Signature = sig + m.SignatureTwo.Data = msg + + clientMsg = m + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + clientID = sm.ClientID + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + + tc.malleate() + + err := lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), clientID, clientMsg) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestVerifyUpgradeAndUpdateState() { + clientID := suite.solomachine.ClientID + + lightClientModule, found := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.Route(clientID) suite.Require().True(found) err := lightClientModule.VerifyUpgradeAndUpdateState(suite.chainA.GetContext(), clientID, nil, nil, nil, nil) suite.Require().Error(err) } + +func (suite *SoloMachineTestSuite) TestLatestHeight() { + var clientID string + + testCases := []struct { + name string + malleate func() + expHeight clienttypes.Height + }{ + { + "success", + func() {}, + // Default as returned by solomachine.ClientState() + clienttypes.NewHeight(0, 1), + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + clienttypes.ZeroHeight(), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + clientID = suite.solomachine.ClientID + clientState := suite.solomachine.ClientState() + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() + + height := lightClientModule.LatestHeight(suite.chainA.GetContext(), clientID) + + suite.Require().Equal(tc.expHeight, height) + }) + } +} diff --git a/modules/light-clients/06-solomachine/proof.go b/modules/light-clients/06-solomachine/proof.go index 6b347e112a5..516b8421d0d 100644 --- a/modules/light-clients/06-solomachine/proof.go +++ b/modules/light-clients/06-solomachine/proof.go @@ -26,7 +26,7 @@ func VerifySignature(pubKey cryptotypes.PubKey, signBytes []byte, sigData signin if err := pubKey.VerifyMultisignature(func(signing.SignMode) ([]byte, error) { return signBytes, nil }, data); err != nil { - return err + return errorsmod.Wrapf(ErrSignatureVerificationFailed, err.Error()) } default: diff --git a/modules/light-clients/06-solomachine/proposal_handle_test.go b/modules/light-clients/06-solomachine/proposal_handle_test.go deleted file mode 100644 index 35109d66a41..00000000000 --- a/modules/light-clients/06-solomachine/proposal_handle_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package solomachine_test - -import ( - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" - solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" - ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v8/testing" -) - -func (suite *SoloMachineTestSuite) TestCheckSubstituteAndUpdateState() { - var ( - subjectClientState *solomachine.ClientState - substituteClientState exported.ClientState - ) - - // test singlesig and multisig public keys - for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "substitute is not the solo machine", func() { - substituteClientState = &ibctm.ClientState{} - }, false, - }, - { - "subject public key is nil", func() { - subjectClientState.ConsensusState.PublicKey = nil - }, false, - }, - - { - "substitute public key is nil", func() { - substituteClientState.(*solomachine.ClientState).ConsensusState.PublicKey = nil - }, false, - }, - { - "subject and substitute use the same public key", func() { - substituteClientState.(*solomachine.ClientState).ConsensusState.PublicKey = subjectClientState.ConsensusState.PublicKey - }, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - - subjectClientState = sm.ClientState() - substitute := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "substitute", "testing", 5) - substituteClientState = substitute.ClientState() - - tc.malleate() - - subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), sm.ClientID) - substituteClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), substitute.ClientID) - - err := subjectClientState.CheckSubstituteAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), subjectClientStore, substituteClientStore, substituteClientState) - - if tc.expPass { - suite.Require().NoError(err) - - // ensure updated client state is set in store - bz := subjectClientStore.Get(host.ClientStateKey()) - updatedClient := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), bz).(*solomachine.ClientState) - - suite.Require().Equal(substituteClientState.(*solomachine.ClientState).ConsensusState, updatedClient.ConsensusState) - suite.Require().Equal(substituteClientState.(*solomachine.ClientState).Sequence, updatedClient.Sequence) - suite.Require().Equal(false, updatedClient.IsFrozen) - - } else { - suite.Require().Error(err) - } - }) - } - } -} diff --git a/modules/light-clients/06-solomachine/solomachine_test.go b/modules/light-clients/06-solomachine/solomachine_test.go index b5b21c894e2..3b43200b4b7 100644 --- a/modules/light-clients/06-solomachine/solomachine_test.go +++ b/modules/light-clients/06-solomachine/solomachine_test.go @@ -45,8 +45,8 @@ func (suite *SoloMachineTestSuite) SetupTest() { suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) - suite.solomachine = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinesingle", "testing", 1) - suite.solomachineMulti = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinemulti", "testing", 4) + suite.solomachine = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-0", "testing", 1) + suite.solomachineMulti = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-1", "testing", 4) suite.store = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), exported.Solomachine) } diff --git a/modules/light-clients/06-solomachine/update_test.go b/modules/light-clients/06-solomachine/update_test.go deleted file mode 100644 index 4299f249fb1..00000000000 --- a/modules/light-clients/06-solomachine/update_test.go +++ /dev/null @@ -1,546 +0,0 @@ -package solomachine_test - -import ( - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" - solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" - ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v8/testing" -) - -func (suite *SoloMachineTestSuite) TestVerifyClientMessageHeader() { - var ( - clientMsg exported.ClientMessage - clientState *solomachine.ClientState - ) - - // test singlesig and multisig public keys - for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - - testCases := []struct { - name string - setup func() - expPass bool - }{ - { - "successful header", - func() { - clientMsg = sm.CreateHeader(sm.Diversifier) - }, - true, - }, - { - "successful header with new diversifier", - func() { - clientMsg = sm.CreateHeader(sm.Diversifier + "0") - }, - true, - }, - { - "successful misbehaviour", - func() { - clientMsg = sm.CreateMisbehaviour() - }, - true, - }, - { - "invalid client message type", - func() { - clientMsg = &ibctm.Header{} - }, - false, - }, - { - "invalid header Signature", - func() { - h := sm.CreateHeader(sm.Diversifier) - h.Signature = suite.GetInvalidProof() - clientMsg = h - }, false, - }, - { - "invalid timestamp in header", - func() { - h := sm.CreateHeader(sm.Diversifier) - h.Timestamp-- - clientMsg = h - }, false, - }, - { - "signature uses wrong sequence", - func() { - sm.Sequence++ - clientMsg = sm.CreateHeader(sm.Diversifier) - }, - false, - }, - { - "signature uses new pubkey to sign", - func() { - // store in temp before assigning to interface type - cs := sm.ClientState() - h := sm.CreateHeader(sm.Diversifier) - - publicKey, err := codectypes.NewAnyWithValue(sm.PublicKey) - suite.NoError(err) - - data := &solomachine.HeaderData{ - NewPubKey: publicKey, - NewDiversifier: h.NewDiversifier, - } - - dataBz, err := suite.chainA.Codec.Marshal(data) - suite.Require().NoError(err) - - // generate invalid signature - signBytes := &solomachine.SignBytes{ - Sequence: cs.Sequence, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: []byte("invalid signature data"), - Data: dataBz, - } - - signBz, err := suite.chainA.Codec.Marshal(signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(signBz) - suite.Require().NoError(err) - h.Signature = sig - - clientState = cs - clientMsg = h - }, - false, - }, - { - "signature signs over old pubkey", - func() { - // store in temp before assigning to interface type - cs := sm.ClientState() - oldPubKey := sm.PublicKey - h := sm.CreateHeader(sm.Diversifier) - - // generate invalid signature - data := append(sdk.Uint64ToBigEndian(cs.Sequence), oldPubKey.Bytes()...) - sig := sm.GenerateSignature(data) - h.Signature = sig - - clientState = cs - clientMsg = h - }, - false, - }, - { - "consensus state public key is nil - header", - func() { - clientState.ConsensusState.PublicKey = nil - clientMsg = sm.CreateHeader(sm.Diversifier) - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - clientState = sm.ClientState() - - // setup test - tc.setup() - - err := clientState.VerifyClientMessage(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, clientMsg) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } - } -} - -func (suite *SoloMachineTestSuite) TestVerifyClientMessageMisbehaviour() { - var ( - clientMsg exported.ClientMessage - clientState *solomachine.ClientState - ) - - // test singlesig and multisig public keys - for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - - testCases := []struct { - name string - setup func() - expPass bool - }{ - { - "successful misbehaviour", - func() { - clientMsg = sm.CreateMisbehaviour() - }, - true, - }, - { - "old misbehaviour is successful (timestamp is less than current consensus state)", - func() { - clientState = sm.ClientState() - sm.Time -= 5 - clientMsg = sm.CreateMisbehaviour() - }, true, - }, - { - "invalid client message type", - func() { - clientMsg = &ibctm.Header{} - }, - false, - }, - { - "consensus state pubkey is nil", - func() { - clientState.ConsensusState.PublicKey = nil - clientMsg = sm.CreateMisbehaviour() - }, - false, - }, - { - "invalid SignatureOne SignatureData", - func() { - m := sm.CreateMisbehaviour() - - m.SignatureOne.Signature = suite.GetInvalidProof() - clientMsg = m - }, false, - }, - { - "invalid SignatureTwo SignatureData", - func() { - m := sm.CreateMisbehaviour() - - m.SignatureTwo.Signature = suite.GetInvalidProof() - clientMsg = m - }, false, - }, - { - "invalid SignatureOne timestamp", - func() { - m := sm.CreateMisbehaviour() - - m.SignatureOne.Timestamp = 1000000000000 - clientMsg = m - }, false, - }, - { - "invalid SignatureTwo timestamp", - func() { - m := sm.CreateMisbehaviour() - - m.SignatureTwo.Timestamp = 1000000000000 - clientMsg = m - }, false, - }, - { - "invalid first signature data", - func() { - // store in temp before assigning to interface type - m := sm.CreateMisbehaviour() - - msg := []byte("DATA ONE") - signBytes := &solomachine.SignBytes{ - Sequence: sm.Sequence + 1, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: []byte("invalid signature data"), - Data: msg, - } - - data, err := suite.chainA.Codec.Marshal(signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(data) - - m.SignatureOne.Signature = sig - m.SignatureOne.Data = msg - clientMsg = m - }, - false, - }, - { - "invalid second signature data", - func() { - // store in temp before assigning to interface type - m := sm.CreateMisbehaviour() - - msg := []byte("DATA TWO") - signBytes := &solomachine.SignBytes{ - Sequence: sm.Sequence + 1, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: []byte("invalid signature data"), - Data: msg, - } - - data, err := suite.chainA.Codec.Marshal(signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(data) - - m.SignatureTwo.Signature = sig - m.SignatureTwo.Data = msg - clientMsg = m - }, - false, - }, - { - "wrong pubkey generates first signature", - func() { - badMisbehaviour := sm.CreateMisbehaviour() - - // update public key to a new one - sm.CreateHeader(sm.Diversifier) - m := sm.CreateMisbehaviour() - - // set SignatureOne to use the wrong signature - m.SignatureOne = badMisbehaviour.SignatureOne - clientMsg = m - }, false, - }, - { - "wrong pubkey generates second signature", - func() { - badMisbehaviour := sm.CreateMisbehaviour() - - // update public key to a new one - sm.CreateHeader(sm.Diversifier) - m := sm.CreateMisbehaviour() - - // set SignatureTwo to use the wrong signature - m.SignatureTwo = badMisbehaviour.SignatureTwo - clientMsg = m - }, false, - }, - { - "signatures sign over different sequence", - func() { - // store in temp before assigning to interface type - m := sm.CreateMisbehaviour() - - // Signature One - msg := []byte("DATA ONE") - // sequence used is plus 1 - signBytes := &solomachine.SignBytes{ - Sequence: sm.Sequence + 1, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: []byte("invalid signature data"), - Data: msg, - } - - data, err := suite.chainA.Codec.Marshal(signBytes) - suite.Require().NoError(err) - - sig := sm.GenerateSignature(data) - - m.SignatureOne.Signature = sig - m.SignatureOne.Data = msg - - // Signature Two - msg = []byte("DATA TWO") - // sequence used is minus 1 - - signBytes = &solomachine.SignBytes{ - Sequence: sm.Sequence - 1, - Timestamp: sm.Time, - Diversifier: sm.Diversifier, - Path: []byte("invalid signature data"), - Data: msg, - } - data, err = suite.chainA.Codec.Marshal(signBytes) - suite.Require().NoError(err) - - sig = sm.GenerateSignature(data) - - m.SignatureTwo.Signature = sig - m.SignatureTwo.Data = msg - - clientMsg = m - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - clientState = sm.ClientState() - - // setup test - tc.setup() - - err := clientState.VerifyClientMessage(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, clientMsg) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } - } -} - -func (suite *SoloMachineTestSuite) TestUpdateState() { - var ( - clientState *solomachine.ClientState - clientMsg exported.ClientMessage - ) - - // test singlesig and multisig public keys - for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - - testCases := []struct { - name string - setup func() - expPass bool - }{ - { - "successful update", - func() { - clientState = sm.ClientState() - clientMsg = sm.CreateHeader(sm.Diversifier) - }, - true, - }, - { - "invalid type misbehaviour", - func() { - clientState = sm.ClientState() - clientMsg = sm.CreateMisbehaviour() - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - tc.setup() // setup test - - if tc.expPass { - consensusHeights := clientState.UpdateState(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, clientMsg) - - clientStateBz := suite.store.Get(host.ClientStateKey()) - suite.Require().NotEmpty(clientStateBz) - - newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz) - - suite.Require().Len(consensusHeights, 1) - suite.Require().Equal(uint64(0), consensusHeights[0].GetRevisionNumber()) - suite.Require().Equal(newClientState.(*solomachine.ClientState).Sequence, consensusHeights[0].GetRevisionHeight()) - - suite.Require().False(newClientState.(*solomachine.ClientState).IsFrozen) - suite.Require().Equal(clientMsg.(*solomachine.Header).NewPublicKey, newClientState.(*solomachine.ClientState).ConsensusState.PublicKey) - suite.Require().Equal(clientMsg.(*solomachine.Header).NewDiversifier, newClientState.(*solomachine.ClientState).ConsensusState.Diversifier) - suite.Require().Equal(clientMsg.(*solomachine.Header).Timestamp, newClientState.(*solomachine.ClientState).ConsensusState.Timestamp) - } else { - suite.Require().Panics(func() { - clientState.UpdateState(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, clientMsg) - }) - } - }) - } - } -} - -func (suite *SoloMachineTestSuite) TestCheckForMisbehaviour() { - var clientMsg exported.ClientMessage - - // test singlesig and multisig public keys - for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", - func() { - clientMsg = sm.CreateMisbehaviour() - }, - true, - }, - { - "normal header returns false", - func() { - clientMsg = sm.CreateHeader(sm.Diversifier) - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - clientState := sm.ClientState() - - tc.malleate() - - foundMisbehaviour := clientState.CheckForMisbehaviour(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, clientMsg) - - if tc.expPass { - suite.Require().True(foundMisbehaviour) - } else { - suite.Require().False(foundMisbehaviour) - } - }) - } - } -} - -func (suite *SoloMachineTestSuite) TestUpdateStateOnMisbehaviour() { - // test singlesig and multisig public keys - for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", - func() {}, - true, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - clientState := sm.ClientState() - - tc.malleate() - - clientState.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, nil) - - if tc.expPass { - clientStateBz := suite.store.Get(host.ClientStateKey()) - suite.Require().NotEmpty(clientStateBz) - - newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz) - - suite.Require().True(newClientState.(*solomachine.ClientState).IsFrozen) - } - }) - } - } -}