x/ibc: implement 09-localhost per specification (#5769)
Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
This commit is contained in:
parent
968fb1f040
commit
de00a7fed1
|
@ -103,8 +103,9 @@ information on how to implement the new `Keyring` interface.
|
|||
* [ICS 020 - Fungible Token Transfer](https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer) module
|
||||
* [ICS 023 - Vector Commitments](https://github.com/cosmos/ics/tree/master/spec/ics-023-vector-commitments) subpackage
|
||||
* (ibc/ante) Implement IBC `AnteHandler` as per [ADR 15 - IBC Packet Receiver](https://github.com/cosmos/tree/master/docs/architecture/adr-015-ibc-packet-receiver.md).
|
||||
* (x/capability) [\#5828](https://github.com/cosmos/cosmos-sdk/pull/5828) Capability module integration as outlined in [ADR 3 - Dynamic Capability Store](https://github.com/cosmos/tree/master/docs/architecture/adr-003-dynamic-capability-store.md).
|
||||
* (x/params) [\#6005](https://github.com/cosmos/cosmos-sdk/pull/6005) Add new CLI command for querying raw x/params parameters by subspace and key.
|
||||
* (x/capability) [\#5828](https://github.com/cosmos/cosmos-sdk/pull/5828) Capability module integration as outlined in [ADR 3 - Dynamic Capability Store](https://github.com/cosmos/tree/master/docs/architecture/adr-003-dynamic-capability-store.md).
|
||||
* (x/params) [\#6005](https://github.com/cosmos/cosmos-sdk/pull/6005) Add new CLI command for querying raw x/params parameters by subspace and key.
|
||||
* (x/ibc) [\#5769] Implementation of localhost client.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
|
@ -142,6 +142,7 @@ type ClientType byte
|
|||
// available client types
|
||||
const (
|
||||
Tendermint ClientType = iota + 1 // 1
|
||||
Localhost
|
||||
)
|
||||
|
||||
// string representation of the client types
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
||||
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
||||
localhost "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost"
|
||||
)
|
||||
|
||||
// HandleMsgCreateClient defines the sdk.Handler for MsgCreateClient
|
||||
|
@ -27,6 +28,8 @@ func HandleMsgCreateClient(ctx sdk.Context, k Keeper, msg exported.MsgCreateClie
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case exported.Localhost:
|
||||
clientState = localhost.NewClientState(ctx.MultiStore().GetKVStore(k.GetStoreKey()))
|
||||
default:
|
||||
return nil, sdkerrors.Wrap(ErrInvalidClientType, msg.GetClientType())
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
|
|||
return ctx.Logger().With("module", fmt.Sprintf("x/%s/%s", ibctypes.ModuleName, types.SubModuleName))
|
||||
}
|
||||
|
||||
func (k Keeper) GetStoreKey() sdk.StoreKey {
|
||||
return k.storeKey
|
||||
}
|
||||
|
||||
// GetClientState gets a particular client from the store
|
||||
func (k Keeper) GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) {
|
||||
store := k.clientStore(ctx, clientID)
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
package localhost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
||||
connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported"
|
||||
channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported"
|
||||
commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported"
|
||||
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
||||
ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types"
|
||||
)
|
||||
|
||||
var _ clientexported.ClientState = ClientState{}
|
||||
|
||||
// ClientState requires (read-only) access to keys outside the client prefix.
|
||||
type ClientState struct {
|
||||
ctx sdk.Context
|
||||
store types.KVStore
|
||||
}
|
||||
|
||||
// NewClientState creates a new ClientState instance
|
||||
func NewClientState(store types.KVStore) ClientState {
|
||||
return ClientState{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext updates the client state context to provide the chain ID and latest height
|
||||
func (cs *ClientState) WithContext(ctx sdk.Context) {
|
||||
cs.ctx = ctx
|
||||
}
|
||||
|
||||
// GetID returns the loop-back client state identifier.
|
||||
func (cs ClientState) GetID() string {
|
||||
return clientexported.Localhost.String()
|
||||
}
|
||||
|
||||
// GetChainID returns an empty string
|
||||
func (cs ClientState) GetChainID() string {
|
||||
return cs.ctx.ChainID()
|
||||
}
|
||||
|
||||
// ClientType is localhost.
|
||||
func (cs ClientState) ClientType() clientexported.ClientType {
|
||||
return clientexported.Localhost
|
||||
}
|
||||
|
||||
// GetLatestHeight returns the block height from the stored context.
|
||||
func (cs ClientState) GetLatestHeight() uint64 {
|
||||
return uint64(cs.ctx.BlockHeight())
|
||||
}
|
||||
|
||||
// IsFrozen returns false.
|
||||
func (cs ClientState) IsFrozen() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// VerifyClientConsensusState verifies a proof of the consensus
|
||||
// state of the loop-back client.
|
||||
// VerifyClientConsensusState verifies a proof of the consensus state of the
|
||||
// Tendermint client stored on the target machine.
|
||||
func (cs ClientState) VerifyClientConsensusState(
|
||||
cdc *codec.Codec,
|
||||
_ commitmentexported.Root,
|
||||
height uint64,
|
||||
_ string,
|
||||
consensusHeight uint64,
|
||||
prefix commitmentexported.Prefix,
|
||||
_ commitmentexported.Proof,
|
||||
consensusState clientexported.ConsensusState,
|
||||
) error {
|
||||
path, err := commitmenttypes.ApplyPrefix(prefix, consensusStatePath(cs.GetID()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := cs.store.Get([]byte(path.String()))
|
||||
if len(data) == 0 {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedClientConsensusStateVerification, "not found")
|
||||
}
|
||||
|
||||
var prevConsensusState exported.ConsensusState
|
||||
cdc.MustUnmarshalBinaryBare(data, &prevConsensusState)
|
||||
if consensusState != prevConsensusState {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedClientConsensusStateVerification, "not equal")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyConnectionState verifies a proof of the connection state of the
|
||||
// specified connection end stored locally.
|
||||
func (cs ClientState) VerifyConnectionState(
|
||||
cdc *codec.Codec,
|
||||
_ uint64,
|
||||
prefix commitmentexported.Prefix,
|
||||
_ commitmentexported.Proof,
|
||||
connectionID string,
|
||||
connectionEnd connectionexported.ConnectionI,
|
||||
_ clientexported.ConsensusState,
|
||||
) error {
|
||||
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.ConnectionPath(connectionID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bz := cs.store.Get([]byte(path.String()))
|
||||
if bz == nil {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedConnectionStateVerification, "not found")
|
||||
}
|
||||
|
||||
var prevConnectionState connectionexported.ConnectionI
|
||||
cdc.MustUnmarshalBinaryBare(bz, &prevConnectionState)
|
||||
if connectionEnd != prevConnectionState {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedConnectionStateVerification, "not equal")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyChannelState verifies a proof of the channel state of the specified
|
||||
// channel end, under the specified port, stored on the local machine.
|
||||
func (cs ClientState) VerifyChannelState(
|
||||
cdc *codec.Codec,
|
||||
_ uint64,
|
||||
prefix commitmentexported.Prefix,
|
||||
_ commitmentexported.Proof,
|
||||
portID,
|
||||
channelID string,
|
||||
channel channelexported.ChannelI,
|
||||
_ clientexported.ConsensusState,
|
||||
) error {
|
||||
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.ChannelPath(portID, channelID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bz := cs.store.Get([]byte(path.String()))
|
||||
if bz == nil {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedChannelStateVerification, "not found")
|
||||
}
|
||||
|
||||
var prevChannelState channelexported.ChannelI
|
||||
cdc.MustUnmarshalBinaryBare(bz, &prevChannelState)
|
||||
if channel != prevChannelState {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedChannelStateVerification, "not equal")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyPacketCommitment verifies a proof of an outgoing packet commitment at
|
||||
// the specified port, specified channel, and specified sequence.
|
||||
func (cs ClientState) VerifyPacketCommitment(
|
||||
_ uint64,
|
||||
prefix commitmentexported.Prefix,
|
||||
_ commitmentexported.Proof,
|
||||
portID,
|
||||
channelID string,
|
||||
sequence uint64,
|
||||
commitmentBytes []byte,
|
||||
_ clientexported.ConsensusState,
|
||||
) error {
|
||||
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.PacketCommitmentPath(portID, channelID, sequence))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := cs.store.Get([]byte(path.String()))
|
||||
if len(data) == 0 {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedPacketCommitmentVerification, "not found")
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, commitmentBytes) {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedPacketCommitmentVerification, "not equal")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyPacketAcknowledgement verifies a proof of an incoming packet
|
||||
// acknowledgement at the specified port, specified channel, and specified sequence.
|
||||
func (cs ClientState) VerifyPacketAcknowledgement(
|
||||
_ uint64,
|
||||
prefix commitmentexported.Prefix,
|
||||
_ commitmentexported.Proof,
|
||||
portID,
|
||||
channelID string,
|
||||
sequence uint64,
|
||||
acknowledgement []byte,
|
||||
_ clientexported.ConsensusState,
|
||||
) error {
|
||||
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.PacketAcknowledgementPath(portID, channelID, sequence))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := cs.store.Get([]byte(path.String()))
|
||||
if len(data) == 0 {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedPacketAckVerification, "not found")
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, acknowledgement) {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedPacketAckVerification, "not equal")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyPacketAcknowledgementAbsence verifies a proof of the absence of an
|
||||
// incoming packet acknowledgement at the specified port, specified channel, and
|
||||
// specified sequence.
|
||||
func (cs ClientState) VerifyPacketAcknowledgementAbsence(
|
||||
_ uint64,
|
||||
prefix commitmentexported.Prefix,
|
||||
_ commitmentexported.Proof,
|
||||
portID,
|
||||
channelID string,
|
||||
sequence uint64,
|
||||
_ clientexported.ConsensusState,
|
||||
) error {
|
||||
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.PacketAcknowledgementPath(portID, channelID, sequence))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := cs.store.Get([]byte(path.String()))
|
||||
if data != nil {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedPacketAckAbsenceVerification, "expected no ack absence")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyNextSequenceRecv verifies a proof of the next sequence number to be
|
||||
// received of the specified channel at the specified port.
|
||||
func (cs ClientState) VerifyNextSequenceRecv(
|
||||
_ uint64,
|
||||
prefix commitmentexported.Prefix,
|
||||
_ commitmentexported.Proof,
|
||||
portID,
|
||||
channelID string,
|
||||
nextSequenceRecv uint64,
|
||||
_ clientexported.ConsensusState,
|
||||
) error {
|
||||
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.NextSequenceRecvPath(portID, channelID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := cs.store.Get([]byte(path.String()))
|
||||
if len(data) == 0 {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedNextSeqRecvVerification, "not found")
|
||||
}
|
||||
|
||||
prevSequenceRecv := binary.BigEndian.Uint64(data)
|
||||
if prevSequenceRecv != nextSequenceRecv {
|
||||
return sdkerrors.Wrap(clienttypes.ErrFailedNextSeqRecvVerification, "not equal")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// consensusStatePath takes an Identifier and returns a Path under which to
|
||||
// store the consensus state of a client.
|
||||
func consensusStatePath(clientID string) string {
|
||||
return fmt.Sprintf("consensusState/%s", clientID)
|
||||
}
|
|
@ -0,0 +1,410 @@
|
|||
package localhost_test
|
||||
|
||||
import (
|
||||
connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection"
|
||||
connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported"
|
||||
channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel"
|
||||
channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported"
|
||||
localhost "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost"
|
||||
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
||||
)
|
||||
|
||||
const (
|
||||
testConnectionID = "connectionid"
|
||||
testPortID = "testportid"
|
||||
testChannelID = "testchannelid"
|
||||
testSequence = 1
|
||||
)
|
||||
|
||||
func (suite *LocalhostTestSuite) TestVerifyClientConsensusState() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientState localhost.ClientState
|
||||
consensusState localhost.ConsensusState
|
||||
prefix commitmenttypes.MerklePrefix
|
||||
proof commitmenttypes.MerkleProof
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
name: "ApplyPrefix failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.MerklePrefix{},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "proof verification failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
proof: commitmenttypes.MerkleProof{},
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
err := tc.clientState.VerifyClientConsensusState(
|
||||
suite.cdc, tc.consensusState.Root, height, "chainA", tc.consensusState.GetHeight(), tc.prefix, tc.proof, tc.consensusState,
|
||||
|
||||
// suite.cdc, height, tc.prefix, tc.proof, tc.consensusState,
|
||||
)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *LocalhostTestSuite) TestVerifyConnectionState() {
|
||||
counterparty := connection.NewCounterparty("clientB", testConnectionID, commitmenttypes.NewMerklePrefix([]byte("ibc")))
|
||||
conn := connection.NewConnectionEnd(connectionexported.OPEN, "clientA", counterparty, []string{"1.0.0"})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientState localhost.ClientState
|
||||
connection connection.ConnectionEnd
|
||||
consensusState localhost.ConsensusState
|
||||
prefix commitmenttypes.MerklePrefix
|
||||
proof commitmenttypes.MerkleProof
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
name: "ApplyPrefix failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
connection: conn,
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.MerklePrefix{},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "proof verification failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
connection: conn,
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
proof: commitmenttypes.MerkleProof{},
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
err := tc.clientState.VerifyConnectionState(
|
||||
suite.cdc, height, tc.prefix, tc.proof, testConnectionID, tc.connection, tc.consensusState,
|
||||
)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *LocalhostTestSuite) TestVerifyChannelState() {
|
||||
counterparty := channel.NewCounterparty(testPortID, testChannelID)
|
||||
ch := channel.NewChannel(channelexported.OPEN, channelexported.ORDERED, counterparty, []string{testConnectionID}, "1.0.0")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientState localhost.ClientState
|
||||
channel channel.Channel
|
||||
consensusState localhost.ConsensusState
|
||||
prefix commitmenttypes.MerklePrefix
|
||||
proof commitmenttypes.MerkleProof
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
name: "ApplyPrefix failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
channel: ch,
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.MerklePrefix{},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "latest client height < height",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
channel: ch,
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "proof verification failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
channel: ch,
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
proof: commitmenttypes.MerkleProof{},
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
err := tc.clientState.VerifyChannelState(
|
||||
suite.cdc, height, tc.prefix, tc.proof, testPortID, testChannelID, tc.channel, tc.consensusState,
|
||||
)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *LocalhostTestSuite) TestVerifyPacketCommitment() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientState localhost.ClientState
|
||||
commitment []byte
|
||||
consensusState localhost.ConsensusState
|
||||
prefix commitmenttypes.MerklePrefix
|
||||
proof commitmenttypes.MerkleProof
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
name: "ApplyPrefix failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
commitment: []byte{},
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.MerklePrefix{},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "latest client height < height",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
commitment: []byte{},
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "client is frozen",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
commitment: []byte{},
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "proof verification failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
commitment: []byte{},
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
proof: commitmenttypes.MerkleProof{},
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
err := tc.clientState.VerifyPacketCommitment(
|
||||
height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.commitment, tc.consensusState,
|
||||
)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *LocalhostTestSuite) TestVerifyPacketAcknowledgement() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientState localhost.ClientState
|
||||
ack []byte
|
||||
consensusState localhost.ConsensusState
|
||||
prefix commitmenttypes.MerklePrefix
|
||||
proof commitmenttypes.MerkleProof
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
name: "ApplyPrefix failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
ack: []byte{},
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.MerklePrefix{},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "latest client height < height",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
ack: []byte{},
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "client is frozen",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
ack: []byte{},
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "proof verification failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
ack: []byte{},
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
proof: commitmenttypes.MerkleProof{},
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
err := tc.clientState.VerifyPacketAcknowledgement(
|
||||
height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.ack, tc.consensusState,
|
||||
)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *LocalhostTestSuite) TestVerifyPacketAcknowledgementAbsence() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientState localhost.ClientState
|
||||
consensusState localhost.ConsensusState
|
||||
prefix commitmenttypes.MerklePrefix
|
||||
proof commitmenttypes.MerkleProof
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
name: "ApplyPrefix failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.MerklePrefix{},
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
err := tc.clientState.VerifyPacketAcknowledgementAbsence(
|
||||
height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.consensusState,
|
||||
)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *LocalhostTestSuite) TestVerifyNextSeqRecv() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientState localhost.ClientState
|
||||
consensusState localhost.ConsensusState
|
||||
prefix commitmenttypes.MerklePrefix
|
||||
proof commitmenttypes.MerkleProof
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
name: "ApplyPrefix failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.MerklePrefix{},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "latest client height < height",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "client is frozen",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
name: "proof verification failed",
|
||||
clientState: localhost.NewClientState(suite.store),
|
||||
consensusState: localhost.ConsensusState{
|
||||
Root: commitmenttypes.NewMerkleRoot([]byte{}),
|
||||
},
|
||||
prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")),
|
||||
proof: commitmenttypes.MerkleProof{},
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
err := tc.clientState.VerifyNextSequenceRecv(
|
||||
height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.consensusState,
|
||||
)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package localhost
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
// SubModuleCdc defines the IBC localhost client codec.
|
||||
var SubModuleCdc *codec.Codec
|
||||
|
||||
// RegisterCodec registers the localhost types
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(ClientState{}, "ibc/client/localhost/ClientState", nil)
|
||||
cdc.RegisterConcrete(ConsensusState{}, "ibc/client/localhost/ConsensusState", nil)
|
||||
cdc.RegisterConcrete(Header{}, "ibc/client/localhost/Header", nil)
|
||||
cdc.RegisterConcrete(Evidence{}, "ibc/client/localhost/Evidence", nil)
|
||||
|
||||
SetSubModuleCodec(cdc)
|
||||
}
|
||||
|
||||
// SetSubModuleCodec sets the ibc localhost client codec
|
||||
func SetSubModuleCodec(cdc *codec.Codec) {
|
||||
SubModuleCdc = cdc
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package localhost
|
||||
|
||||
import (
|
||||
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||
commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported"
|
||||
)
|
||||
|
||||
// ConsensusState defines a Localhost consensus state
|
||||
type ConsensusState struct {
|
||||
Root commitmentexported.Root `json:"root" yaml:"root"`
|
||||
}
|
||||
|
||||
// ClientType returns Localhost
|
||||
func (ConsensusState) ClientType() clientexported.ClientType {
|
||||
return clientexported.Localhost
|
||||
}
|
||||
|
||||
// GetRoot returns the commitment Root for the specific
|
||||
func (cs ConsensusState) GetRoot() commitmentexported.Root {
|
||||
return cs.Root
|
||||
}
|
||||
|
||||
// GetHeight returns the height for the specific consensus state
|
||||
func (cs ConsensusState) GetHeight() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// ValidateBasic defines a basic validation for the localhost consensus state.
|
||||
func (cs ConsensusState) ValidateBasic() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
Package localhost implements a concrete `ConsensusState`, `Header`,
|
||||
`Misbehaviour` and `Equivocation` types for the loop-back client.
|
||||
*/
|
||||
package localhost
|
|
@ -0,0 +1,67 @@
|
|||
package localhost
|
||||
|
||||
import (
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
tmbytes "github.com/tendermint/tendermint/libs/bytes"
|
||||
|
||||
evidenceexported "github.com/cosmos/cosmos-sdk/x/evidence/exported"
|
||||
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
||||
)
|
||||
|
||||
var (
|
||||
_ evidenceexported.Evidence = Evidence{}
|
||||
_ clientexported.Misbehaviour = Evidence{}
|
||||
)
|
||||
|
||||
// Evidence is not required for a loop-back client
|
||||
type Evidence struct {
|
||||
}
|
||||
|
||||
// ClientType is Localhost light client
|
||||
func (ev Evidence) ClientType() clientexported.ClientType {
|
||||
return clientexported.Localhost
|
||||
}
|
||||
|
||||
// GetClientID returns the ID of the client that committed a misbehaviour.
|
||||
func (ev Evidence) GetClientID() string {
|
||||
return clientexported.Localhost.String()
|
||||
}
|
||||
|
||||
// Route implements Evidence interface
|
||||
func (ev Evidence) Route() string {
|
||||
return clienttypes.SubModuleName
|
||||
}
|
||||
|
||||
// Type implements Evidence interface
|
||||
func (ev Evidence) Type() string {
|
||||
return "client_misbehaviour"
|
||||
}
|
||||
|
||||
// String implements Evidence interface
|
||||
func (ev Evidence) String() string {
|
||||
// FIXME: implement custom marshaller
|
||||
bz, err := yaml.Marshal(ev)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(bz)
|
||||
}
|
||||
|
||||
// Hash implements Evidence interface.
|
||||
func (ev Evidence) Hash() tmbytes.HexBytes {
|
||||
bz := SubModuleCdc.MustMarshalBinaryBare(ev)
|
||||
return tmhash.Sum(bz)
|
||||
}
|
||||
|
||||
// GetHeight returns 0.
|
||||
func (ev Evidence) GetHeight() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// ValidateBasic implements Evidence interface.
|
||||
func (ev Evidence) ValidateBasic() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package localhost
|
||||
|
||||
import (
|
||||
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||
)
|
||||
|
||||
var _ clientexported.Header = Header{}
|
||||
|
||||
// Header defines the Localhost consensus Header
|
||||
type Header struct {
|
||||
}
|
||||
|
||||
// ClientType defines that the Header is in loop-back mode.
|
||||
func (h Header) ClientType() clientexported.ClientType {
|
||||
return clientexported.Localhost
|
||||
}
|
||||
|
||||
// ConsensusState returns an empty consensus state.
|
||||
func (h Header) ConsensusState() ConsensusState {
|
||||
return ConsensusState{}
|
||||
}
|
||||
|
||||
// GetHeight returns 0.
|
||||
func (h Header) GetHeight() uint64 {
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package localhost_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store/cachekv"
|
||||
"github.com/cosmos/cosmos-sdk/store/dbadapter"
|
||||
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
const (
|
||||
height = 4
|
||||
)
|
||||
|
||||
type LocalhostTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
cdc *codec.Codec
|
||||
store *cachekv.Store
|
||||
}
|
||||
|
||||
func (suite *LocalhostTestSuite) SetupTest() {
|
||||
suite.cdc = codec.New()
|
||||
codec.RegisterCrypto(suite.cdc)
|
||||
commitmenttypes.RegisterCodec(suite.cdc)
|
||||
|
||||
mem := dbadapter.Store{DB: dbm.NewMemDB()}
|
||||
suite.store = cachekv.NewStore(mem)
|
||||
}
|
||||
|
||||
func TestLocalhostTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(LocalhostTestSuite))
|
||||
}
|
Loading…
Reference in New Issue