x/ibc: implement 09-localhost per specification (#5769)

Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
This commit is contained in:
Greg Hill 2020-04-17 15:36:47 +01:00 committed by GitHub
parent 968fb1f040
commit de00a7fed1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 887 additions and 2 deletions

View File

@ -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

View File

@ -142,6 +142,7 @@ type ClientType byte
// available client types
const (
Tendermint ClientType = iota + 1 // 1
Localhost
)
// string representation of the client types

View File

@ -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())
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,5 @@
/*
Package localhost implements a concrete `ConsensusState`, `Header`,
`Misbehaviour` and `Equivocation` types for the loop-back client.
*/
package localhost

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}