From ac561b43e3ae6a443764b9d70d0f2eae310733aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:39:36 +0100 Subject: [PATCH] fix: allow zero proof height, solo machine discards provided proof height in favor of sequence (#2746) imp: allow proof height to be zero for all core IBC `sdk.Msg` types that contain proofs. imp: discard proofHeight for solo machines and use the solo machine sequence instead. --- CHANGELOG.md | 2 + docs/migrations/v6-to-v7.md | 2 + modules/core/03-connection/types/msgs.go | 9 - modules/core/03-connection/types/msgs_test.go | 8 +- .../core/04-channel/keeper/handshake_test.go | 2 +- modules/core/04-channel/types/msgs.go | 24 -- modules/core/04-channel/types/msgs_test.go | 13 +- .../06-solomachine/client_state.go | 34 +- .../06-solomachine/client_state_test.go | 65 +--- .../06-solomachine/solomachine_test.go | 118 ++++++ .../07-tendermint/client_state.go | 2 + testing/solomachine.go | 362 +++++++++++++++++- 12 files changed, 501 insertions(+), 140 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f70c712e94f..498ecaeb960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (core) [\#2746](https://github.com/cosmos/ibc-go/pull/2746) Allow proof height to be zero for all core IBC `sdk.Msg` types that contain proofs. +* (light-clients/06-solomachine) [\#2746](https://github.com/cosmos/ibc-go/pull/2746) Discard proofHeight for solo machines and use the solo machine sequence instead. * (modules/light-clients/07-tendermint) [\#1713](https://github.com/cosmos/ibc-go/pull/1713) Allow client upgrade proposals to update `TrustingPeriod`. See ADR-026 for context. * (modules/core/02-client) [\#1188](https://github.com/cosmos/ibc-go/pull/1188/files) Routing `MsgSubmitMisbehaviour` to `UpdateClient` keeper function. Deprecating `SubmitMisbehaviour` endpoint. * (modules/core/02-client) [\#1208](https://github.com/cosmos/ibc-go/pull/1208) Replace `CheckHeaderAndUpdateState` usage in 02-client with calls to `VerifyClientMessage`, `CheckForMisbehaviour`, `UpdateStateOnMisbehaviour` and `UpdateState`. diff --git a/docs/migrations/v6-to-v7.md b/docs/migrations/v6-to-v7.md index 70e9519f4e3..c43bddd8dcf 100644 --- a/docs/migrations/v6-to-v7.md +++ b/docs/migrations/v6-to-v7.md @@ -45,6 +45,8 @@ The `CheckMisbehaviourAndUpdateState` function has been removed from `ClientStat The function `GetTimestampAtHeight` has been added to the `ClientState` interface. It should return the timestamp for a consensus state associated with the provided height. +A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + ### `Header` and `Misbehaviour` `exported.Header` and `exported.Misbehaviour` interface types have been merged and renamed to `ClientMessage` interface. diff --git a/modules/core/03-connection/types/msgs.go b/modules/core/03-connection/types/msgs.go index 28a734075df..d9bdb77e081 100644 --- a/modules/core/03-connection/types/msgs.go +++ b/modules/core/03-connection/types/msgs.go @@ -139,9 +139,6 @@ func (msg MsgConnectionOpenTry) ValidateBasic() error { if len(msg.ProofConsensus) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof of consensus state") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } if msg.ConsensusHeight.IsZero() { return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "consensus height must be non-zero") } @@ -226,9 +223,6 @@ func (msg MsgConnectionOpenAck) ValidateBasic() error { if len(msg.ProofConsensus) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof of consensus state") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } if msg.ConsensusHeight.IsZero() { return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "consensus height must be non-zero") } @@ -271,9 +265,6 @@ func (msg MsgConnectionOpenConfirm) ValidateBasic() error { if len(msg.ProofAck) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof ack") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } _, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) diff --git a/modules/core/03-connection/types/msgs_test.go b/modules/core/03-connection/types/msgs_test.go index 79928e6d660..690a8bb721d 100644 --- a/modules/core/03-connection/types/msgs_test.go +++ b/modules/core/03-connection/types/msgs_test.go @@ -144,7 +144,6 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { {"empty proofInit", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, emptyProof, suite.proof, suite.proof, clientHeight, clientHeight, signer), false}, {"empty proofClient", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, emptyProof, suite.proof, clientHeight, clientHeight, signer), false}, {"empty proofConsensus", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, emptyProof, clientHeight, clientHeight, signer), false}, - {"invalid proofHeight", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clienttypes.ZeroHeight(), clientHeight, signer), false}, {"invalid consensusHeight", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clienttypes.ZeroHeight(), signer), false}, {"empty singer", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, ""), false}, {"success", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, signer), true}, @@ -191,7 +190,6 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenAck() { {"empty proofTry", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, emptyProof, suite.proof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, {"empty proofClient", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, suite.proof, emptyProof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, {"empty proofConsensus", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, suite.proof, suite.proof, emptyProof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, - {"invalid proofHeight", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, suite.proof, suite.proof, suite.proof, clienttypes.ZeroHeight(), clientHeight, ibctesting.ConnectionVersion, signer), false}, {"invalid consensusHeight", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, suite.proof, suite.proof, suite.proof, clientHeight, clienttypes.ZeroHeight(), ibctesting.ConnectionVersion, signer), false}, {"invalid version", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, &types.Version{}, signer), false}, {"empty signer", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, ""), false}, @@ -212,7 +210,6 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenConfirm() { testMsgs := []*types.MsgConnectionOpenConfirm{ types.NewMsgConnectionOpenConfirm("test/conn1", suite.proof, clientHeight, signer), types.NewMsgConnectionOpenConfirm(connectionID, emptyProof, clientHeight, signer), - types.NewMsgConnectionOpenConfirm(connectionID, suite.proof, clienttypes.ZeroHeight(), signer), types.NewMsgConnectionOpenConfirm(connectionID, suite.proof, clientHeight, ""), types.NewMsgConnectionOpenConfirm(connectionID, suite.proof, clientHeight, signer), } @@ -224,9 +221,8 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenConfirm() { }{ {testMsgs[0], false, "invalid connection ID"}, {testMsgs[1], false, "empty proofTry"}, - {testMsgs[2], false, "invalid proofHeight"}, - {testMsgs[3], false, "empty signer"}, - {testMsgs[4], true, "success"}, + {testMsgs[2], false, "empty signer"}, + {testMsgs[3], true, "success"}, } for i, tc := range testCases { diff --git a/modules/core/04-channel/keeper/handshake_test.go b/modules/core/04-channel/keeper/handshake_test.go index 4592e9efe7c..b607017f213 100644 --- a/modules/core/04-channel/keeper/handshake_test.go +++ b/modules/core/04-channel/keeper/handshake_test.go @@ -640,7 +640,7 @@ func (suite *KeeperTestSuite) TestChanCloseInit() { path.SetChannelOrdered() err = path.EndpointA.ChanOpenInit() suite.Require().NoError(err) - + // ensure channel capability check passes suite.chainA.CreateChannelCapability(suite.chainA.GetSimApp().ScopedIBCMockKeeper, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) diff --git a/modules/core/04-channel/types/msgs.go b/modules/core/04-channel/types/msgs.go index 7558643f7bc..6792d82a478 100644 --- a/modules/core/04-channel/types/msgs.go +++ b/modules/core/04-channel/types/msgs.go @@ -95,9 +95,6 @@ func (msg MsgChannelOpenTry) ValidateBasic() error { if len(msg.ProofInit) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof init") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } if msg.Channel.State != TRYOPEN { return sdkerrors.Wrapf(ErrInvalidChannelState, "channel state must be TRYOPEN in MsgChannelOpenTry. expected: %s, got: %s", @@ -159,9 +156,6 @@ func (msg MsgChannelOpenAck) ValidateBasic() error { if len(msg.ProofTry) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof try") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } _, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) @@ -207,9 +201,6 @@ func (msg MsgChannelOpenConfirm) ValidateBasic() error { if len(msg.ProofAck) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof ack") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } _, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) @@ -294,9 +285,6 @@ func (msg MsgChannelCloseConfirm) ValidateBasic() error { if len(msg.ProofInit) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof init") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } _, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) @@ -335,9 +323,6 @@ func (msg MsgRecvPacket) ValidateBasic() error { if len(msg.ProofCommitment) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } _, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) @@ -384,9 +369,6 @@ func (msg MsgTimeout) ValidateBasic() error { if len(msg.ProofUnreceived) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty unreceived proof") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } if msg.NextSequenceRecv == 0 { return sdkerrors.Wrap(sdkerrors.ErrInvalidSequence, "next sequence receive cannot be 0") } @@ -435,9 +417,6 @@ func (msg MsgTimeoutOnClose) ValidateBasic() error { if len(msg.ProofClose) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof of closed counterparty channel end") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } _, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) @@ -479,9 +458,6 @@ func (msg MsgAcknowledgement) ValidateBasic() error { if len(msg.ProofAcked) == 0 { return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof") } - if msg.ProofHeight.IsZero() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "proof height must be non-zero") - } if len(msg.Acknowledgement) == 0 { return sdkerrors.Wrap(ErrInvalidAcknowledgement, "ack bytes cannot be empty") } diff --git a/modules/core/04-channel/types/msgs_test.go b/modules/core/04-channel/types/msgs_test.go index 8921d6bd090..661e17eefa7 100644 --- a/modules/core/04-channel/types/msgs_test.go +++ b/modules/core/04-channel/types/msgs_test.go @@ -16,7 +16,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" commitmenttypes "github.com/cosmos/ibc-go/v6/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v6/modules/core/exported" "github.com/cosmos/ibc-go/v6/testing/simapp" ) @@ -56,9 +55,7 @@ var ( packet = types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp) invalidPacket = types.NewPacket(unknownPacketData, 0, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp) - emptyProof = []byte{} - invalidProofs1 = exported.Proof(nil) - invalidProofs2 = emptyProof + emptyProof = []byte{} addr = sdk.AccAddress("testaddr111111111111").String() emptyAddr string @@ -158,7 +155,6 @@ func (suite *TypesTestSuite) TestMsgChannelOpenTryValidateBasic() { {"too long port id", types.NewMsgChannelOpenTry(invalidLongPort, version, types.ORDERED, connHops, cpportid, cpchanid, version, suite.proof, height, addr), false}, {"port id contains non-alpha", types.NewMsgChannelOpenTry(invalidPort, version, types.ORDERED, connHops, cpportid, cpchanid, version, suite.proof, height, addr), false}, {"", types.NewMsgChannelOpenTry(portid, version, types.ORDERED, connHops, cpportid, cpchanid, "", suite.proof, height, addr), true}, - {"proof height is zero", types.NewMsgChannelOpenTry(portid, version, types.ORDERED, connHops, cpportid, cpchanid, version, suite.proof, clienttypes.ZeroHeight(), addr), false}, {"invalid channel order", types.NewMsgChannelOpenTry(portid, version, types.Order(4), connHops, cpportid, cpchanid, version, suite.proof, height, addr), false}, {"connection hops more than 1 ", types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, invalidConnHops, cpportid, cpchanid, version, suite.proof, height, addr), false}, {"too short connection id", types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, invalidShortConnHops, cpportid, cpchanid, version, suite.proof, height, addr), false}, @@ -202,7 +198,6 @@ func (suite *TypesTestSuite) TestMsgChannelOpenAckValidateBasic() { {"channel id contains non-alpha", types.NewMsgChannelOpenAck(portid, invalidChannel, chanid, version, suite.proof, height, addr), false}, {"", types.NewMsgChannelOpenAck(portid, chanid, chanid, "", suite.proof, height, addr), true}, {"empty proof", types.NewMsgChannelOpenAck(portid, chanid, chanid, version, emptyProof, height, addr), false}, - {"proof height is zero", types.NewMsgChannelOpenAck(portid, chanid, chanid, version, suite.proof, clienttypes.ZeroHeight(), addr), false}, {"invalid counterparty channel id", types.NewMsgChannelOpenAck(portid, chanid, invalidShortChannel, version, suite.proof, height, addr), false}, } @@ -235,7 +230,6 @@ func (suite *TypesTestSuite) TestMsgChannelOpenConfirmValidateBasic() { {"too long channel id", types.NewMsgChannelOpenConfirm(portid, invalidLongChannel, suite.proof, height, addr), false}, {"channel id contains non-alpha", types.NewMsgChannelOpenConfirm(portid, invalidChannel, suite.proof, height, addr), false}, {"empty proof", types.NewMsgChannelOpenConfirm(portid, chanid, emptyProof, height, addr), false}, - {"proof height is zero", types.NewMsgChannelOpenConfirm(portid, chanid, suite.proof, clienttypes.ZeroHeight(), addr), false}, } for _, tc := range testCases { @@ -297,7 +291,6 @@ func (suite *TypesTestSuite) TestMsgChannelCloseConfirmValidateBasic() { {"too long channel id", types.NewMsgChannelCloseConfirm(portid, invalidLongChannel, suite.proof, height, addr), false}, {"channel id contains non-alpha", types.NewMsgChannelCloseConfirm(portid, invalidChannel, suite.proof, height, addr), false}, {"empty proof", types.NewMsgChannelCloseConfirm(portid, chanid, emptyProof, height, addr), false}, - {"proof height is zero", types.NewMsgChannelCloseConfirm(portid, chanid, suite.proof, clienttypes.ZeroHeight(), addr), false}, } for _, tc := range testCases { @@ -322,7 +315,6 @@ func (suite *TypesTestSuite) TestMsgRecvPacketValidateBasic() { expPass bool }{ {"success", types.NewMsgRecvPacket(packet, suite.proof, height, addr), true}, - {"proof height is zero", types.NewMsgRecvPacket(packet, suite.proof, clienttypes.ZeroHeight(), addr), false}, {"proof contain empty proof", types.NewMsgRecvPacket(packet, emptyProof, height, addr), false}, {"missing signer address", types.NewMsgRecvPacket(packet, suite.proof, height, emptyAddr), false}, {"invalid packet", types.NewMsgRecvPacket(invalidPacket, suite.proof, height, addr), false}, @@ -358,7 +350,6 @@ func (suite *TypesTestSuite) TestMsgTimeoutValidateBasic() { expPass bool }{ {"success", types.NewMsgTimeout(packet, 1, suite.proof, height, addr), true}, - {"proof height must be > 0", types.NewMsgTimeout(packet, 1, suite.proof, clienttypes.ZeroHeight(), addr), false}, {"seq 0", types.NewMsgTimeout(packet, 0, suite.proof, height, addr), false}, {"missing signer address", types.NewMsgTimeout(packet, 1, suite.proof, height, emptyAddr), false}, {"cannot submit an empty proof", types.NewMsgTimeout(packet, 1, emptyProof, height, addr), false}, @@ -390,7 +381,6 @@ func (suite *TypesTestSuite) TestMsgTimeoutOnCloseValidateBasic() { {"seq 0", types.NewMsgTimeoutOnClose(packet, 0, suite.proof, suite.proof, height, addr), false}, {"empty proof", types.NewMsgTimeoutOnClose(packet, 1, emptyProof, suite.proof, height, addr), false}, {"empty proof close", types.NewMsgTimeoutOnClose(packet, 1, suite.proof, emptyProof, height, addr), false}, - {"proof height is zero", types.NewMsgTimeoutOnClose(packet, 1, suite.proof, suite.proof, clienttypes.ZeroHeight(), addr), false}, {"signer address is empty", types.NewMsgTimeoutOnClose(packet, 1, suite.proof, suite.proof, height, emptyAddr), false}, {"invalid packet", types.NewMsgTimeoutOnClose(invalidPacket, 1, suite.proof, suite.proof, height, addr), false}, } @@ -417,7 +407,6 @@ func (suite *TypesTestSuite) TestMsgAcknowledgementValidateBasic() { expPass bool }{ {"success", types.NewMsgAcknowledgement(packet, packet.GetData(), suite.proof, height, addr), true}, - {"proof height must be > 0", types.NewMsgAcknowledgement(packet, packet.GetData(), suite.proof, clienttypes.ZeroHeight(), addr), false}, {"empty ack", types.NewMsgAcknowledgement(packet, nil, suite.proof, height, addr), false}, {"missing signer address", types.NewMsgAcknowledgement(packet, packet.GetData(), suite.proof, height, emptyAddr), false}, {"cannot submit an empty proof", types.NewMsgAcknowledgement(packet, packet.GetData(), emptyProof, height, addr), false}, diff --git a/modules/light-clients/06-solomachine/client_state.go b/modules/light-clients/06-solomachine/client_state.go index 0d515356eee..ed2bf259418 100644 --- a/modules/light-clients/06-solomachine/client_state.go +++ b/modules/light-clients/06-solomachine/client_state.go @@ -46,9 +46,6 @@ func (cs ClientState) GetTimestampAtHeight( cdc codec.BinaryCodec, height exported.Height, ) (uint64, error) { - if !cs.GetLatestHeight().EQ(height) { - return 0, sdkerrors.Wrapf(ErrInvalidSequence, "not latest height (%s)", height) - } return cs.ConsensusState.Timestamp, nil } @@ -102,20 +99,20 @@ func (cs ClientState) VerifyUpgradeAndUpdateState( return sdkerrors.Wrap(clienttypes.ErrInvalidUpgradeClient, "cannot upgrade solomachine client") } -// VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. +// VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the latest sequence. // The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). func (cs *ClientState) VerifyMembership( ctx sdk.Context, clientStore sdk.KVStore, cdc codec.BinaryCodec, - height exported.Height, + _ exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path, value []byte, ) error { - publicKey, sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, proof) + publicKey, sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, proof) if err != nil { return err } @@ -153,19 +150,19 @@ func (cs *ClientState) VerifyMembership( return nil } -// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. +// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at the latest sequence. // The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). func (cs *ClientState) VerifyNonMembership( ctx sdk.Context, clientStore sdk.KVStore, cdc codec.BinaryCodec, - height exported.Height, + _ exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path, ) error { - publicKey, sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, proof) + publicKey, sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, proof) if err != nil { return err } @@ -201,18 +198,12 @@ func (cs *ClientState) VerifyNonMembership( // produceVerificationArgs perfoms the basic checks on the arguments that are // shared between the verification functions and returns the public key of the -// consensus state, the unmarshalled proof representing the signature and timestamp -// along with the solo-machine sequence encoded in the proofHeight. +// consensus state, the unmarshalled proof representing the signature and timestamp. func produceVerificationArgs( cdc codec.BinaryCodec, cs *ClientState, - height exported.Height, proof []byte, ) (cryptotypes.PubKey, signing.SignatureData, uint64, uint64, error) { - if revision := height.GetRevisionNumber(); revision != 0 { - return nil, nil, 0, 0, sdkerrors.Wrapf(sdkerrors.ErrInvalidHeight, "revision must be 0 for solomachine, got revision-number: %d", revision) - } - if proof == nil { return nil, nil, 0, 0, sdkerrors.Wrap(ErrInvalidProof, "proof cannot be empty") } @@ -232,20 +223,11 @@ func produceVerificationArgs( return nil, nil, 0, 0, err } - // sequence is encoded in the revision height of height struct - sequence := height.GetRevisionHeight() - latestSequence := cs.GetLatestHeight().GetRevisionHeight() - if latestSequence != sequence { - return nil, nil, 0, 0, sdkerrors.Wrapf( - sdkerrors.ErrInvalidHeight, - "client state sequence != proof sequence (%d != %d)", latestSequence, sequence, - ) - } - if cs.ConsensusState.GetTimestamp() > timestamp { return nil, nil, 0, 0, sdkerrors.Wrapf(ErrInvalidProof, "the consensus state timestamp is greater than the signature timestamp (%d >= %d)", cs.ConsensusState.GetTimestamp(), timestamp) } + sequence := cs.GetLatestHeight().GetRevisionHeight() publicKey, err := cs.ConsensusState.GetPubKey() if err != nil { return nil, nil, 0, 0, err diff --git a/modules/light-clients/06-solomachine/client_state_test.go b/modules/light-clients/06-solomachine/client_state_test.go index 11507a5eda9..e494a484ec8 100644 --- a/modules/light-clients/06-solomachine/client_state_test.go +++ b/modules/light-clients/06-solomachine/client_state_test.go @@ -146,12 +146,11 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() { var ( clientState *solomachine.ClientState - err error - height clienttypes.Height path exported.Path proof []byte testingPath *ibctesting.Path signBytes solomachine.SignBytes + err error ) testCases := []struct { @@ -203,7 +202,7 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() { consensusStateBz, err := suite.chainA.Codec.Marshal(consensusState) suite.Require().NoError(err) - path = sm.GetConsensusStatePath(counterpartyClientIdentifier, height) + path = sm.GetConsensusStatePath(counterpartyClientIdentifier, clienttypes.NewHeight(0, 1)) signBytes = solomachine.SignBytes{ Sequence: sm.Sequence, Timestamp: sm.Time, @@ -346,7 +345,7 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() { ) commitmentBz := channeltypes.CommitPacket(suite.chainA.Codec, packet) - path = sm.GetPacketCommitmentPath(ibctesting.MockPort, ibctesting.FirstChannelID) + path = sm.GetPacketCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) signBytes = solomachine.SignBytes{ Sequence: sm.Sequence, Timestamp: sm.Time, @@ -373,7 +372,7 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() { { "success: packet acknowledgement verification", func() { - path = sm.GetPacketAcknowledgementPath(ibctesting.MockPort, ibctesting.FirstChannelID) + path = sm.GetPacketAcknowledgementPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) signBytes = solomachine.SignBytes{ Sequence: sm.Sequence, Timestamp: sm.Time, @@ -400,7 +399,7 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() { { "success: packet receipt verification", func() { - path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID) + path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) signBytes = solomachine.SignBytes{ Sequence: sm.Sequence, Timestamp: sm.Time, @@ -424,25 +423,6 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() { }, true, }, - { - "client state latest height is less than sequence", - func() { - consensusState := &solomachine.ConsensusState{ - Timestamp: sm.Time, - PublicKey: sm.ConsensusState().PublicKey, - } - - clientState = solomachine.NewClientState(sm.Sequence-1, consensusState) - }, - false, - }, - { - "height revision number is not zero", - func() { - height = clienttypes.NewHeight(1, sm.GetHeight().GetRevisionHeight()) - }, - false, - }, { "invalid path type", func() { @@ -534,7 +514,6 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() { testingPath = ibctesting.NewPath(suite.chainA, suite.chainB) clientState = sm.ClientState() - height = clienttypes.NewHeight(sm.GetHeight().GetRevisionNumber(), sm.GetHeight().GetRevisionHeight()) path = commitmenttypes.NewMerklePath("ibc", "solomachine") signBytes = solomachine.SignBytes{ @@ -567,7 +546,7 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() { err = clientState.VerifyMembership( suite.chainA.GetContext(), suite.store, suite.chainA.Codec, - height, 0, 0, // solomachine does not check delay periods + clienttypes.ZeroHeight(), 0, 0, // solomachine does not check delay periods proof, path, signBytes.Data, ) @@ -617,11 +596,10 @@ func (suite *SoloMachineTestSuite) TestVerifyNonMembership() { var ( clientState *solomachine.ClientState - err error - height clienttypes.Height path exported.Path proof []byte signBytes solomachine.SignBytes + err error ) testCases := []struct { @@ -637,7 +615,7 @@ func (suite *SoloMachineTestSuite) TestVerifyNonMembership() { { "success: packet receipt absence verification", func() { - path = suite.solomachine.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID) + path = suite.solomachine.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) signBytes = solomachine.SignBytes{ Sequence: sm.GetHeight().GetRevisionHeight(), Timestamp: sm.Time, @@ -661,25 +639,6 @@ func (suite *SoloMachineTestSuite) TestVerifyNonMembership() { }, true, }, - { - "client state latest height is less than sequence", - func() { - consensusState := &solomachine.ConsensusState{ - Timestamp: sm.Time, - PublicKey: sm.ConsensusState().PublicKey, - } - - clientState = solomachine.NewClientState(sm.Sequence-1, consensusState) - }, - false, - }, - { - "height revision number is not zero", - func() { - height = clienttypes.NewHeight(1, sm.GetHeight().GetRevisionHeight()) - }, - false, - }, { "invalid path type", func() { @@ -774,7 +733,6 @@ func (suite *SoloMachineTestSuite) TestVerifyNonMembership() { suite.Run(tc.name, func() { clientState = sm.ClientState() - height = clienttypes.NewHeight(sm.GetHeight().GetRevisionNumber(), sm.GetHeight().GetRevisionHeight()) path = commitmenttypes.NewMerklePath("ibc", "solomachine") signBytes = solomachine.SignBytes{ @@ -807,7 +765,7 @@ func (suite *SoloMachineTestSuite) TestVerifyNonMembership() { err = clientState.VerifyNonMembership( suite.chainA.GetContext(), suite.store, suite.chainA.Codec, - height, 0, 0, // solomachine does not check delay periods + clienttypes.ZeroHeight(), 0, 0, // solomachine does not check delay periods proof, path, ) @@ -843,11 +801,6 @@ func (suite *SoloMachineTestSuite) TestGetTimestampAtHeight() { expValue: suite.solomachine.ClientState().ConsensusState.Timestamp, expPass: true, }, - { - name: "get timestamp at height not exists", - clientState: suite.solomachine.ClientState(), - height: suite.solomachine.ClientState().GetLatestHeight().Increment(), - }, } for i, tc := range testCases { diff --git a/modules/light-clients/06-solomachine/solomachine_test.go b/modules/light-clients/06-solomachine/solomachine_test.go index 51e01386797..5d27e2edf8e 100644 --- a/modules/light-clients/06-solomachine/solomachine_test.go +++ b/modules/light-clients/06-solomachine/solomachine_test.go @@ -2,6 +2,7 @@ package solomachine_test import ( "testing" + "time" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -11,10 +12,17 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v6/modules/core/24-host" "github.com/cosmos/ibc-go/v6/modules/core/exported" solomachine "github.com/cosmos/ibc-go/v6/modules/light-clients/06-solomachine" ibctesting "github.com/cosmos/ibc-go/v6/testing" + "github.com/cosmos/ibc-go/v6/testing/mock" +) + +var ( + channelIDSolomachine = "channel-on-solomachine" // channelID generated on solo machine side ) type SoloMachineTestSuite struct { @@ -46,6 +54,116 @@ func TestSoloMachineTestSuite(t *testing.T) { suite.Run(t, new(SoloMachineTestSuite)) } +func (suite *SoloMachineTestSuite) SetupSolomachine() string { + clientID := suite.solomachine.CreateClient(suite.chainA) + + connectionID := suite.solomachine.ConnOpenInit(suite.chainA, clientID) + + // open try is not necessary as the solo machine implementation is mocked + + suite.solomachine.ConnOpenAck(suite.chainA, clientID, connectionID) + + // open confirm is not necessary as the solo machine implementation is mocked + + channelID := suite.solomachine.ChanOpenInit(suite.chainA, connectionID) + + // open try is not necessary as the solo machine implementation is mocked + + suite.solomachine.ChanOpenAck(suite.chainA, channelID) + + // open confirm is not necessary as the solo machine implementation is mocked + + return channelID +} + +func (suite *SoloMachineTestSuite) TestRecvPacket() { + channelID := suite.SetupSolomachine() + + packet := channeltypes.NewPacket( + mock.MockPacketData, + 1, + mock.PortID, + channelIDSolomachine, + mock.PortID, + channelID, + clienttypes.ZeroHeight(), + uint64(suite.chainA.GetContext().BlockTime().Add(time.Hour).UnixNano()), + ) + + // send packet is not necessary as the solo machine implementation is mocked + + suite.solomachine.RecvPacket(suite.chainA, packet) + + // close init is not necessary as the solomachine implementation is mocked + + suite.solomachine.ChanCloseConfirm(suite.chainA, channelID) +} + +func (suite *SoloMachineTestSuite) TestAcknowledgePacket() { + channelID := suite.SetupSolomachine() + + packet := channeltypes.NewPacket( + mock.MockPacketData, + 1, + mock.PortID, + channelID, + mock.PortID, + channelIDSolomachine, + clienttypes.ZeroHeight(), + uint64(suite.chainA.GetContext().BlockTime().Add(time.Hour).UnixNano()), + ) + + suite.solomachine.SendPacket(suite.chainA, packet) + + // recv packet is not necessary as the solo machine implementation is mocked + + suite.solomachine.AcknowledgePacket(suite.chainA, packet) + + // close init is not necessary as the solomachine implementation is mocked + + suite.solomachine.ChanCloseConfirm(suite.chainA, channelID) +} + +func (suite *SoloMachineTestSuite) TestTimeout() { + channelID := suite.SetupSolomachine() + + packet := channeltypes.NewPacket( + mock.MockPacketData, + 1, + mock.PortID, + channelID, + mock.PortID, + channelIDSolomachine, + clienttypes.ZeroHeight(), + 1, + ) + + suite.solomachine.SendPacket(suite.chainA, packet) + + suite.solomachine.TimeoutPacket(suite.chainA, packet) + + suite.solomachine.ChanCloseConfirm(suite.chainA, channelID) +} + +func (suite *SoloMachineTestSuite) TestTimeoutOnClose() { + channelID := suite.SetupSolomachine() + + packet := channeltypes.NewPacket( + mock.MockPacketData, + 1, + mock.PortID, + channelID, + mock.PortID, + channelIDSolomachine, + clienttypes.ZeroHeight(), + 1, + ) + + suite.solomachine.SendPacket(suite.chainA, packet) + + suite.solomachine.TimeoutPacketOnClose(suite.chainA, packet, channelID) +} + func (suite *SoloMachineTestSuite) GetSequenceFromStore() uint64 { bz := suite.store.Get(host.ClientStateKey()) suite.Require().NotNil(bz) diff --git a/modules/light-clients/07-tendermint/client_state.go b/modules/light-clients/07-tendermint/client_state.go index b1de29b44b7..9f6da36bf00 100644 --- a/modules/light-clients/07-tendermint/client_state.go +++ b/modules/light-clients/07-tendermint/client_state.go @@ -202,6 +202,7 @@ func (cs ClientState) Initialize(ctx sdk.Context, _ codec.BinaryCodec, clientSto // VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. // The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. func (cs ClientState) VerifyMembership( ctx sdk.Context, clientStore sdk.KVStore, @@ -248,6 +249,7 @@ func (cs ClientState) VerifyMembership( // VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. // The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. func (cs ClientState) VerifyNonMembership( ctx sdk.Context, clientStore sdk.KVStore, diff --git a/testing/solomachine.go b/testing/solomachine.go index 2397d13167f..8939936824d 100644 --- a/testing/solomachine.go +++ b/testing/solomachine.go @@ -13,10 +13,20 @@ import ( "github.com/stretchr/testify/require" clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v6/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" commitmenttypes "github.com/cosmos/ibc-go/v6/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v6/modules/core/24-host" "github.com/cosmos/ibc-go/v6/modules/core/exported" solomachine "github.com/cosmos/ibc-go/v6/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint" + "github.com/cosmos/ibc-go/v6/testing/mock" +) + +var ( + clientIDSolomachine = "client-on-solomachine" // clientID generated on solo machine side + connectionIDSolomachine = "connection-on-solomachine" // connectionID generated on solo machine side + channelIDSolomachine = "channel-on-solomachine" // channelID generated on solo machine side ) // Solomachine is a testing helper used to simulate a counterparty @@ -104,6 +114,21 @@ func (solo *Solomachine) GetHeight() exported.Height { return clienttypes.NewHeight(0, solo.Sequence) } +// CreateClient creates an on-chain client on the provided chain. +func (solo *Solomachine) CreateClient(chain *TestChain) string { + msgCreateClient, err := clienttypes.NewMsgCreateClient(solo.ClientState(), solo.ConsensusState(), chain.SenderAccount.GetAddress().String()) + require.NoError(solo.t, err) + + res, err := chain.SendMsgs(msgCreateClient) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + clientID, err := ParseClientIDFromEvents(res.GetEvents()) + require.NoError(solo.t, err) + + return clientID +} + // CreateHeader generates a new private/public key pair and creates the // necessary signature to construct a valid solo machine header. // A new diversifier will be used as well @@ -218,6 +243,178 @@ func (solo *Solomachine) CreateMisbehaviour() *solomachine.Misbehaviour { } } +// ConnOpenInit initializes a connection on the provided chain given a solo machine clientID. +func (solo *Solomachine) ConnOpenInit(chain *TestChain, clientID string) string { + msgConnOpenInit := connectiontypes.NewMsgConnectionOpenInit( + clientID, + clientIDSolomachine, // clientID generated on solo machine side + chain.GetPrefix(), DefaultOpenInitVersion, DefaultDelayPeriod, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgConnOpenInit) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + connectionID, err := ParseConnectionIDFromEvents(res.GetEvents()) + require.NoError(solo.t, err) + + return connectionID +} + +// ConnOpenAck performs the connection open ack handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ConnOpenAck(chain *TestChain, clientID, connectionID string) { + proofTry := solo.GenerateConnOpenTryProof(clientID, connectionID) + + clientState := ibctm.NewClientState(chain.ChainID, DefaultTrustLevel, TrustingPeriod, UnbondingPeriod, MaxClockDrift, chain.LastHeader.GetHeight().(clienttypes.Height), commitmenttypes.GetSDKSpecs(), UpgradePath) + proofClient := solo.GenerateClientStateProof(clientState) + + consensusState := chain.LastHeader.ConsensusState() + consensusHeight := chain.LastHeader.GetHeight() + proofConsensus := solo.GenerateConsensusStateProof(consensusState, consensusHeight) + + msgConnOpenAck := connectiontypes.NewMsgConnectionOpenAck( + connectionID, connectionIDSolomachine, clientState, + proofTry, proofClient, proofConsensus, + clienttypes.ZeroHeight(), clientState.GetLatestHeight().(clienttypes.Height), + ConnectionVersion, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgConnOpenAck) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// ChanOpenInit initializes a channel on the provided chain given a solo machine connectionID. +func (solo *Solomachine) ChanOpenInit(chain *TestChain, connectionID string) string { + msgChanOpenInit := channeltypes.NewMsgChannelOpenInit( + mock.PortID, + mock.Version, + channeltypes.UNORDERED, + []string{connectionID}, + mock.PortID, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanOpenInit) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + if res, ok := res.MsgResponses[0].GetCachedValue().(*channeltypes.MsgChannelOpenInitResponse); ok { + return res.ChannelId + } + + return "" +} + +// ChanOpenAck performs the channel open ack handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ChanOpenAck(chain *TestChain, channelID string) { + proofTry := solo.GenerateChanOpenTryProof(channelID) + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + mock.PortID, + channelID, + channelIDSolomachine, + mock.Version, + proofTry, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanOpenAck) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// ChanCloseConfirm performs the channel close confirm handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ChanCloseConfirm(chain *TestChain, channelID string) { + proofInit := solo.GenerateChanClosedProof(channelID) + msgChanCloseConfirm := channeltypes.NewMsgChannelCloseConfirm( + mock.PortID, + channelID, + proofInit, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanCloseConfirm) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// SendPacket mocks sending a packet by setting a packet commitment directly. +func (solo *Solomachine) SendPacket(chain *TestChain, packet channeltypes.Packet) { + commitmentHash := channeltypes.CommitPacket(chain.Codec, packet) + chain.GetSimApp().IBCKeeper.ChannelKeeper.SetPacketCommitment(chain.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), commitmentHash) +} + +// RecvPacket creates a commitment proof and broadcasts a new MsgRecvPacket. +func (solo *Solomachine) RecvPacket(chain *TestChain, packet channeltypes.Packet) { + proofCommitment := solo.GenerateCommitmentProof(packet) + msgRecvPacket := channeltypes.NewMsgRecvPacket( + packet, + proofCommitment, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgRecvPacket) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// AcknowledgePacket creates an acknowledgement proof and broadcasts a MsgAcknowledgement. +func (solo *Solomachine) AcknowledgePacket(chain *TestChain, packet channeltypes.Packet) { + proofAck := solo.GenerateAcknowledgementProof(packet) + msgAcknowledgement := channeltypes.NewMsgAcknowledgement( + packet, mock.MockAcknowledgement.Acknowledgement(), + proofAck, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgAcknowledgement) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// TimeoutPacket creates a unreceived packet proof and broadcasts a MsgTimeout. +func (solo *Solomachine) TimeoutPacket(chain *TestChain, packet channeltypes.Packet) { + proofUnreceived := solo.GenerateReceiptAbsenceProof(packet) + msgTimeout := channeltypes.NewMsgTimeout( + packet, + 1, // nextSequenceRecv is unused for UNORDERED channels + proofUnreceived, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgTimeout) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// TimeoutPacket creates a channel closed and unreceived packet proof and broadcasts a MsgTimeoutOnClose. +func (solo *Solomachine) TimeoutPacketOnClose(chain *TestChain, packet channeltypes.Packet, channelID string) { + proofClosed := solo.GenerateChanClosedProof(channelID) + proofUnreceived := solo.GenerateReceiptAbsenceProof(packet) + msgTimeout := channeltypes.NewMsgTimeoutOnClose( + packet, + 1, // nextSequenceRecv is unused for UNORDERED channels + proofUnreceived, + proofClosed, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgTimeout) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + // GenerateSignature uses the stored private keys to generate a signature // over the sign bytes with each key. If the amount of keys is greater than // 1 then a multisig data type is returned. @@ -253,6 +450,159 @@ func (solo *Solomachine) GenerateSignature(signBytes []byte) []byte { return bz } +// GenerateProof takes in solo machine sign bytes, generates a signature and marshals it as a proof. +// The solo machine sequence is incremented. +func (solo *Solomachine) GenerateProof(signBytes *solomachine.SignBytes) []byte { + bz, err := solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: solo.Time, + } + proof, err := solo.cdc.Marshal(signatureDoc) + require.NoError(solo.t, err) + + solo.Sequence++ + + return proof +} + +// GenerateClientStateProof generates the proof of the client state required for the connection open try and ack handshake steps. +// The client state should be the self client states of the tendermint chain. +func (solo *Solomachine) GenerateClientStateProof(clientState exported.ClientState) []byte { + data, err := clienttypes.MarshalClientState(solo.cdc, clientState) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solo.GetClientStatePath(clientIDSolomachine).String()), + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateConsensusStateProof generates the proof of the consensus state required for the connection open try and ack handshake steps. +// The consensus state should be the self consensus states of the tendermint chain. +func (solo *Solomachine) GenerateConsensusStateProof(consensusState exported.ConsensusState, consensusHeight exported.Height) []byte { + data, err := clienttypes.MarshalConsensusState(solo.cdc, consensusState) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solo.GetConsensusStatePath(clientIDSolomachine, consensusHeight).String()), + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateConnOpenTryProof generates the proofTry required for the connection open ack handshake step. +// The clientID, connectionID provided represent the clientID and connectionID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateConnOpenTryProof(counterpartyClientID, counterpartyConnectionID string) []byte { + counterparty := connectiontypes.NewCounterparty(counterpartyClientID, counterpartyConnectionID, prefix) + connection := connectiontypes.NewConnectionEnd(connectiontypes.TRYOPEN, clientIDSolomachine, counterparty, []*connectiontypes.Version{ConnectionVersion}, DefaultDelayPeriod) + + data, err := solo.cdc.Marshal(&connection) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solo.GetConnectionStatePath(connectionIDSolomachine).String()), + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateChanOpenTryProof generates the proofTry required for the channel open ack handshake step. +// The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateChanOpenTryProof(counterpartyChannelID string) []byte { + counterparty := channeltypes.NewCounterparty(mock.PortID, counterpartyChannelID) + channel := channeltypes.NewChannel(channeltypes.TRYOPEN, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, mock.Version) + + data, err := solo.cdc.Marshal(&channel) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solo.GetChannelStatePath(mock.PortID, channelIDSolomachine).String()), + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateChanClosedProof generates a channel closed proof. +// The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateChanClosedProof(counterpartyChannelID string) []byte { + counterparty := channeltypes.NewCounterparty(mock.PortID, counterpartyChannelID) + channel := channeltypes.NewChannel(channeltypes.CLOSED, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, mock.Version) + + data, err := solo.cdc.Marshal(&channel) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solo.GetChannelStatePath(mock.PortID, channelIDSolomachine).String()), + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateCommitmentProof generates a commitment proof for the provided packet. +func (solo *Solomachine) GenerateCommitmentProof(packet channeltypes.Packet) []byte { + commitment := channeltypes.CommitPacket(solo.cdc, packet) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solo.GetPacketCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()).String()), + Data: commitment, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateAcknowledgementProof generates an acknowledgement proof. +func (solo *Solomachine) GenerateAcknowledgementProof(packet channeltypes.Packet) []byte { + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solo.GetPacketAcknowledgementPath(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()).String()), + Data: channeltypes.CommitAcknowledgement(mock.MockAcknowledgement.Acknowledgement()), + } + + return solo.GenerateProof(signBytes) +} + +// GenerateReceiptAbsenceProof generates a receipt absence proof for the provided packet. +func (solo *Solomachine) GenerateReceiptAbsenceProof(packet channeltypes.Packet) []byte { + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solo.GetPacketReceiptPath(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()).String()), + Data: nil, + } + return solo.GenerateProof(signBytes) +} + // GetClientStatePath returns the commitment path for the client state. func (solo *Solomachine) GetClientStatePath(counterpartyClientIdentifier string) commitmenttypes.MerklePath { path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullClientStatePath(counterpartyClientIdentifier))) @@ -288,8 +638,8 @@ func (solo *Solomachine) GetChannelStatePath(portID, channelID string) commitmen } // GetPacketCommitmentPath returns the commitment path for a packet commitment. -func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string) commitmenttypes.MerklePath { - commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, solo.Sequence)) +func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string, sequence uint64) commitmenttypes.MerklePath { + commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, sequence)) path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath) require.NoError(solo.t, err) @@ -297,8 +647,8 @@ func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string) commi } // GetPacketAcknowledgementPath returns the commitment path for a packet acknowledgement. -func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string) commitmenttypes.MerklePath { - ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, solo.Sequence)) +func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string, sequence uint64) commitmenttypes.MerklePath { + ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, sequence)) path, err := commitmenttypes.ApplyPrefix(prefix, ackPath) require.NoError(solo.t, err) @@ -307,8 +657,8 @@ func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string) // GetPacketReceiptPath returns the commitment path for a packet receipt // and an absent receipts. -func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string) commitmenttypes.MerklePath { - receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptPath(portID, channelID, solo.Sequence)) +func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string, sequence uint64) commitmenttypes.MerklePath { + receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptPath(portID, channelID, sequence)) path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath) require.NoError(solo.t, err)