Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
1687 lines
48 KiB
Go
1687 lines
48 KiB
Go
package solomachine_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
codectypes "git.cw.tr/mukan-network/mukan-sdk/codec/types"
|
|
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
|
|
|
|
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
|
|
channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
|
|
commitmenttypesv2 "git.cw.tr/mukan-network/mukan-ibc/modules/core/23-commitment/types/v2"
|
|
host "git.cw.tr/mukan-network/mukan-ibc/modules/core/24-host"
|
|
ibcerrors "git.cw.tr/mukan-network/mukan-ibc/modules/core/errors"
|
|
"git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
|
|
solomachine "git.cw.tr/mukan-network/mukan-ibc/modules/light-clients/06-solomachine"
|
|
ibctm "git.cw.tr/mukan-network/mukan-ibc/modules/light-clients/07-tendermint"
|
|
ibctesting "git.cw.tr/mukan-network/mukan-ibc/testing"
|
|
ibcmock "git.cw.tr/mukan-network/mukan-ibc/testing/mock"
|
|
)
|
|
|
|
const (
|
|
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 {
|
|
suite.Run(tc.name, func() {
|
|
clientID = suite.solomachine.ClientID
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
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 {
|
|
suite.Run(tc.name, func() {
|
|
clientID = suite.solomachine.ClientID
|
|
clientState := suite.solomachine.ClientState()
|
|
height = clienttypes.NewHeight(0, suite.solomachine.ClientState().Sequence)
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
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} {
|
|
malleatedConsensus := sm.ClientState().ConsensusState
|
|
malleatedConsensus.Timestamp += 10
|
|
|
|
testCases := []struct {
|
|
name string
|
|
consState exported.ConsensusState
|
|
clientState exported.ClientState
|
|
expErr error
|
|
}{
|
|
{
|
|
"success: valid consensus state",
|
|
sm.ConsensusState(),
|
|
sm.ClientState(),
|
|
nil,
|
|
},
|
|
{
|
|
"failure: nil consensus state",
|
|
nil,
|
|
sm.ClientState(),
|
|
clienttypes.ErrInvalidConsensus,
|
|
},
|
|
{
|
|
"failure: invalid consensus state: Tendermint consensus state",
|
|
&ibctm.ConsensusState{},
|
|
sm.ClientState(),
|
|
errors.New("proto: wrong wireType = 0 for field TypeUrl"),
|
|
},
|
|
{
|
|
"failure: invalid consensus state: consensus state does not match consensus state in client",
|
|
malleatedConsensus,
|
|
sm.ClientState(),
|
|
clienttypes.ErrInvalidConsensus,
|
|
},
|
|
{
|
|
"failure: invalid client state: sequence is zero",
|
|
sm.ConsensusState(),
|
|
solomachine.NewClientState(0, sm.ConsensusState()),
|
|
clienttypes.ErrInvalidClient,
|
|
},
|
|
{
|
|
"failure: invalid client state: Tendermint client state",
|
|
sm.ConsensusState(),
|
|
&ibctm.ClientState{},
|
|
errors.New("proto: wrong wireType = 2 for field IsFrozen"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
clientID := sm.ClientID
|
|
|
|
clientStateBz := suite.chainA.Codec.MustMarshal(tc.clientState)
|
|
consStateBz := suite.chainA.Codec.MustMarshal(tc.consState)
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
err = lightClientModule.Initialize(suite.chainA.GetContext(), clientID, clientStateBz, consStateBz)
|
|
store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID)
|
|
|
|
if tc.expErr == nil {
|
|
suite.Require().NoError(err)
|
|
suite.Require().True(store.Has(host.ClientStateKey()))
|
|
} else {
|
|
suite.Require().ErrorContains(err, tc.expErr.Error())
|
|
suite.Require().False(store.Has(host.ClientStateKey()))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (suite *SoloMachineTestSuite) TestVerifyMembership() {
|
|
var (
|
|
clientState *solomachine.ClientState
|
|
path exported.Path
|
|
proof []byte
|
|
testingPath *ibctesting.Path
|
|
signBytes solomachine.SignBytes
|
|
err error
|
|
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",
|
|
func() {},
|
|
nil,
|
|
},
|
|
{
|
|
"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.(commitmenttypesv2.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,
|
|
},
|
|
{
|
|
"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.(commitmenttypesv2.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,
|
|
},
|
|
{
|
|
"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.(commitmenttypesv2.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,
|
|
},
|
|
{
|
|
"success: channel state verification",
|
|
func() {
|
|
testingPath.SetupConnections()
|
|
suite.coordinator.CreateMockChannels(testingPath)
|
|
|
|
channelEnd, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetChannel(suite.chainA.GetContext(), ibctesting.MockPort, testingPath.EndpointA.ChannelID)
|
|
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.(commitmenttypesv2.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,
|
|
},
|
|
{
|
|
"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, testingPath.EndpointA.ChannelID)
|
|
suite.Require().True(found)
|
|
|
|
path = sm.GetNextSequenceRecvPath(ibctesting.MockPort, ibctesting.FirstChannelID)
|
|
merklePath, ok := path.(commitmenttypesv2.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)
|
|
},
|
|
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,
|
|
)
|
|
|
|
commitmentBz := channeltypes.CommitPacket(packet)
|
|
path = sm.GetPacketCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
|
|
merklePath, ok := path.(commitmenttypesv2.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)
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"success: packet acknowledgement verification",
|
|
func() {
|
|
path = sm.GetPacketAcknowledgementPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1)
|
|
merklePath, ok := path.(commitmenttypesv2.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.(commitmenttypesv2.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")
|
|
},
|
|
errors.New("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)
|
|
},
|
|
errors.New("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 = commitmenttypesv2.MerklePath{}
|
|
},
|
|
fmt.Errorf("path must be of length 2: []: %s", host.ErrInvalidPath),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
testingPath = ibctesting.NewPath(suite.chainA, suite.chainB)
|
|
|
|
clientID = sm.ClientID
|
|
clientState = sm.ClientState()
|
|
|
|
path = commitmenttypesv2.NewMerklePath([]byte("ibc"), []byte("solomachine"))
|
|
merklePath, ok := path.(commitmenttypesv2.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)
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
// 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,
|
|
)
|
|
|
|
if tc.expErr == nil {
|
|
// Grab fresh client state after updates.
|
|
cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), clientID)
|
|
suite.Require().True(found)
|
|
clientState, ok = cs.(*solomachine.ClientState)
|
|
suite.Require().True(ok)
|
|
|
|
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) TestVerifyNonMembership() {
|
|
var (
|
|
clientState *solomachine.ClientState
|
|
path exported.Path
|
|
proof []byte
|
|
signBytes solomachine.SignBytes
|
|
err error
|
|
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",
|
|
func() {},
|
|
nil,
|
|
},
|
|
{
|
|
"success: packet receipt absence verification",
|
|
func() {
|
|
path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1)
|
|
merklePath, ok := path.(commitmenttypesv2.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")
|
|
},
|
|
errors.New("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)
|
|
},
|
|
errors.New("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 {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
|
|
clientState = sm.ClientState()
|
|
clientID = sm.ClientID
|
|
|
|
path = commitmenttypesv2.NewMerklePath([]byte("ibc"), []byte("solomachine"))
|
|
merklePath, ok := path.(commitmenttypesv2.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, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
// 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,
|
|
)
|
|
|
|
if tc.expErr == nil {
|
|
// Grab fresh client state after updates.
|
|
cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), clientID)
|
|
suite.Require().True(found)
|
|
clientState, ok = cs.(*solomachine.ClientState)
|
|
suite.Require().True(ok)
|
|
|
|
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 {
|
|
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, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), subjectClientID)
|
|
suite.Require().NoError(err)
|
|
|
|
tc.malleate()
|
|
|
|
err = lightClientModule.RecoverClient(ctx, subjectClientID, substituteClientID)
|
|
|
|
if tc.expErr == nil {
|
|
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, ok := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, bz).(*solomachine.ClientState)
|
|
suite.Require().True(ok)
|
|
|
|
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,
|
|
},
|
|
{
|
|
"invalid type misbehaviour no-ops",
|
|
func() {
|
|
clientState = sm.ClientState()
|
|
clientMsg = sm.CreateMisbehaviour()
|
|
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState)
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"failure: cannot find client state",
|
|
func() {
|
|
clientID = unusedSmClientID
|
|
},
|
|
fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
|
|
clientID = sm.ClientID
|
|
clientState = sm.ClientState()
|
|
clientMsg = sm.CreateHeader(sm.Diversifier)
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
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)
|
|
}
|
|
|
|
if tc.expPanic == nil {
|
|
updateStateFunc()
|
|
|
|
clientStateBz := store.Get(host.ClientStateKey())
|
|
suite.Require().NotEmpty(clientStateBz)
|
|
|
|
newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz)
|
|
|
|
if len(consensusHeights) == 0 {
|
|
suite.Require().Equal(clientState, newClientState)
|
|
return
|
|
}
|
|
|
|
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 {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
|
|
clientID = sm.ClientID
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
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)
|
|
}
|
|
|
|
if tc.expPanic == nil {
|
|
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 {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
clientID = sm.ClientID
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState())
|
|
|
|
tc.malleate()
|
|
|
|
updateOnMisbehaviourFunc := func() {
|
|
lightClientModule.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), clientID, nil)
|
|
}
|
|
|
|
if tc.expPanic == nil {
|
|
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 (
|
|
clientID string
|
|
clientMsg exported.ClientMessage
|
|
)
|
|
|
|
// 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
|
|
}, errors.New("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
|
|
|
|
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
|
|
|
|
clientMsg = h
|
|
},
|
|
solomachine.ErrSignatureVerificationFailed,
|
|
},
|
|
{
|
|
"failure: consensus state public key is nil - header",
|
|
func() {
|
|
h := sm.CreateHeader(sm.Diversifier)
|
|
h.NewPublicKey = nil
|
|
clientMsg = h
|
|
},
|
|
solomachine.ErrSignatureVerificationFailed,
|
|
},
|
|
{
|
|
"failure: cannot find client state",
|
|
func() {
|
|
clientID = unusedSmClientID
|
|
},
|
|
fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
clientID = sm.ClientID
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState())
|
|
|
|
tc.malleate()
|
|
|
|
err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), clientID, clientMsg)
|
|
|
|
if tc.expErr == nil {
|
|
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
|
|
}, errors.New("proto: wrong wireType = 0 for field Multi"),
|
|
},
|
|
{
|
|
"failure: invalid SignatureTwo SignatureData",
|
|
func() {
|
|
m := sm.CreateMisbehaviour()
|
|
|
|
m.SignatureTwo.Signature = suite.GetInvalidProof()
|
|
clientMsg = m
|
|
}, errors.New("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 {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
clientID = sm.ClientID
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState())
|
|
|
|
tc.malleate()
|
|
|
|
err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), clientID, clientMsg)
|
|
|
|
if tc.expErr == nil {
|
|
suite.Require().NoError(err)
|
|
} else {
|
|
suite.Require().ErrorContains(err, tc.expErr.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (suite *SoloMachineTestSuite) TestVerifyUpgradeAndUpdateState() {
|
|
clientID := suite.solomachine.ClientID
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
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 {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
clientID = suite.solomachine.ClientID
|
|
clientState := suite.solomachine.ClientState()
|
|
|
|
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
|
|
suite.Require().NoError(err)
|
|
|
|
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)
|
|
})
|
|
}
|
|
}
|