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:
parent
535c3ffcbd
commit
46927c31cf
|
@ -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];
|
||||
}
|
||||
|
|
@ -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,14 +176,16 @@ 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 (
|
||||
ClientTypeTendermint string = "tendermint"
|
||||
ClientTypeLocalHost string = "localhost"
|
||||
ClientTypeSoloMachine string = "solomachine"
|
||||
ClientTypeTendermint string = "tendermint"
|
||||
ClientTypeLocalHost string = "localhost"
|
||||
)
|
||||
|
||||
func (ct ClientType) String() string {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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/
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue