implement solo machine client (#6267)

* pause work until client refactor resolved

* continued scaffolding

* add msgs and evidence

* add update and misbehaviour functionality

* implement cli

* various types compile issues

* add sig proof and various bug fixes

* added rest routes

* verification funcs now update sequence number

* add sm suite and header test

* msgcreateclient and msgupdateclient tests

* add basic evidence test

* evidence validate basic test

* consensus state tests

* rm accidental file

* add verify con state and channel state, pause work for counterparty commitment type

* client state tests added

* update clienttype numbers

* update test added

* add misbehaviour tests

* update alias

* update cli tx

* update import alias

* cleanup code

* remove todo, tested by evidence tests

* add info to err msg

* wrapf

* Update x/ibc/06-solomachine/client/cli/tx.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update x/ibc/06-solomachine/client/cli/tx.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update x/ibc/06-solomachine/client/cli/tx.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update x/ibc/06-solomachine/client/cli/tx.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update x/ibc/06-solomachine/update.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update x/ibc/23-commitment/types/signature.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update x/ibc/06-solomachine/types/header.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* apply most of the review suggestions from @fedekunze

* remove alias.go

* update cli context with master changes

* merge selective downstream changes from colin/solomachine

* fix build issues

* remove signature proof

* try to migrate to proto

* rm go structs for consensus state and header

* address @fedekunze review and continue proto migration

* fix proto issues

* fix compile bugs in types, proto panics on getpubkey currently

* add timestamp

* update pubkey to be type sdkcrypto.PublicKey

* update timestamp handling

* begin removing amino, migrate to proto

* fix various build issues

* fix most test in types

* change sanitize to produce, fix bug, types test passing

* begin updating cli

* move solomachine into light-clients/

* fix merge issue

* update proto and fix cli

* more fixes and update proto again

* update pubkey to be any

* fix string func issue

* update tests to use testing pkg

* update from tm crypto keys ref to sdk

* fix tests 🎉

* increase codecov

* address TODOs

* address most of @fedekunze comments

* add test case for misbehaviour frozen client

* fix lint

* fix proto lint?

* rename Signature to TimestampedSignature

* remove chainID

* rename FrozenHeight to FrozenSequence

* apply verify consensus state changes requested by @AdityaSripal

* remove dup check

* fix typo in proto file comment

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Federico Kunze <federico.kunze94@gmail.com>
Co-authored-by: Christopher Goes <cwgoes@pluranimity.org>
This commit is contained in:
colin axnér 2020-08-24 12:06:48 +02:00 committed by GitHub
parent 535c3ffcbd
commit 46927c31cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 5249 additions and 23 deletions

View File

@ -0,0 +1,92 @@
syntax = "proto3";
package ibc.lightclients.solomachine.v1;
option go_package = "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types";
import "cosmos/base/crypto/v1beta1/crypto.proto";
import "gogoproto/gogo.proto";
// ClientState defines a solo machine client that tracks the current consensus
// state and if the client is frozen.
message ClientState {
option (gogoproto.goproto_getters) = false;
// frozen sequence of the solo machine
uint64 frozen_sequence = 1
[(gogoproto.moretags) = "yaml:\"frozen_sequence\""];
ConsensusState consensus_state = 2
[(gogoproto.moretags) = "yaml:\"consensus_state\""];
}
// ConsensusState defines a solo machine consensus state
message ConsensusState {
option (gogoproto.goproto_getters) = false;
// current sequence of the consensus state
uint64 sequence = 1;
// public key of the solo machine
cosmos.base.crypto.v1beta1.PublicKey public_key = 2
[(gogoproto.moretags) = "yaml:\"public_key\""];
uint64 timestamp = 3;
}
// Header defines a solo machine consensus header
message Header {
option (gogoproto.goproto_getters) = false;
// sequence to update solo machine public key at
uint64 sequence = 1;
bytes signature = 2;
cosmos.base.crypto.v1beta1.PublicKey new_public_key = 3
[(gogoproto.moretags) = "yaml:\"new_public_key\""];
}
// Evidence defines evidence of misbehaviour for a solo machine which consists
// of a sequence and two signatures over different messages at that sequence.
message Evidence {
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;
string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""];
uint64 sequence = 2;
SignatureAndData signature_one = 3
[(gogoproto.moretags) = "yaml:\"signature_one\""];
SignatureAndData signature_two = 4
[(gogoproto.moretags) = "yaml:\"signature_two\""];
}
// SignatureAndData contains a signature and the data signed over to create that
// signature.
message SignatureAndData {
option (gogoproto.goproto_getters) = false;
bytes signature = 1;
bytes data = 2;
}
// TimestampedSignature contains the signature and the timestamp of the
// signature.
message TimestampedSignature {
option (gogoproto.goproto_getters) = false;
bytes signature = 1;
uint64 timestamp = 2;
}
// MsgCreateClient defines a message to create an IBC client
message MsgCreateClient {
option (gogoproto.goproto_getters) = false;
string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""];
ConsensusState consensus_state = 2
[(gogoproto.moretags) = "yaml:\"consensus_state\""];
}
// MsgUpdateClient defines a message to update an IBC client
message MsgUpdateClient {
option (gogoproto.goproto_getters) = false;
string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""];
Header header = 2 [(gogoproto.nullable) = false];
}
// MsgSubmitClientMisbehaviour defines an sdk.Msg type that supports submitting
// arbitrary Evidence.
message MsgSubmitClientMisbehaviour {
option (gogoproto.goproto_getters) = false;
bytes submitter = 1
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
Evidence evidence = 2 [(gogoproto.nullable) = false];
}

View File

@ -146,6 +146,13 @@ type Header interface {
GetHeight() uint64
}
// message types for the IBC client
const (
TypeMsgCreateClient string = "create_client"
TypeMsgUpdateClient string = "update_client"
TypeMsgSubmitClientMisbehaviour string = "submit_client_misbehaviour"
)
// MsgCreateClient defines the msg interface that the
// CreateClient Handler expects
type MsgCreateClient interface {
@ -169,12 +176,14 @@ type ClientType byte
// available client types
const (
Tendermint ClientType = iota + 1 // 1
Localhost
SoloMachine ClientType = 6
Tendermint ClientType = 7
Localhost ClientType = 9
)
// string representation of the client types
const (
ClientTypeSoloMachine string = "solomachine"
ClientTypeTendermint string = "tendermint"
ClientTypeLocalHost string = "localhost"
)

View File

@ -6,7 +6,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
)
// NewTxCmd returns a root CLI command handler for all x/bank transaction commands.
// NewTxCmd returns a root CLI command handler for all x/ibc/07-tendermint transaction commands.
func NewTxCmd() *cobra.Command {
txCmd := &cobra.Command{
Use: types.SubModuleName,

View File

@ -15,13 +15,6 @@ import (
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
// Message types for the IBC client
const (
TypeMsgCreateClient string = "create_client"
TypeMsgUpdateClient string = "update_client"
TypeMsgSubmitClientMisbehaviour string = "submit_client_misbehaviour"
)
var (
_ clientexported.MsgCreateClient = (*MsgCreateClient)(nil)
_ clientexported.MsgUpdateClient = (*MsgUpdateClient)(nil)
@ -74,7 +67,7 @@ func (msg MsgCreateClient) Route() string {
// Type implements sdk.Msg
func (msg MsgCreateClient) Type() string {
return TypeMsgCreateClient
return clientexported.TypeMsgCreateClient
}
// ValidateBasic implements sdk.Msg
@ -184,7 +177,7 @@ func (msg MsgUpdateClient) Route() string {
// Type implements sdk.Msg
func (msg MsgUpdateClient) Type() string {
return TypeMsgUpdateClient
return clientexported.TypeMsgUpdateClient
}
// ValidateBasic implements sdk.Msg
@ -238,7 +231,7 @@ func (msg MsgSubmitClientMisbehaviour) Route() string { return host.RouterKey }
// Type returns the MsgSubmitClientMisbehaviour's type.
func (msg MsgSubmitClientMisbehaviour) Type() string {
return TypeMsgSubmitClientMisbehaviour
return clientexported.TypeMsgSubmitClientMisbehaviour
}
// ValidateBasic performs basic (non-state-dependant) validation on a MsgSubmitClientMisbehaviour.

View File

@ -7,11 +7,6 @@ import (
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
// Message types for the IBC client
const (
TypeMsgCreateClient string = "create_client"
)
var (
_ clientexported.MsgCreateClient = (*MsgCreateClient)(nil)
)
@ -30,7 +25,7 @@ func (msg MsgCreateClient) Route() string {
// Type implements sdk.Msg
func (msg MsgCreateClient) Type() string {
return TypeMsgCreateClient
return clientexported.TypeMsgCreateClient
}
// ValidateBasic implements sdk.Msg

View File

@ -0,0 +1,27 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
// NewTxCmd returns a root CLI command handler for all solo machine transaction commands.
func NewTxCmd() *cobra.Command {
txCmd := &cobra.Command{
Use: types.SubModuleName,
Short: "Solo Machine transaction subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
txCmd.AddCommand(
NewCreateClientCmd(),
NewUpdateClientCmd(),
NewSubmitMisbehaviourCmd(),
)
return txCmd
}

View File

@ -0,0 +1,139 @@
package cli
import (
"fmt"
"io/ioutil"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
// NewCreateClientCmd defines the command to create a new solo machine client.
func NewCreateClientCmd() *cobra.Command {
return &cobra.Command{
Use: "create [client-id] [path/to/consensus_state.json]",
Short: "create new solo machine client",
Long: "create a new solo machine client with the specified identifier and consensus state",
Example: fmt.Sprintf("%s tx ibc %s create [client-id] [path/to/consensus_state.json] --from node0 --home ../node0/<app>cli --chain-id $CID", version.AppName, types.SubModuleName),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}
clientID := args[0]
cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry)
var consensusState *types.ConsensusState
if err := cdc.UnmarshalJSON([]byte(args[1]), &consensusState); err != nil {
// check for file path if JSON input is not provided
contents, err := ioutil.ReadFile(args[1])
if err != nil {
return errors.New("neither JSON input nor path to .json file were provided")
}
if err := cdc.UnmarshalJSON(contents, &consensusState); err != nil {
return errors.Wrap(err, "error unmarshalling consensus state file")
}
}
msg := types.NewMsgCreateClient(clientID, consensusState)
if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
}
// NewUpdateClientCmd defines the command to update a solo machine client.
func NewUpdateClientCmd() *cobra.Command {
return &cobra.Command{
Use: "update [client-id] [path/to/header.json]",
Short: "update existing client with a header",
Long: "update existing client with a solo machine header",
Example: fmt.Sprintf("%s tx ibc %s update [client-id] [path/to/header.json] --from node0 --home ../node0/<app>cli --chain-id $CID", version.AppName, types.SubModuleName),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}
clientID := args[0]
cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry)
var header types.Header
if err := cdc.UnmarshalJSON([]byte(args[1]), &header); err != nil {
// check for file path if JSON input is not provided
contents, err := ioutil.ReadFile(args[1])
if err != nil {
return errors.New("neither JSON input nor path to .json file were provided")
}
if err := cdc.UnmarshalJSON(contents, &header); err != nil {
return errors.Wrap(err, "error unmarshalling header file")
}
}
msg := types.NewMsgUpdateClient(clientID, header)
if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
}
// NewSubmitMisbehaviourCmd defines the command to submit a misbehaviour to prevent
// future updates.
func NewSubmitMisbehaviourCmd() *cobra.Command {
return &cobra.Command{
Use: "misbehaviour [path/to/evidence.json]",
Short: "submit a client misbehaviour",
Long: "submit a client misbehaviour to prevent future updates",
Example: fmt.Sprintf("%s tx ibc %s misbehaviour [path/to/evidence.json] --from node0 --home ../node0/<app>cli --chain-id $CID", version.AppName, types.SubModuleName),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}
cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry)
var ev types.Evidence
if err := cdc.UnmarshalJSON([]byte(args[0]), &ev); err != nil {
// check for file path if JSON input is not provided
contents, err := ioutil.ReadFile(args[0])
if err != nil {
return errors.New("neither JSON input nor path to .json file were provided")
}
if err := cdc.UnmarshalJSON(contents, &ev); err != nil {
return errors.Wrap(err, "error unmarshalling evidence file")
}
}
msg := types.NewMsgSubmitClientMisbehaviour(ev, clientCtx.GetFromAddress())
if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
}

View File

@ -0,0 +1,7 @@
/*
Package solomachine implements a concrete `ConsensusState`, `Header`,
`Misbehaviour` and `Equivocation` types for the Solo Machine light client.
This implementation is based off the ICS 06 specification:
https://github.com/cosmos/ics/tree/master/spec/ics-006-solo-machine-client
*/
package solomachine

View File

@ -0,0 +1,18 @@
package solomachine
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/client/cli"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
// Name returns the solo machine client name.
func Name() string {
return types.SubModuleName
}
// GetTxCmd returns the root tx command for the solo machine client.
func GetTxCmd() *cobra.Command {
return cli.NewTxCmd()
}

View File

@ -0,0 +1,405 @@
package types
import (
ics23 "github.com/confio/ics23/go"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
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"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
var _ clientexported.ClientState = (*ClientState)(nil)
// NewClientState creates a new ClientState instance.
func NewClientState(consensusState *ConsensusState) *ClientState {
return &ClientState{
FrozenSequence: 0,
ConsensusState: consensusState,
}
}
// GetChainID returns an empty string.
func (cs ClientState) GetChainID() string {
return ""
}
// ClientType is Solo Machine.
func (cs ClientState) ClientType() clientexported.ClientType {
return clientexported.SoloMachine
}
// GetLatestHeight returns the latest sequence number.
func (cs ClientState) GetLatestHeight() uint64 {
return cs.ConsensusState.Sequence
}
// IsFrozen returns true if the client is frozen.
func (cs ClientState) IsFrozen() bool {
return cs.FrozenSequence != 0
}
// GetFrozenHeight returns the frozen sequence of the client.
func (cs ClientState) GetFrozenHeight() uint64 {
return cs.FrozenSequence
}
// GetProofSpecs returns nil proof specs since client state verification uses signatures.
func (cs ClientState) GetProofSpecs() []*ics23.ProofSpec {
return nil
}
// Validate performs basic validation of the client state fields.
func (cs ClientState) Validate() error {
return cs.ConsensusState.ValidateBasic()
}
// VerifyClientState verifies a proof of the client state of the running chain
// stored on the solo machine.
func (cs ClientState) VerifyClientState(
store sdk.KVStore,
cdc codec.BinaryMarshaler,
_ commitmentexported.Root,
sequence uint64,
prefix commitmentexported.Prefix,
counterpartyClientIdentifier string,
proof []byte,
clientState clientexported.ClientState,
) error {
signature, err := produceVerificationArgs(cdc, cs, sequence, prefix, proof)
if err != nil {
return err
}
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath()
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
if err != nil {
return err
}
data, err := ClientStateSignBytes(cdc, sequence, signature.Timestamp, path, clientState)
if err != nil {
return err
}
if err := VerifySignature(cs.ConsensusState.GetPubKey(), data, signature.Signature); err != nil {
return err
}
cs.ConsensusState.Sequence++
cs.ConsensusState.Timestamp = signature.Timestamp
setClientState(store, cdc, &cs)
return nil
}
// VerifyClientConsensusState verifies a proof of the consensus state of the
// running chain stored on the solo machine.
func (cs ClientState) VerifyClientConsensusState(
store sdk.KVStore,
cdc codec.BinaryMarshaler,
_ commitmentexported.Root,
sequence uint64,
counterpartyClientIdentifier string,
consensusHeight uint64,
prefix commitmentexported.Prefix,
proof []byte,
consensusState clientexported.ConsensusState,
) error {
signature, err := produceVerificationArgs(cdc, cs, sequence, prefix, proof)
if err != nil {
return err
}
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight)
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
if err != nil {
return err
}
data, err := ConsensusStateSignBytes(cdc, sequence, signature.Timestamp, path, consensusState)
if err != nil {
return err
}
if err := VerifySignature(cs.ConsensusState.GetPubKey(), data, signature.Signature); err != nil {
return err
}
cs.ConsensusState.Sequence++
cs.ConsensusState.Timestamp = signature.Timestamp
setClientState(store, cdc, &cs)
return nil
}
// VerifyConnectionState verifies a proof of the connection state of the
// specified connection end stored on the target machine.
func (cs ClientState) VerifyConnectionState(
store sdk.KVStore,
cdc codec.BinaryMarshaler,
sequence uint64,
prefix commitmentexported.Prefix,
proof []byte,
connectionID string,
connectionEnd connectionexported.ConnectionI,
) error {
signature, err := produceVerificationArgs(cdc, cs, sequence, prefix, proof)
if err != nil {
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.ConnectionPath(connectionID))
if err != nil {
return err
}
data, err := ConnectionStateSignBytes(cdc, sequence, signature.Timestamp, path, connectionEnd)
if err != nil {
return err
}
if err := VerifySignature(cs.ConsensusState.GetPubKey(), data, signature.Signature); err != nil {
return err
}
cs.ConsensusState.Sequence++
cs.ConsensusState.Timestamp = signature.Timestamp
setClientState(store, cdc, &cs)
return nil
}
// VerifyChannelState verifies a proof of the channel state of the specified
// channel end, under the specified port, stored on the target machine.
func (cs ClientState) VerifyChannelState(
store sdk.KVStore,
cdc codec.BinaryMarshaler,
sequence uint64,
prefix commitmentexported.Prefix,
proof []byte,
portID,
channelID string,
channel channelexported.ChannelI,
) error {
signature, err := produceVerificationArgs(cdc, cs, sequence, prefix, proof)
if err != nil {
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.ChannelPath(portID, channelID))
if err != nil {
return err
}
data, err := ChannelStateSignBytes(cdc, sequence, signature.Timestamp, path, channel)
if err != nil {
return err
}
if err := VerifySignature(cs.ConsensusState.GetPubKey(), data, signature.Signature); err != nil {
return err
}
cs.ConsensusState.Sequence++
cs.ConsensusState.Timestamp = signature.Timestamp
setClientState(store, cdc, &cs)
return nil
}
// VerifyPacketCommitment verifies a proof of an outgoing packet commitment at
// the specified port, specified channel, and specified sequence.
func (cs ClientState) VerifyPacketCommitment(
store sdk.KVStore,
cdc codec.BinaryMarshaler,
sequence uint64,
prefix commitmentexported.Prefix,
proof []byte,
portID,
channelID string,
packetSequence uint64,
commitmentBytes []byte,
) error {
signature, err := produceVerificationArgs(cdc, cs, sequence, prefix, proof)
if err != nil {
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(portID, channelID, packetSequence))
if err != nil {
return err
}
data := PacketCommitmentSignBytes(sequence, signature.Timestamp, path, commitmentBytes)
if err := VerifySignature(cs.ConsensusState.GetPubKey(), data, signature.Signature); err != nil {
return err
}
cs.ConsensusState.Sequence++
cs.ConsensusState.Timestamp = signature.Timestamp
setClientState(store, cdc, &cs)
return nil
}
// VerifyPacketAcknowledgement verifies a proof of an incoming packet
// acknowledgement at the specified port, specified channel, and specified sequence.
func (cs ClientState) VerifyPacketAcknowledgement(
store sdk.KVStore,
cdc codec.BinaryMarshaler,
sequence uint64,
prefix commitmentexported.Prefix,
proof []byte,
portID,
channelID string,
packetSequence uint64,
acknowledgement []byte,
) error {
signature, err := produceVerificationArgs(cdc, cs, sequence, prefix, proof)
if err != nil {
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(portID, channelID, packetSequence))
if err != nil {
return err
}
data := PacketAcknowledgementSignBytes(sequence, signature.Timestamp, path, acknowledgement)
if err := VerifySignature(cs.ConsensusState.GetPubKey(), data, signature.Signature); err != nil {
return err
}
cs.ConsensusState.Sequence++
cs.ConsensusState.Timestamp = signature.Timestamp
setClientState(store, cdc, &cs)
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(
store sdk.KVStore,
cdc codec.BinaryMarshaler,
sequence uint64,
prefix commitmentexported.Prefix,
proof []byte,
portID,
channelID string,
packetSequence uint64,
) error {
signature, err := produceVerificationArgs(cdc, cs, sequence, prefix, proof)
if err != nil {
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(portID, channelID, packetSequence))
if err != nil {
return err
}
data := PacketAcknowledgementAbsenceSignBytes(sequence, signature.Timestamp, path)
if err := VerifySignature(cs.ConsensusState.GetPubKey(), data, signature.Signature); err != nil {
return err
}
cs.ConsensusState.Sequence++
cs.ConsensusState.Timestamp = signature.Timestamp
setClientState(store, cdc, &cs)
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(
store sdk.KVStore,
cdc codec.BinaryMarshaler,
sequence uint64,
prefix commitmentexported.Prefix,
proof []byte,
portID,
channelID string,
nextSequenceRecv uint64,
) error {
signature, err := produceVerificationArgs(cdc, cs, sequence, prefix, proof)
if err != nil {
return err
}
path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(portID, channelID))
if err != nil {
return err
}
data := NextSequenceRecvSignBytes(sequence, signature.Timestamp, path, nextSequenceRecv)
if err := VerifySignature(cs.ConsensusState.GetPubKey(), data, signature.Signature); err != nil {
return err
}
cs.ConsensusState.Sequence++
cs.ConsensusState.Timestamp = signature.Timestamp
setClientState(store, cdc, &cs)
return nil
}
// produceVerificationArgs perfoms the basic checks on the arguments that are
// shared between the verification functions and returns the unmarshalled
// proof representing the signature and timestamp.
func produceVerificationArgs(
cdc codec.BinaryMarshaler,
cs ClientState,
sequence uint64,
prefix commitmentexported.Prefix,
proof []byte,
) (signature TimestampedSignature, err error) {
if cs.IsFrozen() {
return TimestampedSignature{}, clienttypes.ErrClientFrozen
}
if prefix == nil {
return TimestampedSignature{}, sdkerrors.Wrap(commitmenttypes.ErrInvalidPrefix, "prefix cannot be empty")
}
_, ok := prefix.(commitmenttypes.MerklePrefix)
if !ok {
return TimestampedSignature{}, sdkerrors.Wrapf(commitmenttypes.ErrInvalidPrefix, "invalid prefix type %T, expected MerklePrefix", prefix)
}
if proof == nil {
return TimestampedSignature{}, sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "proof cannot be empty")
}
if err = cdc.UnmarshalBinaryBare(proof, &signature); err != nil {
return TimestampedSignature{}, sdkerrors.Wrapf(ErrInvalidProof, "failed to unmarshal proof into type %T", TimestampedSignature{})
}
if cs.ConsensusState == nil {
return TimestampedSignature{}, sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty")
}
if cs.GetLatestHeight() < sequence {
return TimestampedSignature{}, sdkerrors.Wrapf(
sdkerrors.ErrInvalidHeight,
"client state sequence < proof sequence (%d < %d)", cs.GetLatestHeight(), sequence,
)
}
if cs.ConsensusState.GetTimestamp() > signature.Timestamp {
return TimestampedSignature{}, sdkerrors.Wrapf(ErrInvalidProof, "the consensus state timestamp is greater than the signature timestamp (%d >= %d)", cs.ConsensusState.GetTimestamp(), signature.Timestamp)
}
return signature, nil
}
// sets the client state to the store
func setClientState(store sdk.KVStore, cdc codec.BinaryMarshaler, clientState clientexported.ClientState) {
bz := clienttypes.MustMarshalClientState(cdc, clientState)
store.Set(host.KeyClientState(), bz)
}

View File

@ -0,0 +1,800 @@
package types_test
import (
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types"
channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types"
commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
const (
counterpartyClientIdentifier = "chainA"
consensusHeight = uint64(0)
testConnectionID = "connectionid"
testChannelID = "testchannelid"
testPortID = "testportid"
)
var (
prefix = commitmenttypes.NewMerklePrefix([]byte("ibc"))
)
func (suite *SoloMachineTestSuite) TestClientStateValidateBasic() {
testCases := []struct {
name string
clientState *types.ClientState
expPass bool
}{
{
"valid client state",
suite.solomachine.ClientState(),
true,
},
{
"sequence is zero",
types.NewClientState(&types.ConsensusState{0, suite.solomachine.ConsensusState().PublicKey, suite.solomachine.Time}),
false,
},
{
"timstamp is zero",
types.NewClientState(&types.ConsensusState{1, suite.solomachine.ConsensusState().PublicKey, 0}),
false,
},
{
"pubkey is empty",
types.NewClientState(&types.ConsensusState{suite.solomachine.Sequence, nil, suite.solomachine.Time}),
false,
},
}
for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
err := tc.clientState.Validate()
if tc.expPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *SoloMachineTestSuite) TestVerifyClientState() {
// create client for tendermint so we can use client state for verification
clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, clientexported.Tendermint)
clientState := suite.chainA.GetClientState(clientA)
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath()
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
suite.Require().NoError(err)
value, err := types.ClientStateSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, path, clientState)
suite.Require().NoError(err)
sig, err := suite.solomachine.PrivateKey.Sign(value)
suite.Require().NoError(err)
signatureDoc := &types.TimestampedSignature{
Signature: sig,
Timestamp: suite.solomachine.Time,
}
proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc)
suite.Require().NoError(err)
testCases := []struct {
name string
clientState *types.ClientState
prefix commitmentexported.Prefix
proof []byte
expPass bool
}{
{
"successful verification",
suite.solomachine.ClientState(),
prefix,
proof,
true,
},
{
"ApplyPrefix failed",
suite.solomachine.ClientState(),
nil,
proof,
false,
},
{
"client is frozen",
&types.ClientState{1, suite.solomachine.ConsensusState()},
prefix,
proof,
false,
},
{
"consensus state in client state is nil",
types.NewClientState(nil),
prefix,
proof,
false,
},
{
"client state latest height is less than sequence",
types.NewClientState(
&types.ConsensusState{
Sequence: suite.solomachine.Sequence - 1,
Timestamp: suite.solomachine.Time,
PublicKey: suite.solomachine.ConsensusState().PublicKey,
}),
prefix,
proof,
false,
},
{
"consensus state timestamp is greater than signature",
types.NewClientState(
&types.ConsensusState{
Sequence: suite.solomachine.Sequence,
Timestamp: suite.solomachine.Time + 1,
PublicKey: suite.solomachine.ConsensusState().PublicKey,
}),
prefix,
proof,
false,
},
{
"proof is nil",
suite.solomachine.ClientState(),
prefix,
nil,
false,
},
{
"proof verification failed",
suite.solomachine.ClientState(),
prefix,
suite.GetInvalidProof(),
false,
},
}
for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
var expSeq uint64
if tc.clientState.ConsensusState != nil {
expSeq = tc.clientState.ConsensusState.Sequence + 1
}
err := tc.clientState.VerifyClientState(
suite.store, suite.chainA.Codec, nil, suite.solomachine.Sequence, tc.prefix, counterpartyClientIdentifier, tc.proof, clientState,
)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %s", suite.GetSequenceFromStore(), tc.name)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *SoloMachineTestSuite) TestVerifyClientConsensusState() {
// create client for tendermint so we can use consensus state for verification
clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, clientexported.Tendermint)
clientState := suite.chainA.GetClientState(clientA)
consensusState, found := suite.chainA.GetConsensusState(clientA, clientState.GetLatestHeight())
suite.Require().True(found)
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight)
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
suite.Require().NoError(err)
value, err := types.ConsensusStateSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, path, consensusState)
suite.Require().NoError(err)
sig, err := suite.solomachine.PrivateKey.Sign(value)
suite.Require().NoError(err)
signatureDoc := &types.TimestampedSignature{
Signature: sig,
Timestamp: suite.solomachine.Time,
}
proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc)
suite.Require().NoError(err)
testCases := []struct {
name string
clientState *types.ClientState
prefix commitmentexported.Prefix
proof []byte
expPass bool
}{
{
"successful verification",
suite.solomachine.ClientState(),
prefix,
proof,
true,
},
{
"ApplyPrefix failed",
suite.solomachine.ClientState(),
nil,
proof,
false,
},
{
"client is frozen",
&types.ClientState{1, suite.solomachine.ConsensusState()},
prefix,
proof,
false,
},
{
"consensus state in client state is nil",
types.NewClientState(nil),
prefix,
proof,
false,
},
{
"client state latest height is less than sequence",
types.NewClientState(
&types.ConsensusState{
Sequence: suite.solomachine.Sequence - 1,
Timestamp: suite.solomachine.Time,
PublicKey: suite.solomachine.ConsensusState().PublicKey,
}),
prefix,
proof,
false,
},
{
"consensus state timestamp is greater than signature",
types.NewClientState(
&types.ConsensusState{
Sequence: suite.solomachine.Sequence,
Timestamp: suite.solomachine.Time + 1,
PublicKey: suite.solomachine.ConsensusState().PublicKey,
}),
prefix,
proof,
false,
},
{
"proof is nil",
suite.solomachine.ClientState(),
prefix,
nil,
false,
},
{
"proof verification failed",
suite.solomachine.ClientState(),
prefix,
suite.GetInvalidProof(),
false,
},
}
for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
var expSeq uint64
if tc.clientState.ConsensusState != nil {
expSeq = tc.clientState.ConsensusState.Sequence + 1
}
err := tc.clientState.VerifyClientConsensusState(
suite.store, suite.chainA.Codec, nil, suite.solomachine.Sequence, counterpartyClientIdentifier, consensusHeight, tc.prefix, tc.proof, consensusState,
)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %s", suite.GetSequenceFromStore(), tc.name)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *SoloMachineTestSuite) TestVerifyConnectionState() {
counterparty := connectiontypes.NewCounterparty("clientB", testConnectionID, prefix)
conn := connectiontypes.NewConnectionEnd(connectiontypes.OPEN, "clientA", counterparty, []string{"1.0.0"})
path, err := commitmenttypes.ApplyPrefix(prefix, host.ConnectionPath(testConnectionID))
suite.Require().NoError(err)
value, err := types.ConnectionStateSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, path, conn)
suite.Require().NoError(err)
sig, err := suite.solomachine.PrivateKey.Sign(value)
suite.Require().NoError(err)
signatureDoc := &types.TimestampedSignature{
Signature: sig,
Timestamp: suite.solomachine.Time,
}
proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc)
suite.Require().NoError(err)
testCases := []struct {
name string
clientState *types.ClientState
prefix commitmentexported.Prefix
proof []byte
expPass bool
}{
{
"successful verification",
suite.solomachine.ClientState(),
prefix,
proof,
true,
},
{
"ApplyPrefix failed",
suite.solomachine.ClientState(),
commitmenttypes.NewMerklePrefix([]byte{}),
proof,
false,
},
{
"client is frozen",
&types.ClientState{1, suite.solomachine.ConsensusState()},
prefix,
proof,
false,
},
{
"proof is nil",
suite.solomachine.ClientState(),
prefix,
nil,
false,
},
{
"proof verification failed",
suite.solomachine.ClientState(),
prefix,
suite.GetInvalidProof(),
false,
},
}
for i, tc := range testCases {
tc := tc
expSeq := tc.clientState.ConsensusState.Sequence + 1
err := tc.clientState.VerifyConnectionState(
suite.store, suite.chainA.Codec, suite.solomachine.Sequence, tc.prefix, tc.proof, testConnectionID, conn,
)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
}
}
}
func (suite *SoloMachineTestSuite) TestVerifyChannelState() {
counterparty := channeltypes.NewCounterparty(testPortID, testChannelID)
ch := channeltypes.NewChannel(channeltypes.OPEN, channeltypes.ORDERED, counterparty, []string{testConnectionID}, "1.0.0")
path, err := commitmenttypes.ApplyPrefix(prefix, host.ChannelPath(testPortID, testChannelID))
suite.Require().NoError(err)
value, err := types.ChannelStateSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, path, ch)
suite.Require().NoError(err)
sig, err := suite.solomachine.PrivateKey.Sign(value)
suite.Require().NoError(err)
signatureDoc := &types.TimestampedSignature{
Signature: sig,
Timestamp: suite.solomachine.Time,
}
proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc)
suite.Require().NoError(err)
testCases := []struct {
name string
clientState *types.ClientState
prefix commitmentexported.Prefix
proof []byte
expPass bool
}{
{
"successful verification",
suite.solomachine.ClientState(),
prefix,
proof,
true,
},
{
"ApplyPrefix failed",
suite.solomachine.ClientState(),
nil,
proof,
false,
},
{
"client is frozen",
&types.ClientState{1, suite.solomachine.ConsensusState()},
prefix,
proof,
false,
},
{
"proof is nil",
suite.solomachine.ClientState(),
prefix,
nil,
false,
},
{
"proof verification failed",
suite.solomachine.ClientState(),
prefix,
suite.GetInvalidProof(),
false,
},
}
for i, tc := range testCases {
tc := tc
expSeq := tc.clientState.ConsensusState.Sequence + 1
err := tc.clientState.VerifyChannelState(
suite.store, suite.chainA.Codec, suite.solomachine.Sequence, tc.prefix, tc.proof, testPortID, testChannelID, ch,
)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
}
}
}
func (suite *SoloMachineTestSuite) TestVerifyPacketCommitment() {
commitmentBytes := []byte("COMMITMENT BYTES")
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(testPortID, testChannelID, suite.solomachine.Sequence))
suite.Require().NoError(err)
value := types.PacketCommitmentSignBytes(suite.solomachine.Sequence, suite.solomachine.Time, path, commitmentBytes)
sig, err := suite.solomachine.PrivateKey.Sign(value)
suite.Require().NoError(err)
signatureDoc := &types.TimestampedSignature{
Signature: sig,
Timestamp: suite.solomachine.Time,
}
proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc)
suite.Require().NoError(err)
testCases := []struct {
name string
clientState *types.ClientState
prefix commitmentexported.Prefix
proof []byte
expPass bool
}{
{
"successful verification",
suite.solomachine.ClientState(),
prefix,
proof,
true,
},
{
"ApplyPrefix failed",
suite.solomachine.ClientState(),
commitmenttypes.NewMerklePrefix([]byte{}),
proof,
false,
},
{
"client is frozen",
&types.ClientState{1, suite.solomachine.ConsensusState()},
prefix,
proof,
false,
},
{
"proof is nil",
suite.solomachine.ClientState(),
prefix,
nil,
false,
},
{
"proof verification failed",
suite.solomachine.ClientState(),
prefix,
suite.GetInvalidProof(),
false,
},
}
for i, tc := range testCases {
tc := tc
expSeq := tc.clientState.ConsensusState.Sequence + 1
err := tc.clientState.VerifyPacketCommitment(
suite.store, suite.chainA.Codec, suite.solomachine.Sequence, tc.prefix, tc.proof, testPortID, testChannelID, suite.solomachine.Sequence, commitmentBytes,
)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
}
}
}
func (suite *SoloMachineTestSuite) TestVerifyPacketAcknowledgement() {
ack := []byte("ACK")
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(testPortID, testChannelID, suite.solomachine.Sequence))
suite.Require().NoError(err)
value := types.PacketAcknowledgementSignBytes(suite.solomachine.Sequence, suite.solomachine.Time, path, ack)
sig, err := suite.solomachine.PrivateKey.Sign(value)
suite.Require().NoError(err)
signatureDoc := &types.TimestampedSignature{
Signature: sig,
Timestamp: suite.solomachine.Time,
}
proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc)
suite.Require().NoError(err)
testCases := []struct {
name string
clientState *types.ClientState
prefix commitmentexported.Prefix
proof []byte
expPass bool
}{
{
"successful verification",
suite.solomachine.ClientState(),
prefix,
proof,
true,
},
{
"ApplyPrefix failed",
suite.solomachine.ClientState(),
commitmenttypes.NewMerklePrefix([]byte{}),
proof,
false,
},
{
"client is frozen",
&types.ClientState{1, suite.solomachine.ConsensusState()},
prefix,
proof,
false,
},
{
"proof is nil",
suite.solomachine.ClientState(),
prefix,
nil,
false,
},
{
"proof verification failed",
suite.solomachine.ClientState(),
prefix,
suite.GetInvalidProof(),
false,
},
}
for i, tc := range testCases {
tc := tc
expSeq := tc.clientState.ConsensusState.Sequence + 1
err := tc.clientState.VerifyPacketAcknowledgement(
suite.store, suite.chainA.Codec, suite.solomachine.Sequence, tc.prefix, tc.proof, testPortID, testChannelID, suite.solomachine.Sequence, ack,
)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
}
}
}
func (suite *SoloMachineTestSuite) TestVerifyPacketAcknowledgementAbsence() {
path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(testPortID, testChannelID, suite.solomachine.Sequence))
suite.Require().NoError(err)
value := types.PacketAcknowledgementAbsenceSignBytes(suite.solomachine.Sequence, suite.solomachine.Time, path)
sig, err := suite.solomachine.PrivateKey.Sign(value)
suite.Require().NoError(err)
signatureDoc := &types.TimestampedSignature{
Signature: sig,
Timestamp: suite.solomachine.Time,
}
proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc)
suite.Require().NoError(err)
testCases := []struct {
name string
clientState *types.ClientState
prefix commitmentexported.Prefix
proof []byte
expPass bool
}{
{
"successful verification",
suite.solomachine.ClientState(),
prefix,
proof,
true,
},
{
"ApplyPrefix failed",
suite.solomachine.ClientState(),
commitmenttypes.NewMerklePrefix([]byte{}),
proof,
false,
},
{
"client is frozen",
&types.ClientState{1, suite.solomachine.ConsensusState()},
prefix,
proof,
false,
},
{
"proof is nil",
suite.solomachine.ClientState(),
prefix,
nil,
false,
},
{
"proof verification failed",
suite.solomachine.ClientState(),
prefix,
suite.GetInvalidProof(),
false,
},
}
for i, tc := range testCases {
tc := tc
expSeq := tc.clientState.ConsensusState.Sequence + 1
err := tc.clientState.VerifyPacketAcknowledgementAbsence(
suite.store, suite.chainA.Codec, suite.solomachine.Sequence, tc.prefix, tc.proof, testPortID, testChannelID, suite.solomachine.Sequence,
)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
}
}
}
func (suite *SoloMachineTestSuite) TestVerifyNextSeqRecv() {
nextSeqRecv := suite.solomachine.Sequence + 1
path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(testPortID, testChannelID))
suite.Require().NoError(err)
value := types.NextSequenceRecvSignBytes(suite.solomachine.Sequence, suite.solomachine.Time, path, nextSeqRecv)
sig, err := suite.solomachine.PrivateKey.Sign(value)
suite.Require().NoError(err)
signatureDoc := &types.TimestampedSignature{
Signature: sig,
Timestamp: suite.solomachine.Time,
}
proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc)
suite.Require().NoError(err)
testCases := []struct {
name string
clientState *types.ClientState
prefix commitmentexported.Prefix
proof []byte
expPass bool
}{
{
"successful verification",
suite.solomachine.ClientState(),
prefix,
proof,
true,
},
{
"ApplyPrefix failed",
suite.solomachine.ClientState(),
commitmenttypes.NewMerklePrefix([]byte{}),
proof,
false,
},
{
"client is frozen",
&types.ClientState{1, suite.solomachine.ConsensusState()},
prefix,
proof,
false,
},
{
"proof is nil",
suite.solomachine.ClientState(),
prefix,
nil,
false,
},
{
"proof verification failed",
suite.solomachine.ClientState(),
prefix,
suite.GetInvalidProof(),
false,
},
}
for i, tc := range testCases {
tc := tc
expSeq := tc.clientState.ConsensusState.Sequence + 1
err := tc.clientState.VerifyNextSequenceRecv(
suite.store, suite.chainA.Codec, suite.solomachine.Sequence, tc.prefix, tc.proof, testPortID, testChannelID, nextSeqRecv,
)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
}
}
}

View File

@ -0,0 +1,36 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
)
// RegisterInterfaces register the ibc channel submodule interfaces to protobuf
// Any.
func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
registry.RegisterImplementations(
(*sdk.Msg)(nil),
&MsgCreateClient{},
&MsgUpdateClient{},
&MsgSubmitClientMisbehaviour{},
)
registry.RegisterImplementations(
(*clientexported.ClientState)(nil),
&ClientState{},
)
registry.RegisterImplementations(
(*clientexported.ConsensusState)(nil),
&ConsensusState{},
)
}
var (
// SubModuleCdc references the global x/ibc/light-clients/solomachine module codec. Note, the codec
// should ONLY be used in certain instances of tests and for JSON encoding..
//
// The actual codec used for serialization should be provided to x/ibc/light-clients/solomachine and
// defined at the application level.
SubModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
)

View File

@ -0,0 +1,58 @@
package types
import (
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/std"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported"
)
var _ clientexported.ConsensusState = ConsensusState{}
// ClientType returns Solo Machine type.
func (ConsensusState) ClientType() clientexported.ClientType {
return clientexported.SoloMachine
}
// GetHeight returns the sequence number.
func (cs ConsensusState) GetHeight() uint64 {
return cs.Sequence
}
// GetTimestamp returns zero.
func (cs ConsensusState) GetTimestamp() uint64 {
return cs.Timestamp
}
// GetRoot returns nil since solo machines do not have roots.
func (cs ConsensusState) GetRoot() commitmentexported.Root {
return nil
}
// GetPubKey unmarshals the public key into a tmcrypto.PubKey type.
func (cs ConsensusState) GetPubKey() tmcrypto.PubKey {
publicKey, err := std.DefaultPublicKeyCodec{}.Decode(cs.PublicKey)
if err != nil {
panic(err)
}
return publicKey
}
// ValidateBasic defines basic validation for the solo machine consensus state.
func (cs ConsensusState) ValidateBasic() error {
if cs.Sequence == 0 {
return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "sequence cannot be 0")
}
if cs.Timestamp == 0 {
return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "timestamp cannot be 0")
}
if cs.PublicKey == nil || cs.GetPubKey() == nil || len(cs.GetPubKey().Bytes()) == 0 {
return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "public key cannot be empty")
}
return nil
}

View File

@ -0,0 +1,60 @@
package types_test
import (
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
func (suite *SoloMachineTestSuite) TestConsensusState() {
consensusState := suite.solomachine.ConsensusState()
suite.Require().Equal(clientexported.SoloMachine, consensusState.ClientType())
suite.Require().Equal(suite.solomachine.Sequence, consensusState.GetHeight())
suite.Require().Equal(suite.solomachine.Time, consensusState.GetTimestamp())
suite.Require().Nil(consensusState.GetRoot())
}
func (suite *SoloMachineTestSuite) TestConsensusStateValidateBasic() {
testCases := []struct {
name string
consensusState *types.ConsensusState
expPass bool
}{
{
"valid consensus state",
suite.solomachine.ConsensusState(),
true,
},
{
"sequence is zero",
&types.ConsensusState{
Sequence: 0,
PublicKey: suite.solomachine.ConsensusState().PublicKey,
},
false,
},
{
"pubkey is nil",
&types.ConsensusState{
Sequence: suite.solomachine.Sequence,
PublicKey: nil,
},
false,
},
}
for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
err := tc.consensusState.ValidateBasic()
if tc.expPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}

View File

@ -0,0 +1,17 @@
package types
import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
const (
SubModuleName = "solo machine"
)
var (
ErrInvalidHeader = sdkerrors.Register(SubModuleName, 2, "invalid header")
ErrInvalidSequence = sdkerrors.Register(SubModuleName, 3, "invalid sequence")
ErrInvalidSignatureAndData = sdkerrors.Register(SubModuleName, 4, "invalid signature and data")
ErrSignatureVerificationFailed = sdkerrors.Register(SubModuleName, 5, "signature verification failed")
ErrInvalidProof = sdkerrors.Register(SubModuleName, 6, "invalid solo machine proof")
)

View File

@ -0,0 +1,101 @@
package types
import (
"bytes"
yaml "gopkg.in/yaml.v2"
"github.com/tendermint/tendermint/crypto/tmhash"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
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"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
var (
_ evidenceexported.Evidence = (*Evidence)(nil)
_ clientexported.Misbehaviour = (*Evidence)(nil)
)
// ClientType is a Solo Machine light client.
func (ev Evidence) ClientType() clientexported.ClientType {
return clientexported.SoloMachine
}
// GetClientID returns the ID of the client that committed a misbehaviour.
func (ev Evidence) GetClientID() string {
return ev.ClientId
}
// 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 {
out, _ := yaml.Marshal(ev)
return string(out)
}
// Hash implements Evidence interface
func (ev Evidence) Hash() tmbytes.HexBytes {
bz := SubModuleCdc.MustMarshalBinaryBare(&ev)
return tmhash.Sum(bz)
}
// GetHeight returns the sequence at which misbehaviour occurred.
func (ev Evidence) GetHeight() int64 {
return int64(ev.Sequence)
}
// ValidateBasic implements Evidence interface.
func (ev Evidence) ValidateBasic() error {
if err := host.ClientIdentifierValidator(ev.ClientId); err != nil {
return sdkerrors.Wrap(err, "invalid client identifier for solo machine")
}
if ev.Sequence == 0 {
return sdkerrors.Wrap(clienttypes.ErrInvalidEvidence, "sequence cannot be 0")
}
if err := ev.SignatureOne.ValidateBasic(); err != nil {
return sdkerrors.Wrap(err, "signature one failed basic validation")
}
if err := ev.SignatureTwo.ValidateBasic(); err != nil {
return sdkerrors.Wrap(err, "signature two failed basic validation")
}
// evidence signatures cannot be identical
if bytes.Equal(ev.SignatureOne.Signature, ev.SignatureTwo.Signature) {
return sdkerrors.Wrap(clienttypes.ErrInvalidEvidence, "evidence signatures cannot be equal")
}
// message data signed cannot be identical
if bytes.Equal(ev.SignatureOne.Data, ev.SignatureTwo.Data) {
return sdkerrors.Wrap(clienttypes.ErrInvalidEvidence, "evidence signature data must be signed over different messages")
}
return nil
}
// ValidateBasic ensures that the signature and data fields are non-empty.
func (sd SignatureAndData) ValidateBasic() error {
if len(sd.Signature) == 0 {
return sdkerrors.Wrap(ErrInvalidSignatureAndData, "signature cannot be empty")
}
if len(sd.Data) == 0 {
return sdkerrors.Wrap(ErrInvalidSignatureAndData, "data for signature cannot be empty")
}
return nil
}

View File

@ -0,0 +1,108 @@
package types_test
import (
"github.com/tendermint/tendermint/crypto/tmhash"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
func (suite *SoloMachineTestSuite) TestEvidence() {
ev := suite.solomachine.CreateEvidence()
suite.Require().Equal(clientexported.SoloMachine, ev.ClientType())
suite.Require().Equal(suite.solomachine.ClientID, ev.GetClientID())
suite.Require().Equal("client", ev.Route())
suite.Require().Equal("client_misbehaviour", ev.Type())
suite.Require().Equal(tmbytes.HexBytes(tmhash.Sum(types.SubModuleCdc.MustMarshalBinaryBare(&ev))), ev.Hash())
suite.Require().Equal(int64(suite.solomachine.Sequence), ev.GetHeight())
}
func (suite *SoloMachineTestSuite) TestEvidenceValidateBasic() {
testCases := []struct {
name string
malleateEvidence func(ev *types.Evidence)
expPass bool
}{
{
"valid evidence",
func(*types.Evidence) {},
true,
},
{
"invalid client ID",
func(ev *types.Evidence) {
ev.ClientId = "(badclientid)"
},
false,
},
{
"sequence is zero",
func(ev *types.Evidence) {
ev.Sequence = 0
},
false,
},
{
"signature one sig is empty",
func(ev *types.Evidence) {
ev.SignatureOne.Signature = []byte{}
},
false,
},
{
"signature two sig is empty",
func(ev *types.Evidence) {
ev.SignatureTwo.Signature = []byte{}
},
false,
},
{
"signature one data is empty",
func(ev *types.Evidence) {
ev.SignatureOne.Data = nil
},
false,
},
{
"signature two data is empty",
func(ev *types.Evidence) {
ev.SignatureTwo.Data = []byte{}
},
false,
},
{
"signatures are identical",
func(ev *types.Evidence) {
ev.SignatureTwo.Signature = ev.SignatureOne.Signature
},
false,
},
{
"data signed is identical",
func(ev *types.Evidence) {
ev.SignatureTwo.Data = ev.SignatureOne.Data
},
false,
},
}
for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
ev := suite.solomachine.CreateEvidence()
tc.malleateEvidence(&ev)
err := ev.ValidateBasic()
if tc.expPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}

View File

@ -0,0 +1,50 @@
package types
import (
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/std"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
)
var _ clientexported.Header = Header{}
// ClientType defines that the Header is a Solo Machine.
func (Header) ClientType() clientexported.ClientType {
return clientexported.SoloMachine
}
// GetHeight returns the current sequence number as the height.
func (h Header) GetHeight() uint64 {
return h.Sequence
}
// GetPubKey unmarshals the new public key into a tmcrypto.PubKey type.
func (h Header) GetPubKey() tmcrypto.PubKey {
publicKey, err := std.DefaultPublicKeyCodec{}.Decode(h.NewPublicKey)
if err != nil {
panic(err)
}
return publicKey
}
// ValidateBasic ensures that the sequence, signature and public key have all
// been initialized.
func (h Header) ValidateBasic() error {
if h.Sequence == 0 {
return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "sequence number cannot be zero")
}
if len(h.Signature) == 0 {
return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "signature cannot be empty")
}
if h.NewPublicKey == nil || h.GetPubKey() == nil || len(h.GetPubKey().Bytes()) == 0 {
return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "new public key cannot be empty")
}
return nil
}

View File

@ -0,0 +1,65 @@
package types_test
import (
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
func (suite *SoloMachineTestSuite) TestHeaderValidateBasic() {
header := suite.solomachine.CreateHeader()
cases := []struct {
name string
header types.Header
expPass bool
}{
{
"valid header",
header,
true,
},
{
"sequence is zero",
types.Header{
Sequence: 0,
Signature: header.Signature,
NewPublicKey: header.NewPublicKey,
},
false,
},
{
"signature is empty",
types.Header{
Sequence: header.Sequence,
Signature: []byte{},
NewPublicKey: header.NewPublicKey,
},
false,
},
{
"public key is nil",
types.Header{
Sequence: header.Sequence,
Signature: header.Signature,
NewPublicKey: nil,
},
false,
},
}
suite.Require().Equal(clientexported.SoloMachine, header.ClientType())
for _, tc := range cases {
tc := tc
suite.Run(tc.name, func() {
err := tc.header.ValidateBasic()
if tc.expPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}

View File

@ -0,0 +1,63 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
)
// CheckMisbehaviourAndUpdateState determines whether or not the currently registered
// public key signed over two different messages with the same sequence. If this is true
// the client state is updated to a frozen status.
func (cs ClientState) CheckMisbehaviourAndUpdateState(
ctx sdk.Context,
cdc codec.BinaryMarshaler,
clientStore sdk.KVStore,
misbehaviour clientexported.Misbehaviour,
) (clientexported.ClientState, error) {
evidence, ok := misbehaviour.(Evidence)
if !ok {
return nil, sdkerrors.Wrapf(
clienttypes.ErrInvalidClientType,
"evidence type %T, expected %T", misbehaviour, Evidence{},
)
}
if cs.IsFrozen() {
return nil, sdkerrors.Wrapf(clienttypes.ErrClientFrozen, "client is already frozen")
}
if err := checkMisbehaviour(cs, evidence); err != nil {
return nil, err
}
cs.FrozenSequence = uint64(evidence.GetHeight())
return cs, nil
}
// checkMisbehaviour checks if the currently registered public key has signed
// over two different messages at the same sequence.
// NOTE: a check that the evidence message data are not equal is done by
// evidence.ValidateBasic which is called by the 02-client keeper.
func checkMisbehaviour(clientState ClientState, evidence Evidence) error {
pubKey := clientState.ConsensusState.GetPubKey()
data := EvidenceSignBytes(evidence.Sequence, evidence.SignatureOne.Data)
// check first signature
if err := VerifySignature(pubKey, data, evidence.SignatureOne.Signature); err != nil {
return sdkerrors.Wrap(err, "evidence signature one failed to be verified")
}
data = EvidenceSignBytes(evidence.Sequence, evidence.SignatureTwo.Data)
// check second signature
if err := VerifySignature(pubKey, data, evidence.SignatureTwo.Signature); err != nil {
return sdkerrors.Wrap(err, "evidence signature two failed to be verified")
}
return nil
}

View File

@ -0,0 +1,145 @@
package types_test
import (
sdk "github.com/cosmos/cosmos-sdk/types"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
)
func (suite *SoloMachineTestSuite) TestCheckMisbehaviourAndUpdateState() {
var (
clientState clientexported.ClientState
evidence clientexported.Misbehaviour
)
testCases := []struct {
name string
setup func()
expPass bool
}{
{
"valid misbehaviour evidence",
func() {
clientState = suite.solomachine.ClientState()
evidence = suite.solomachine.CreateEvidence()
},
true,
},
{
"client is frozen",
func() {
cs := suite.solomachine.ClientState()
cs.FrozenSequence = 1
clientState = cs
evidence = suite.solomachine.CreateEvidence()
},
false,
},
{
"wrong client state type",
func() {
clientState = ibctmtypes.ClientState{}
evidence = suite.solomachine.CreateEvidence()
},
false,
},
{
"invalid evidence type",
func() {
clientState = suite.solomachine.ClientState()
evidence = ibctmtypes.Evidence{}
},
false,
},
{
"invalid first signature",
func() {
clientState = suite.solomachine.ClientState()
// store in temp before assigning to interface type
ev := suite.solomachine.CreateEvidence()
msg := []byte("DATA ONE")
data := append(sdk.Uint64ToBigEndian(suite.solomachine.Sequence+1), msg...)
sig, err := suite.solomachine.PrivateKey.Sign(data)
suite.Require().NoError(err)
ev.SignatureOne.Signature = sig
ev.SignatureOne.Data = msg
evidence = ev
},
false,
},
{
"invalid second signature",
func() {
clientState = suite.solomachine.ClientState()
// store in temp before assigning to interface type
ev := suite.solomachine.CreateEvidence()
msg := []byte("DATA TWO")
data := append(sdk.Uint64ToBigEndian(suite.solomachine.Sequence+1), msg...)
sig, err := suite.solomachine.PrivateKey.Sign(data)
suite.Require().NoError(err)
ev.SignatureTwo.Signature = sig
ev.SignatureTwo.Data = msg
evidence = ev
},
false,
},
{
"signatures sign over different sequence",
func() {
clientState = suite.solomachine.ClientState()
// store in temp before assigning to interface type
ev := suite.solomachine.CreateEvidence()
// Signature One
msg := []byte("DATA ONE")
// sequence used is plus 1
data := append(sdk.Uint64ToBigEndian(suite.solomachine.Sequence+1), msg...)
sig, err := suite.solomachine.PrivateKey.Sign(data)
suite.Require().NoError(err)
ev.SignatureOne.Signature = sig
ev.SignatureOne.Data = msg
// Signature Two
msg = []byte("DATA TWO")
// sequence used is minus 1
data = append(sdk.Uint64ToBigEndian(suite.solomachine.Sequence-1), msg...)
sig, err = suite.solomachine.PrivateKey.Sign(data)
suite.Require().NoError(err)
ev.SignatureTwo.Signature = sig
ev.SignatureTwo.Data = msg
evidence = ev
},
false,
},
}
for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
// setup test
tc.setup()
clientState, err := clientState.CheckMisbehaviourAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), suite.store, evidence)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().True(clientState.IsFrozen(), "client not frozen")
} else {
suite.Require().Error(err)
suite.Require().Nil(clientState)
}
})
}
}

View File

@ -0,0 +1,163 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
evidenceexported "github.com/cosmos/cosmos-sdk/x/evidence/exported"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
var (
_ clientexported.MsgCreateClient = &MsgCreateClient{}
_ clientexported.MsgUpdateClient = &MsgUpdateClient{}
_ evidenceexported.MsgSubmitEvidence = &MsgSubmitClientMisbehaviour{}
)
// NewMsgCreateClient creates a new MsgCreateClient instance
func NewMsgCreateClient(id string, consensusState *ConsensusState) *MsgCreateClient {
return &MsgCreateClient{
ClientId: id,
ConsensusState: consensusState,
}
}
// Route implements sdk.Msg
func (msg MsgCreateClient) Route() string {
return host.RouterKey
}
// Type implements sdk.Msg
func (msg MsgCreateClient) Type() string {
return clientexported.TypeMsgCreateClient
}
// ValidateBasic implements sdk.Msg
func (msg MsgCreateClient) ValidateBasic() error {
if err := msg.ConsensusState.ValidateBasic(); err != nil {
return err
}
return host.ClientIdentifierValidator(msg.ClientId)
}
// GetSignBytes implements sdk.Msg
func (msg MsgCreateClient) GetSignBytes() []byte {
return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg))
}
// GetSigners implements sdk.Msg
func (msg MsgCreateClient) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.ConsensusState.GetPubKey().Address())}
}
// GetClientID implements clientexported.MsgCreateClient
func (msg MsgCreateClient) GetClientID() string {
return msg.ClientId
}
// GetClientType implements clientexported.MsgCreateClient
func (msg MsgCreateClient) GetClientType() string {
return clientexported.ClientTypeSoloMachine
}
// GetConsensusState implements clientexported.MsgCreateClient
func (msg MsgCreateClient) GetConsensusState() clientexported.ConsensusState {
return msg.ConsensusState
}
// InitializeFromMsg creates a solo machine client state from a MsgCreateClient
func (msg MsgCreateClient) InitializeClientState() clientexported.ClientState {
return NewClientState(msg.ConsensusState)
}
// NewMsgUpdateClient creates a new MsgUpdateClient instance
func NewMsgUpdateClient(id string, header Header) *MsgUpdateClient {
return &MsgUpdateClient{
ClientId: id,
Header: header,
}
}
// Route implements sdk.Msg
func (msg MsgUpdateClient) Route() string {
return host.RouterKey
}
// Type implements sdk.Msg
func (msg MsgUpdateClient) Type() string {
return clientexported.TypeMsgUpdateClient
}
// ValidateBasic implements sdk.Msg
func (msg MsgUpdateClient) ValidateBasic() error {
if err := msg.Header.ValidateBasic(); err != nil {
return err
}
return host.ClientIdentifierValidator(msg.ClientId)
}
// GetSignBytes implements sdk.Msg
func (msg MsgUpdateClient) GetSignBytes() []byte {
return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg))
}
// GetSigners implements sdk.Msg
func (msg MsgUpdateClient) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.Header.GetPubKey().Address())}
}
// GetClientID implements clientexported.MsgUpdateClient
func (msg MsgUpdateClient) GetClientID() string {
return msg.ClientId
}
// GetHeader implements clientexported.MsgUpdateClient
func (msg MsgUpdateClient) GetHeader() clientexported.Header {
return msg.Header
}
// NewMsgSubmitClientMisbehaviour creates a new MsgSubmitClientMisbehaviour
// instance.
func NewMsgSubmitClientMisbehaviour(e Evidence, s sdk.AccAddress) *MsgSubmitClientMisbehaviour {
return &MsgSubmitClientMisbehaviour{Evidence: e, Submitter: s}
}
// Route returns the MsgSubmitClientMisbehaviour's route.
func (msg MsgSubmitClientMisbehaviour) Route() string { return host.RouterKey }
// Type returns the MsgSubmitClientMisbehaviour's type.
func (msg MsgSubmitClientMisbehaviour) Type() string {
return clientexported.TypeMsgSubmitClientMisbehaviour
}
// ValidateBasic performs basic (non-state-dependent) validation on a MsgSubmitClientMisbehaviour.
func (msg MsgSubmitClientMisbehaviour) ValidateBasic() error {
if err := msg.Evidence.ValidateBasic(); err != nil {
return err
}
if msg.Submitter.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "submitter address cannot be empty")
}
return nil
}
// GetSignBytes returns the raw bytes a signer is expected to sign when submitting
// a MsgSubmitClientMisbehaviour message.
func (msg MsgSubmitClientMisbehaviour) GetSignBytes() []byte {
return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg))
}
// GetSigners returns the single expected signer for a MsgSubmitClientMisbehaviour.
func (msg MsgSubmitClientMisbehaviour) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Submitter}
}
func (msg MsgSubmitClientMisbehaviour) GetEvidence() evidenceexported.Evidence {
return &msg.Evidence
}
func (msg MsgSubmitClientMisbehaviour) GetSubmitter() sdk.AccAddress {
return msg.Submitter
}

View File

@ -0,0 +1,54 @@
package types_test
import (
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
func (suite *SoloMachineTestSuite) TestMsgCreateClientValidateBasic() {
cases := []struct {
name string
msg *types.MsgCreateClient
expPass bool
}{
{"valid msg", types.NewMsgCreateClient(suite.solomachine.ClientID, suite.solomachine.ConsensusState()), true},
{"invalid client id", types.NewMsgCreateClient("(BADCLIENTID)", suite.solomachine.ConsensusState()), false},
{"invalid consensus state with zero sequence", types.NewMsgCreateClient(suite.solomachine.ClientID, &types.ConsensusState{0, suite.solomachine.ConsensusState().PublicKey, suite.solomachine.Time}), false},
{"invalid consensus state with zero timestamp", types.NewMsgCreateClient(suite.solomachine.ClientID, &types.ConsensusState{1, suite.solomachine.ConsensusState().PublicKey, 0}), false},
{"invalid consensus state with nil pubkey", types.NewMsgCreateClient(suite.solomachine.ClientID, &types.ConsensusState{suite.solomachine.Sequence, nil, suite.solomachine.Time}), false},
}
for i, tc := range cases {
err := tc.msg.ValidateBasic()
if tc.expPass {
suite.Require().NoError(err, "Msg %d failed: %v", i, tc.name)
} else {
suite.Require().Error(err, "Invalid Msg %d passed: %s", i, tc.name)
}
}
}
func (suite *SoloMachineTestSuite) TestMsgUpdateClientValidateBasic() {
header := suite.solomachine.CreateHeader()
cases := []struct {
name string
msg *types.MsgUpdateClient
expPass bool
}{
{"valid msg", types.NewMsgUpdateClient(suite.solomachine.ClientID, header), true},
{"invalid client id", types.NewMsgUpdateClient("(BADCLIENTID)", header), false},
{"invalid header - sequence is zero", types.NewMsgUpdateClient(suite.solomachine.ClientID, types.Header{0, header.Signature, header.NewPublicKey}), false},
{"invalid header - signature is empty", types.NewMsgUpdateClient(suite.solomachine.ClientID, types.Header{header.Sequence, []byte{}, header.NewPublicKey}), false},
{"invalid header - pubkey is empty", types.NewMsgUpdateClient(suite.solomachine.ClientID, types.Header{header.Sequence, header.Signature, nil}), false},
}
for i, tc := range cases {
err := tc.msg.ValidateBasic()
if tc.expPass {
suite.Require().NoError(err, "Msg %d failed: %v", i, tc.name)
} else {
suite.Require().Error(err, "Invalid Msg %d passed: %s", i, tc.name)
}
}
}

View File

@ -0,0 +1,217 @@
package types
import (
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
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"
connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types"
channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported"
channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
)
// VerifySignature verifies if the the provided public key generated the signature
// over the given data.
func VerifySignature(pubKey crypto.PubKey, data, signature []byte) error {
if !pubKey.VerifySignature(data, signature) {
return ErrSignatureVerificationFailed
}
return nil
}
// EvidenceSignBytes returns the sign bytes for verification of misbehaviour.
//
// Format: {sequence}{data}
func EvidenceSignBytes(sequence uint64, data []byte) []byte {
return append(
sdk.Uint64ToBigEndian(sequence),
data...,
)
}
// HeaderSignBytes returns the sign bytes for verification of misbehaviour.
//
// Format: {sequence}{header.newPubKey}
func HeaderSignBytes(header Header) []byte {
return append(
sdk.Uint64ToBigEndian(header.Sequence),
header.GetPubKey().Bytes()...,
)
}
// ClientStateSignBytes returns the sign bytes for verification of the
// client state.
//
// Format: {sequence}{timestamp}{path}{client-state}
func ClientStateSignBytes(
cdc codec.BinaryMarshaler,
sequence, timestamp uint64,
path commitmenttypes.MerklePath,
clientState clientexported.ClientState,
) ([]byte, error) {
bz, err := codec.MarshalAny(cdc, clientState)
if err != nil {
return nil, err
}
// sequence + timestamp + path + client state
return append(
combineSequenceTimestampPath(sequence, timestamp, path),
bz...,
), nil
}
// ConsensusStateSignBytes returns the sign bytes for verification of the
// consensus state.
//
// Format: {sequence}{timestamp}{path}{consensus-state}
func ConsensusStateSignBytes(
cdc codec.BinaryMarshaler,
sequence, timestamp uint64,
path commitmenttypes.MerklePath,
consensusState clientexported.ConsensusState,
) ([]byte, error) {
bz, err := codec.MarshalAny(cdc, consensusState)
if err != nil {
return nil, err
}
// sequence + timestamp + path + consensus state
return append(
combineSequenceTimestampPath(sequence, timestamp, path),
bz...,
), nil
}
// ConnectionStateSignBytes returns the sign bytes for verification of the
// connection state.
//
// Format: {sequence}{timestamp}{path}{connection-end}
func ConnectionStateSignBytes(
cdc codec.BinaryMarshaler,
sequence, timestamp uint64,
path commitmenttypes.MerklePath,
connectionEnd connectionexported.ConnectionI,
) ([]byte, error) {
connection, ok := connectionEnd.(connectiontypes.ConnectionEnd)
if !ok {
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "invalid connection type %T", connectionEnd)
}
bz, err := cdc.MarshalBinaryBare(&connection)
if err != nil {
return nil, err
}
// sequence + timestamp + path + connection end
return append(
combineSequenceTimestampPath(sequence, timestamp, path),
bz...,
), nil
}
// ChannelStateSignBytes returns the sign bytes for verification of the
// channel state.
//
// Format: {sequence}{timestamp}{path}{channel-end}
func ChannelStateSignBytes(
cdc codec.BinaryMarshaler,
sequence, timestamp uint64,
path commitmenttypes.MerklePath,
channelEnd channelexported.ChannelI,
) ([]byte, error) {
channel, ok := channelEnd.(channeltypes.Channel)
if !ok {
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "invalid channel type %T", channelEnd)
}
bz, err := cdc.MarshalBinaryBare(&channel)
if err != nil {
return nil, err
}
// sequence + timestamp + path + channel
return append(
combineSequenceTimestampPath(sequence, timestamp, path),
bz...,
), nil
}
// PacketCommitmentSignBytes returns the sign bytes for verification of the
// packet commitment.
//
// Format: {sequence}{timestamp}{path}{commitment-bytes}
func PacketCommitmentSignBytes(
sequence, timestamp uint64,
path commitmenttypes.MerklePath,
commitmentBytes []byte,
) []byte {
// sequence + timestamp + path + commitment bytes
return append(
combineSequenceTimestampPath(sequence, timestamp, path),
commitmentBytes...,
)
}
// PacketAcknowledgementSignBytes returns the sign bytes for verification of
// the acknowledgement.
//
// Format: {sequence}{timestamp}{path}{acknowledgement}
func PacketAcknowledgementSignBytes(
sequence, timestamp uint64,
path commitmenttypes.MerklePath,
acknowledgement []byte,
) []byte {
// sequence + timestamp + path + acknowledgement
return append(
combineSequenceTimestampPath(sequence, timestamp, path),
acknowledgement...,
)
}
// PacketAcknowledgementAbsenceSignBytes returns the sign bytes for verification
// of the absence of an acknowledgement.
//
// Format: {sequence}{timestamp}{path}
func PacketAcknowledgementAbsenceSignBytes(
sequence, timestamp uint64,
path commitmenttypes.MerklePath,
) []byte {
// value = sequence + timestamp + path
return combineSequenceTimestampPath(sequence, timestamp, path)
}
// NextSequenceRecv returns the sign bytes for verification of the next
// sequence to be received.
//
// Format: {sequence}{timestamp}{path}{next-sequence-recv}
func NextSequenceRecvSignBytes(
sequence, timestamp uint64,
path commitmenttypes.MerklePath,
nextSequenceRecv uint64,
) []byte {
// sequence + timestamp + path + nextSequenceRecv
return append(
combineSequenceTimestampPath(sequence, timestamp, path),
sdk.Uint64ToBigEndian(nextSequenceRecv)...,
)
}
// combineSequenceTimestampPath combines the sequence, the timestamp and
// the path into one byte slice.
func combineSequenceTimestampPath(sequence, timestamp uint64, path commitmenttypes.MerklePath) []byte {
bz := append(sdk.Uint64ToBigEndian(sequence), sdk.Uint64ToBigEndian(timestamp)...)
return append(
bz,
[]byte(path.String())...,
)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
package types_test
import (
"testing"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing"
)
type SoloMachineTestSuite struct {
suite.Suite
solomachine *ibctesting.Solomachine
coordinator *ibctesting.Coordinator
// testing chain used for convenience and readability
chainA *ibctesting.TestChain
chainB *ibctesting.TestChain
store sdk.KVStore
}
func (suite *SoloMachineTestSuite) SetupTest() {
suite.solomachine = ibctesting.NewSolomachine(suite.T(), "testingsolomachine")
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2)
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0))
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1))
suite.store = suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), clientexported.ClientTypeSoloMachine)
bz, err := codec.MarshalAny(suite.chainA.Codec, suite.solomachine.ClientState())
suite.Require().NoError(err)
suite.store.Set(host.KeyClientState(), bz)
}
func TestSoloMachineTestSuite(t *testing.T) {
suite.Run(t, new(SoloMachineTestSuite))
}
func (suite *SoloMachineTestSuite) GetSequenceFromStore() uint64 {
bz := suite.store.Get(host.KeyClientState())
suite.Require().NotNil(bz)
var clientState clientexported.ClientState
err := codec.UnmarshalAny(suite.chainA.Codec, &clientState, bz)
suite.Require().NoError(err)
return clientState.GetLatestHeight()
}
func (suite *SoloMachineTestSuite) GetInvalidProof() []byte {
invalidProof, err := suite.chainA.Codec.MarshalBinaryBare(&types.TimestampedSignature{Timestamp: suite.solomachine.Time})
suite.Require().NoError(err)
return invalidProof
}

View File

@ -0,0 +1,63 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
)
// CheckHeaderAndUpdateState checks if the provided header is valid and updates
// the consensus state if appropriate. It returns an error if:
// - the client or header provided are not parseable to solo machine types
// - the currently registered public key did not provide the update signature
func (cs ClientState) CheckHeaderAndUpdateState(
ctx sdk.Context, cdc codec.BinaryMarshaler, clientStore sdk.KVStore,
header clientexported.Header,
) (clientexported.ClientState, clientexported.ConsensusState, error) {
smHeader, ok := header.(Header)
if !ok {
return nil, nil, sdkerrors.Wrapf(
clienttypes.ErrInvalidHeader, "header type %T is not solomachine", header,
)
}
if err := checkHeader(&cs, smHeader); err != nil {
return nil, nil, err
}
clientState, consensusState := update(&cs, smHeader)
return clientState, consensusState, nil
}
// checkHeader checks if the Solo Machine update signature is valid.
func checkHeader(clientState *ClientState, header Header) error {
// assert update sequence is current sequence
if header.Sequence != clientState.ConsensusState.Sequence {
return sdkerrors.Wrapf(
clienttypes.ErrInvalidHeader,
"sequence provided in the header does not match the client state sequence (%d != %d)", header.Sequence, clientState.ConsensusState.Sequence,
)
}
// assert currently registered public key signed over the new public key with correct sequence
data := HeaderSignBytes(header)
if err := VerifySignature(clientState.ConsensusState.GetPubKey(), data, header.Signature); err != nil {
return sdkerrors.Wrap(ErrInvalidHeader, err.Error())
}
return nil
}
// update the consensus state to the new public key and an incremented sequence
func update(clientState *ClientState, header Header) (*ClientState, *ConsensusState) {
consensusState := &ConsensusState{
// increment sequence number
Sequence: clientState.ConsensusState.Sequence + 1,
PublicKey: header.NewPublicKey,
}
clientState.ConsensusState = consensusState
return clientState, consensusState
}

View File

@ -0,0 +1,127 @@
package types_test
import (
sdk "github.com/cosmos/cosmos-sdk/types"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
"github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
func (suite *SoloMachineTestSuite) TestCheckHeaderAndUpdateState() {
var (
clientState clientexported.ClientState
header clientexported.Header
)
testCases := []struct {
name string
setup func()
expPass bool
}{
{
"successful update",
func() {
clientState = suite.solomachine.ClientState()
header = suite.solomachine.CreateHeader()
},
true,
},
{
"wrong client state type",
func() {
clientState = ibctmtypes.ClientState{}
header = suite.solomachine.CreateHeader()
},
false,
},
{
"invalid header type",
func() {
clientState = suite.solomachine.ClientState()
header = ibctmtypes.Header{}
},
false,
},
{
"wrong sequence in header",
func() {
clientState = suite.solomachine.ClientState()
// store in temp before assigning to interface type
h := suite.solomachine.CreateHeader()
h.Sequence++
header = h
},
false,
},
{
"signature uses wrong sequence",
func() {
clientState = suite.solomachine.ClientState()
suite.solomachine.Sequence++
header = suite.solomachine.CreateHeader()
},
false,
},
{
"signature uses new pubkey to sign",
func() {
// store in temp before assinging to interface type
cs := suite.solomachine.ClientState()
h := suite.solomachine.CreateHeader()
// generate invalid signature
data := append(sdk.Uint64ToBigEndian(cs.ConsensusState.Sequence), suite.solomachine.PublicKey.Bytes()...)
sig, err := suite.solomachine.PrivateKey.Sign(data)
suite.Require().NoError(err)
h.Signature = sig
clientState = cs
header = h
},
false,
},
{
"signature signs over old pubkey",
func() {
// store in temp before assinging to interface type
cs := suite.solomachine.ClientState()
oldPrivKey := suite.solomachine.PrivateKey
h := suite.solomachine.CreateHeader()
// generate invalid signature
data := append(sdk.Uint64ToBigEndian(cs.ConsensusState.Sequence), oldPrivKey.PubKey().Bytes()...)
sig, err := oldPrivKey.Sign(data)
suite.Require().NoError(err)
h.Signature = sig
clientState = cs
header = h
},
false,
},
}
for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
// setup test
tc.setup()
clientState, consensusState, err := clientState.CheckHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, header)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(header.(types.Header).NewPublicKey, clientState.(*types.ClientState).ConsensusState.PublicKey)
suite.Require().Equal(uint64(0), clientState.(*types.ClientState).FrozenSequence)
suite.Require().Equal(header.(types.Header).Sequence+1, clientState.(*types.ClientState).ConsensusState.Sequence)
suite.Require().Equal(consensusState, clientState.(*types.ClientState).ConsensusState)
} else {
suite.Require().Error(err)
suite.Require().Nil(clientState)
suite.Require().Nil(consensusState)
}
})
}
}

View File

@ -40,7 +40,7 @@ in the SDK's `x/ibc` module:
* [ICS 003 - Connection Semantics](https://github.com/cosmos/ics/blob/master/spec/ics-003-connection-semantics): Implemented in [`x/ibc/03-connection`](https://github.com/cosmos/x/ibc/03-connection)
* [ICS 004 - Channel and Packet Semantics](https://github.com/cosmos/ics/blob/master/spec/ics-004-channel-and-packet-semantics): Implemented in [`x/ibc/04-channel`](https://github.com/cosmos/x/ibc/04-channel)
* [ICS 005 - Port Allocation](https://github.com/cosmos/ics/blob/master/spec/ics-005-port-allocation): Implemented in [`x/ibc/05-port`](https://github.com/cosmos/x/ibc/05-port)
* [ICS 006 - Solo Machine Client](https://github.com/cosmos/ics/blob/master/spec/ics-006-solo-machine-client): Implemented in [`x/ibc/06-solomachine`](https://github.com/cosmos/x/ibc/06-solomachine)
* [ICS 006 - Solo Machine Client](https://github.com/cosmos/ics/blob/master/spec/ics-006-solo-machine-client): Implemented in [`x/ibc/light-clients/solomachine`](https://github.com/cosmos/x/ibc/solomachine)
* [ICS 007 - Tendermint Client](https://github.com/cosmos/ics/blob/master/spec/ics-007-tendermint-client): Implemented in [`x/ibc/07-tendermint`](https://github.com/cosmos/x/ibc/07-tendermint)
* [ICS 009 - Loopback Client](https://github.com/cosmos/ics/blob/master/spec/ics-009-loopback-client): Implemented in [`x/ibc/09-localhost`](https://github.com/cosmos/x/ibc/09-localhost)
* [ICS 018- Relayer Algorithms](https://github.com/cosmos/ics/tree/master/spec/ics-018-relayer-algorithms): Implemented in it's own [relayer repository](https://github.com/cosmos/relayer)
@ -86,7 +86,8 @@ x/
│ ├── 03-connection/
│ ├── 04-channel/
│ ├── 05-port/
│ ├── 06-solo/
│ ├── light-clients/
│ │ └── solomachine/
│ ├── 07-tendermint/
│ ├── 09-localhost/
│ ├── 23-commitment/

View File

@ -0,0 +1,111 @@
package testing
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/cosmos/cosmos-sdk/std"
sdk "github.com/cosmos/cosmos-sdk/types"
solomachinetypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
// Solomachine is a testing helper used to simulate a counterparty
// solo machine client.
type Solomachine struct {
t *testing.T
ClientID string
PrivateKey crypto.PrivKey
PublicKey crypto.PubKey
Sequence uint64
Time uint64
}
// NewSolomachine returns a new solomachine instance with a generated private/public
// key pair and a sequence starting at 1.
func NewSolomachine(t *testing.T, clientID string) *Solomachine {
privKey := ed25519.GenPrivKey()
return &Solomachine{
t: t,
ClientID: clientID,
PrivateKey: privKey,
PublicKey: privKey.PubKey(),
Sequence: 1,
Time: 10,
}
}
func (solo *Solomachine) ClientState() *solomachinetypes.ClientState {
return solomachinetypes.NewClientState(solo.ConsensusState())
}
func (solo *Solomachine) ConsensusState() *solomachinetypes.ConsensusState {
publicKey, err := std.DefaultPublicKeyCodec{}.Encode(solo.PublicKey)
require.NoError(solo.t, err)
return &solomachinetypes.ConsensusState{
Sequence: solo.Sequence,
PublicKey: publicKey,
Timestamp: solo.Time,
}
}
// CreateHeader generates a new private/public key pair and creates the
// necessary signature to construct a valid solo machine header.
func (solo *Solomachine) CreateHeader() solomachinetypes.Header {
// generate new private key and signature for header
newPrivKey := ed25519.GenPrivKey()
data := append(sdk.Uint64ToBigEndian(solo.Sequence), newPrivKey.PubKey().Bytes()...)
signature, err := solo.PrivateKey.Sign(data)
require.NoError(solo.t, err)
publicKey, err := std.DefaultPublicKeyCodec{}.Encode(newPrivKey.PubKey())
require.NoError(solo.t, err)
header := solomachinetypes.Header{
Sequence: solo.Sequence,
Signature: signature,
NewPublicKey: publicKey,
}
// assumes successful header update
solo.Sequence++
solo.PrivateKey = newPrivKey
solo.PublicKey = newPrivKey.PubKey()
return header
}
// CreateEvidence constructs testing evidence for the solo machine client
// by signing over two different data bytes at the same sequence.
func (solo *Solomachine) CreateEvidence() solomachinetypes.Evidence {
dataOne := []byte("DATA ONE")
dataTwo := []byte("DATA TWO")
sig, err := solo.PrivateKey.Sign(append(sdk.Uint64ToBigEndian(solo.Sequence), dataOne...))
require.NoError(solo.t, err)
signatureOne := solomachinetypes.SignatureAndData{
Signature: sig,
Data: dataOne,
}
sig, err = solo.PrivateKey.Sign(append(sdk.Uint64ToBigEndian(solo.Sequence), dataTwo...))
require.NoError(solo.t, err)
signatureTwo := solomachinetypes.SignatureAndData{
Signature: sig,
Data: dataTwo,
}
return solomachinetypes.Evidence{
ClientId: solo.ClientID,
Sequence: solo.Sequence,
SignatureOne: &signatureOne,
SignatureTwo: &signatureTwo,
}
}

View File

@ -9,6 +9,7 @@ import (
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
solomachinetypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types"
)
// RegisterCodec registers the necessary x/ibc interfaces and concrete types
@ -25,6 +26,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
clienttypes.RegisterInterfaces(registry)
connectiontypes.RegisterInterfaces(registry)
channeltypes.RegisterInterfaces(registry)
solomachinetypes.RegisterInterfaces(registry)
ibctmtypes.RegisterInterfaces(registry)
localhosttypes.RegisterInterfaces(registry)
commitmenttypes.RegisterInterfaces(registry)