Modular IBC Client (#7028)
* start modular client work * fix panic * reuse keeper marshal methods * readd TODO * add nil checks for misbehaviour * address reviews * address rest of reviews and fix builds * fixed tests * address rest of reviews * fix expired blocks bug * fix expired bug
This commit is contained in:
parent
4c762db64e
commit
3735b182bc
|
@ -35,7 +35,7 @@ var DefaultConsensusParams = &abci.ConsensusParams{
|
||||||
},
|
},
|
||||||
Evidence: &abci.EvidenceParams{
|
Evidence: &abci.EvidenceParams{
|
||||||
MaxAgeNumBlocks: 302400,
|
MaxAgeNumBlocks: 302400,
|
||||||
MaxAgeDuration: 1814400,
|
MaxAgeDuration: 504 * time.Hour, // 3 weeks is the max duration
|
||||||
},
|
},
|
||||||
Validator: &abci.ValidatorParams{
|
Validator: &abci.ValidatorParams{
|
||||||
PubKeyTypes: []string{
|
PubKeyTypes: []string{
|
||||||
|
|
|
@ -3,29 +3,24 @@ package client_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
"github.com/cosmos/cosmos-sdk/simapp"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientTestSuite struct {
|
type ClientTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
|
||||||
cdc *codec.LegacyAmino
|
coordinator *ibctesting.Coordinator
|
||||||
ctx sdk.Context
|
|
||||||
app *simapp.SimApp
|
chainA *ibctesting.TestChain
|
||||||
|
chainB *ibctesting.TestChain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ClientTestSuite) SetupTest() {
|
func (suite *ClientTestSuite) SetupTest() {
|
||||||
isCheckTx := false
|
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2)
|
||||||
|
|
||||||
suite.app = simapp.Setup(isCheckTx)
|
|
||||||
suite.cdc = suite.app.LegacyAmino()
|
|
||||||
suite.ctx = suite.app.BaseApp.NewContext(isCheckTx, abci.Header{Height: 0, ChainID: "localhost_chain"})
|
|
||||||
|
|
||||||
|
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0))
|
||||||
|
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientTestSuite(t *testing.T) {
|
func TestClientTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -25,6 +25,11 @@ type ClientState interface {
|
||||||
Validate() error
|
Validate() error
|
||||||
GetProofSpecs() []*ics23.ProofSpec
|
GetProofSpecs() []*ics23.ProofSpec
|
||||||
|
|
||||||
|
// Update and Misbehaviour functions
|
||||||
|
|
||||||
|
CheckHeaderAndUpdateState(sdk.Context, codec.BinaryMarshaler, sdk.KVStore, Header) (ClientState, ConsensusState, error)
|
||||||
|
CheckMisbehaviourAndUpdateState(sdk.Context, codec.BinaryMarshaler, sdk.KVStore, Misbehaviour) (ClientState, error)
|
||||||
|
|
||||||
// State verification functions
|
// State verification functions
|
||||||
|
|
||||||
VerifyClientConsensusState(
|
VerifyClientConsensusState(
|
||||||
|
@ -138,6 +143,7 @@ type MsgCreateClient interface {
|
||||||
GetClientID() string
|
GetClientID() string
|
||||||
GetClientType() string
|
GetClientType() string
|
||||||
GetConsensusState() ConsensusState
|
GetConsensusState() ConsensusState
|
||||||
|
InitializeClientState() ClientState
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgUpdateClient defines the msg interface that the
|
// MsgUpdateClient defines the msg interface that the
|
||||||
|
|
|
@ -10,34 +10,28 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper"
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
||||||
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
|
||||||
localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types"
|
localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleMsgCreateClient defines the sdk.Handler for MsgCreateClient
|
// HandleMsgCreateClient defines the sdk.Handler for MsgCreateClient
|
||||||
func HandleMsgCreateClient(ctx sdk.Context, k keeper.Keeper, msg exported.MsgCreateClient) (*sdk.Result, error) {
|
func HandleMsgCreateClient(ctx sdk.Context, k keeper.Keeper, msg exported.MsgCreateClient) (*sdk.Result, error) {
|
||||||
clientType := exported.ClientTypeFromString(msg.GetClientType())
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
clientState exported.ClientState
|
|
||||||
consensusHeight uint64
|
consensusHeight uint64
|
||||||
|
clientState exported.ClientState
|
||||||
)
|
)
|
||||||
|
|
||||||
switch clientType {
|
switch msg.(type) {
|
||||||
case exported.Tendermint:
|
// localhost is a special case that must initialize client state
|
||||||
tmMsg, ok := msg.(*ibctmtypes.MsgCreateClient)
|
// from context and not from msg
|
||||||
if !ok {
|
case *localhosttypes.MsgCreateClient:
|
||||||
return nil, sdkerrors.Wrapf(types.ErrInvalidClientType, "got %T, expected %T", msg, &ibctmtypes.MsgCreateClient{})
|
|
||||||
}
|
|
||||||
|
|
||||||
clientState = ibctmtypes.InitializeFromMsg(tmMsg)
|
|
||||||
consensusHeight = msg.GetConsensusState().GetHeight()
|
|
||||||
case exported.Localhost:
|
|
||||||
// msg client id is always "localhost"
|
|
||||||
clientState = localhosttypes.NewClientState(ctx.ChainID(), ctx.BlockHeight())
|
clientState = localhosttypes.NewClientState(ctx.ChainID(), ctx.BlockHeight())
|
||||||
|
// Localhost consensus height is chain's blockheight
|
||||||
consensusHeight = uint64(ctx.BlockHeight())
|
consensusHeight = uint64(ctx.BlockHeight())
|
||||||
default:
|
default:
|
||||||
return nil, sdkerrors.Wrapf(types.ErrInvalidClientType, "unsupported client type (%s)", msg.GetClientType())
|
clientState = msg.InitializeClientState()
|
||||||
|
if consState := msg.GetConsensusState(); consState != nil {
|
||||||
|
consensusHeight = consState.GetHeight()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := k.CreateClient(
|
_, err := k.CreateClient(
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package client_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
client "github.com/cosmos/cosmos-sdk/x/ibc/02-client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
|
localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *ClientTestSuite) TestHandleCreateClientLocalHost() {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
clientID string
|
||||||
|
msg exported.MsgCreateClient
|
||||||
|
expPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"localhost",
|
||||||
|
exported.ClientTypeLocalHost,
|
||||||
|
&localhosttypes.MsgCreateClient{suite.chainA.SenderAccount.GetAddress()},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tendermint client",
|
||||||
|
"gaiamainnet",
|
||||||
|
suite.chainA.ConstructMsgCreateClient(suite.chainB, "gaiamainnet"),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client already exists",
|
||||||
|
exported.ClientTypeLocalHost,
|
||||||
|
&localhosttypes.MsgCreateClient{suite.chainA.SenderAccount.GetAddress()},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
_, err := client.HandleMsgCreateClient(
|
||||||
|
suite.chainA.GetContext(),
|
||||||
|
suite.chainA.App.IBCKeeper.ClientKeeper,
|
||||||
|
tc.msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tc.expPass {
|
||||||
|
suite.Require().NoError(err, "expected test case %s to pass, got error %v", tc.name, err)
|
||||||
|
|
||||||
|
clientState, ok := suite.chainA.App.IBCKeeper.ClientKeeper.GetClientState(suite.chainA.GetContext(), tc.clientID)
|
||||||
|
suite.Require().True(ok, "could not retrieve clientState")
|
||||||
|
suite.Require().NotNil(clientState, "clientstate is nil")
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err, "invalid test case %s passed", tc.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,6 @@ import (
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
||||||
tendermint "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint"
|
|
||||||
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
|
||||||
localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateClient creates a new client state and populates it with a given consensus
|
// CreateClient creates a new client state and populates it with a given consensus
|
||||||
|
@ -69,34 +66,7 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
switch clientType {
|
clientState, consensusState, err = clientState.CheckHeaderAndUpdateState(ctx, k.cdc, k.ClientStore(ctx, clientID), header)
|
||||||
case exported.Tendermint:
|
|
||||||
tmHeader, ok := header.(ibctmtypes.Header)
|
|
||||||
if !ok {
|
|
||||||
err = sdkerrors.Wrapf(types.ErrInvalidHeader, "expected tendermint header: %T, got header type: %T", ibctmtypes.Header{}, header)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Get the consensus state at the trusted height of header
|
|
||||||
trustedConsState, found := k.GetClientConsensusState(ctx, clientID, tmHeader.TrustedHeight)
|
|
||||||
if !found {
|
|
||||||
return nil, sdkerrors.Wrapf(types.ErrConsensusStateNotFound, "could not find consensus state for trusted header height: %d to verify header against for clientID: %s", tmHeader.TrustedHeight, clientID)
|
|
||||||
}
|
|
||||||
clientState, consensusState, err = tendermint.CheckValidityAndUpdateState(
|
|
||||||
clientState, trustedConsState, header, ctx.BlockTime(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
err = sdkerrors.Wrapf(err, "failed to update client using trusted consensus state height %d", trustedConsState.GetHeight())
|
|
||||||
}
|
|
||||||
case exported.Localhost:
|
|
||||||
// override client state and update the block height
|
|
||||||
clientState = localhosttypes.NewClientState(
|
|
||||||
ctx.ChainID(), // use the chain ID from context since the client is from the running chain (i.e self).
|
|
||||||
ctx.BlockHeight(),
|
|
||||||
)
|
|
||||||
consensusHeight = uint64(ctx.BlockHeight())
|
|
||||||
default:
|
|
||||||
err = types.ErrInvalidClientType
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, sdkerrors.Wrapf(err, "cannot update client with ID %s", clientID)
|
return nil, sdkerrors.Wrapf(err, "cannot update client with ID %s", clientID)
|
||||||
|
@ -136,30 +106,7 @@ func (k Keeper) CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour ex
|
||||||
return sdkerrors.Wrap(err, "IBC misbehaviour failed validate basic")
|
return sdkerrors.Wrap(err, "IBC misbehaviour failed validate basic")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, k.ClientStore(ctx, misbehaviour.GetClientID()), misbehaviour)
|
||||||
switch e := misbehaviour.(type) {
|
|
||||||
case ibctmtypes.Evidence:
|
|
||||||
// Get consensus states at TrustedHeight for each header
|
|
||||||
consensusState1, found := k.GetClientConsensusState(ctx, misbehaviour.GetClientID(), e.Header1.TrustedHeight)
|
|
||||||
if !found {
|
|
||||||
return sdkerrors.Wrapf(types.ErrConsensusStateNotFound, "could not find ConsensusState for clientID %s at TrustedHeight (%d) for first header",
|
|
||||||
misbehaviour.GetClientID(), e.Header1.TrustedHeight)
|
|
||||||
}
|
|
||||||
consensusState2, found := k.GetClientConsensusState(ctx, misbehaviour.GetClientID(), e.Header2.TrustedHeight)
|
|
||||||
if !found {
|
|
||||||
return sdkerrors.Wrapf(types.ErrConsensusStateNotFound, "could not find ConsensusState for clientID %s at TrustedHeight (%d) for second header",
|
|
||||||
misbehaviour.GetClientID(), e.Header2.TrustedHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Retrieve consensusparams from client and not context
|
|
||||||
// Issue #6516: https://github.com/cosmos/cosmos-sdk/issues/6516
|
|
||||||
clientState, err = tendermint.CheckMisbehaviourAndUpdateState(
|
|
||||||
clientState, consensusState1, consensusState2, misbehaviour, ctx.BlockTime(), ctx.ConsensusParams(),
|
|
||||||
)
|
|
||||||
|
|
||||||
default:
|
|
||||||
err = sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized IBC client evidence type: %T", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,90 +1,30 @@
|
||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MustUnmarshalClientState attempts to decode and return an ClientState object from
|
// MustUnmarshalClientState attempts to decode and return an ClientState object from
|
||||||
// raw encoded bytes. It panics on error.
|
// raw encoded bytes. It panics on error.
|
||||||
func (k Keeper) MustUnmarshalClientState(bz []byte) exported.ClientState {
|
func (k Keeper) MustUnmarshalClientState(bz []byte) exported.ClientState {
|
||||||
clientState, err := k.UnmarshalClientState(bz)
|
return types.MustUnmarshalClientState(k.cdc, bz)
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("failed to decode client state: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientState
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustMarshalClientState attempts to encode an ClientState object and returns the
|
|
||||||
// raw encoded bytes. It panics on error.
|
|
||||||
func (k Keeper) MustMarshalClientState(clientState exported.ClientState) []byte {
|
|
||||||
bz, err := k.MarshalClientState(clientState)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("failed to encode client state: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return bz
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalClientState marshals an ClientState interface. If the given type implements
|
|
||||||
// the Marshaler interface, it is treated as a Proto-defined message and
|
|
||||||
// serialized that way.
|
|
||||||
func (k Keeper) MarshalClientState(clientStateI exported.ClientState) ([]byte, error) {
|
|
||||||
return codec.MarshalAny(k.cdc, clientStateI)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalClientState returns an ClientState interface from raw encoded clientState
|
|
||||||
// bytes of a Proto-based ClientState type. An error is returned upon decoding
|
|
||||||
// failure.
|
|
||||||
func (k Keeper) UnmarshalClientState(bz []byte) (exported.ClientState, error) {
|
|
||||||
var clientState exported.ClientState
|
|
||||||
if err := codec.UnmarshalAny(k.cdc, &clientState, bz); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientState, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustUnmarshalConsensusState attempts to decode and return an ConsensusState object from
|
// MustUnmarshalConsensusState attempts to decode and return an ConsensusState object from
|
||||||
// raw encoded bytes. It panics on error.
|
// raw encoded bytes. It panics on error.
|
||||||
func (k Keeper) MustUnmarshalConsensusState(bz []byte) exported.ConsensusState {
|
func (k Keeper) MustUnmarshalConsensusState(bz []byte) exported.ConsensusState {
|
||||||
consensusState, err := k.UnmarshalConsensusState(bz)
|
return types.MustUnmarshalConsensusState(k.cdc, bz)
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("failed to decode consensus state: %w", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return consensusState
|
// MustMarshalClientState attempts to encode an ClientState object and returns the
|
||||||
|
// raw encoded bytes. It panics on error.
|
||||||
|
func (k Keeper) MustMarshalClientState(clientState exported.ClientState) []byte {
|
||||||
|
return types.MustMarshalClientState(k.cdc, clientState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustMarshalConsensusState attempts to encode an ConsensusState object and returns the
|
// MustMarshalConsensusState attempts to encode an ConsensusState object and returns the
|
||||||
// raw encoded bytes. It panics on error.
|
// raw encoded bytes. It panics on error.
|
||||||
func (k Keeper) MustMarshalConsensusState(consensusState exported.ConsensusState) []byte {
|
func (k Keeper) MustMarshalConsensusState(consensusState exported.ConsensusState) []byte {
|
||||||
bz, err := k.MarshalConsensusState(consensusState)
|
return types.MustMarshalConsensusState(k.cdc, consensusState)
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("failed to encode consensus state: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return bz
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalConsensusState marshals an ConsensusState interface. If the given type implements
|
|
||||||
// the Marshaler interface, it is treated as a Proto-defined message and
|
|
||||||
// serialized that way.
|
|
||||||
func (k Keeper) MarshalConsensusState(consensusStateI exported.ConsensusState) ([]byte, error) {
|
|
||||||
return codec.MarshalAny(k.cdc, consensusStateI)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalConsensusState returns an ConsensusState interface from raw encoded clientState
|
|
||||||
// bytes of a Proto-based ConsensusState type. An error is returned upon decoding
|
|
||||||
// failure.
|
|
||||||
func (k Keeper) UnmarshalConsensusState(bz []byte) (exported.ConsensusState, error) {
|
|
||||||
var consensusState exported.ConsensusState
|
|
||||||
if err := codec.UnmarshalAny(k.cdc, &consensusState, bz); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return consensusState, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,12 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/kv"
|
"github.com/cosmos/cosmos-sdk/types/kv"
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper"
|
||||||
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
|
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ ClientUnmarshaler = (*keeper.Keeper)(nil)
|
||||||
|
|
||||||
// ClientUnmarshaler defines an interface for unmarshaling ICS02 interfaces.
|
// ClientUnmarshaler defines an interface for unmarshaling ICS02 interfaces.
|
||||||
type ClientUnmarshaler interface {
|
type ClientUnmarshaler interface {
|
||||||
MustUnmarshalClientState([]byte) exported.ClientState
|
MustUnmarshalClientState([]byte) exported.ClientState
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MustUnmarshalClientState attempts to decode and return an ClientState object from
|
||||||
|
// raw encoded bytes. It panics on error.
|
||||||
|
func MustUnmarshalClientState(cdc codec.BinaryMarshaler, bz []byte) exported.ClientState {
|
||||||
|
clientState, err := UnmarshalClientState(cdc, bz)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to decode client state: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientState
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustMarshalClientState attempts to encode an ClientState object and returns the
|
||||||
|
// raw encoded bytes. It panics on error.
|
||||||
|
func MustMarshalClientState(cdc codec.BinaryMarshaler, clientState exported.ClientState) []byte {
|
||||||
|
bz, err := MarshalClientState(cdc, clientState)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to encode client state: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return bz
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalClientState marshals an ClientState interface. If the given type implements
|
||||||
|
// the Marshaler interface, it is treated as a Proto-defined message and
|
||||||
|
// serialized that way.
|
||||||
|
func MarshalClientState(cdc codec.BinaryMarshaler, clientStateI exported.ClientState) ([]byte, error) {
|
||||||
|
return codec.MarshalAny(cdc, clientStateI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalClientState returns an ClientState interface from raw encoded clientState
|
||||||
|
// bytes of a Proto-based ClientState type. An error is returned upon decoding
|
||||||
|
// failure.
|
||||||
|
func UnmarshalClientState(cdc codec.BinaryMarshaler, bz []byte) (exported.ClientState, error) {
|
||||||
|
var clientState exported.ClientState
|
||||||
|
if err := codec.UnmarshalAny(cdc, &clientState, bz); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustUnmarshalConsensusState attempts to decode and return an ConsensusState object from
|
||||||
|
// raw encoded bytes. It panics on error.
|
||||||
|
func MustUnmarshalConsensusState(cdc codec.BinaryMarshaler, bz []byte) exported.ConsensusState {
|
||||||
|
consensusState, err := UnmarshalConsensusState(cdc, bz)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to decode consensus state: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return consensusState
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustMarshalConsensusState attempts to encode an ConsensusState object and returns the
|
||||||
|
// raw encoded bytes. It panics on error.
|
||||||
|
func MustMarshalConsensusState(cdc codec.BinaryMarshaler, consensusState exported.ConsensusState) []byte {
|
||||||
|
bz, err := MarshalConsensusState(cdc, consensusState)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to encode consensus state: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return bz
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalConsensusState marshals an ConsensusState interface. If the given type implements
|
||||||
|
// the Marshaler interface, it is treated as a Proto-defined message and
|
||||||
|
// serialized that way.
|
||||||
|
func MarshalConsensusState(cdc codec.BinaryMarshaler, consensusStateI exported.ConsensusState) ([]byte, error) {
|
||||||
|
return codec.MarshalAny(cdc, consensusStateI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalConsensusState returns an ConsensusState interface from raw encoded clientState
|
||||||
|
// bytes of a Proto-based ConsensusState type. An error is returned upon decoding
|
||||||
|
// failure.
|
||||||
|
func UnmarshalConsensusState(cdc codec.BinaryMarshaler, bz []byte) (exported.ConsensusState, error) {
|
||||||
|
var consensusState exported.ConsensusState
|
||||||
|
if err := codec.UnmarshalAny(cdc, &consensusState, bz); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return consensusState, nil
|
||||||
|
}
|
|
@ -1,70 +0,0 @@
|
||||||
package tendermint_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
tmbytes "github.com/tendermint/tendermint/libs/bytes"
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
|
||||||
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
|
||||||
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
chainID = "gaia"
|
|
||||||
height = 4
|
|
||||||
trustingPeriod time.Duration = time.Hour * 24 * 7 * 2
|
|
||||||
ubdPeriod time.Duration = time.Hour * 24 * 7 * 3
|
|
||||||
maxClockDrift time.Duration = time.Second * 10
|
|
||||||
)
|
|
||||||
|
|
||||||
type TendermintTestSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
cdc *codec.LegacyAmino
|
|
||||||
signers []tmtypes.PrivValidator
|
|
||||||
privVal tmtypes.PrivValidator
|
|
||||||
valSet *tmtypes.ValidatorSet
|
|
||||||
valsHash tmbytes.HexBytes
|
|
||||||
header ibctmtypes.Header
|
|
||||||
now time.Time
|
|
||||||
clientTime time.Time
|
|
||||||
headerTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *TendermintTestSuite) SetupTest() {
|
|
||||||
suite.cdc = codec.New()
|
|
||||||
cryptocodec.RegisterCrypto(suite.cdc)
|
|
||||||
ibctmtypes.RegisterCodec(suite.cdc)
|
|
||||||
commitmenttypes.RegisterCodec(suite.cdc)
|
|
||||||
|
|
||||||
// now is the time of the current chain, must be after the updating header
|
|
||||||
// mocks ctx.BlockTime()
|
|
||||||
suite.now = time.Date(2020, 1, 3, 0, 0, 0, 0, time.UTC)
|
|
||||||
suite.clientTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
// Header time is intended to be time for any new header used for updates
|
|
||||||
suite.headerTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
|
|
||||||
suite.privVal = tmtypes.NewMockPV()
|
|
||||||
suite.signers = []tmtypes.PrivValidator{suite.privVal}
|
|
||||||
|
|
||||||
pubKey, err := suite.privVal.GetPubKey()
|
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
val := tmtypes.NewValidator(pubKey, 10)
|
|
||||||
suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{val})
|
|
||||||
suite.valsHash = suite.valSet.Hash()
|
|
||||||
|
|
||||||
// Suite header is intended to be header passed in for initial ClientState
|
|
||||||
// Thus it should have same height and time as ClientState
|
|
||||||
// Note: default header has the same validator set suite.valSet as next validators set
|
|
||||||
suite.header = ibctmtypes.CreateTestHeader(chainID, height, height-1, suite.clientTime, suite.valSet, suite.valSet, suite.signers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTendermintTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(TendermintTestSuite))
|
|
||||||
}
|
|
|
@ -23,14 +23,6 @@ import (
|
||||||
|
|
||||||
var _ clientexported.ClientState = (*ClientState)(nil)
|
var _ clientexported.ClientState = (*ClientState)(nil)
|
||||||
|
|
||||||
// InitializeFromMsg creates a tendermint client state from a CreateClientMsg
|
|
||||||
func InitializeFromMsg(msg *MsgCreateClient) *ClientState {
|
|
||||||
return NewClientState(msg.Header.ChainID, msg.TrustLevel,
|
|
||||||
msg.TrustingPeriod, msg.UnbondingPeriod, msg.MaxClockDrift,
|
|
||||||
uint64(msg.Header.Height), msg.ProofSpecs,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientState creates a new ClientState instance
|
// NewClientState creates a new ClientState instance
|
||||||
func NewClientState(
|
func NewClientState(
|
||||||
chainID string, trustLevel Fraction,
|
chainID string, trustLevel Fraction,
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package tendermint
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckMisbehaviourAndUpdateState determines whether or not two conflicting
|
// CheckMisbehaviourAndUpdateState determines whether or not two conflicting
|
||||||
|
@ -18,53 +18,64 @@ import (
|
||||||
// of misbehaviour.Header1
|
// of misbehaviour.Header1
|
||||||
// Similarly, consensusState2 is the trusted consensus state that corresponds
|
// Similarly, consensusState2 is the trusted consensus state that corresponds
|
||||||
// to misbehaviour.Header2
|
// to misbehaviour.Header2
|
||||||
func CheckMisbehaviourAndUpdateState(
|
func (cs ClientState) CheckMisbehaviourAndUpdateState(
|
||||||
clientState clientexported.ClientState,
|
ctx sdk.Context,
|
||||||
consensusState1, consensusState2 clientexported.ConsensusState,
|
cdc codec.BinaryMarshaler,
|
||||||
|
clientStore sdk.KVStore,
|
||||||
misbehaviour clientexported.Misbehaviour,
|
misbehaviour clientexported.Misbehaviour,
|
||||||
currentTimestamp time.Time,
|
|
||||||
consensusParams *abci.ConsensusParams,
|
|
||||||
) (clientexported.ClientState, error) {
|
) (clientexported.ClientState, error) {
|
||||||
|
|
||||||
// cast the interface to specific types before checking for misbehaviour
|
|
||||||
tmClientState, ok := clientState.(*types.ClientState)
|
|
||||||
if !ok {
|
|
||||||
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", &types.ClientState{}, clientState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If client is already frozen at earlier height than evidence, return with error
|
// If client is already frozen at earlier height than evidence, return with error
|
||||||
if tmClientState.IsFrozen() && tmClientState.FrozenHeight <= uint64(misbehaviour.GetHeight()) {
|
if cs.IsFrozen() && cs.FrozenHeight <= uint64(misbehaviour.GetHeight()) {
|
||||||
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence,
|
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence,
|
||||||
"client is already frozen at earlier height %d than misbehaviour height %d", tmClientState.FrozenHeight, misbehaviour.GetHeight())
|
"client is already frozen at earlier height %d than misbehaviour height %d", cs.FrozenHeight, misbehaviour.GetHeight())
|
||||||
}
|
}
|
||||||
|
|
||||||
tmConsensusState1, ok := consensusState1.(*types.ConsensusState)
|
tmEvidence, ok := misbehaviour.(Evidence)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "invalid consensus state type for first header: expected type %T, got %T", &types.ConsensusState{}, consensusState1)
|
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", misbehaviour, Evidence{})
|
||||||
}
|
|
||||||
tmConsensusState2, ok := consensusState2.(*types.ConsensusState)
|
|
||||||
if !ok {
|
|
||||||
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "invalid consensus state for second header: expected type %T, got %T", &types.ConsensusState{}, consensusState2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tmEvidence, ok := misbehaviour.(types.Evidence)
|
// Retrieve trusted consensus states for each Header in misbehaviour
|
||||||
|
// and unmarshal from clientStore
|
||||||
|
|
||||||
|
// Get consensus bytes from clientStore
|
||||||
|
consBytes1 := clientStore.Get(host.KeyConsensusState(tmEvidence.Header1.TrustedHeight))
|
||||||
|
if consBytes1 == nil {
|
||||||
|
return nil, sdkerrors.Wrapf(clienttypes.ErrConsensusStateNotFound,
|
||||||
|
"could not find trusted consensus state at height %d", tmEvidence.Header1.TrustedHeight)
|
||||||
|
}
|
||||||
|
// Unmarshal consensus bytes into clientexported.ConensusState
|
||||||
|
consensusState1 := clienttypes.MustUnmarshalConsensusState(cdc, consBytes1)
|
||||||
|
// Cast to tendermint-specific type
|
||||||
|
tmConsensusState1, ok := consensusState1.(*ConsensusState)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", misbehaviour, types.Evidence{})
|
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "invalid consensus state type for first header: expected type %T, got %T", &ConsensusState{}, consensusState1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use earliest height of trusted consensus states to verify ageBlocks
|
// Get consensus bytes from clientStore
|
||||||
var height uint64
|
consBytes2 := clientStore.Get(host.KeyConsensusState(tmEvidence.Header2.TrustedHeight))
|
||||||
if tmConsensusState1.Height < tmConsensusState2.Height {
|
if consBytes2 == nil {
|
||||||
height = tmConsensusState1.Height
|
return nil, sdkerrors.Wrapf(clienttypes.ErrConsensusStateNotFound,
|
||||||
} else {
|
"could not find trusted consensus state at height %d", tmEvidence.Header2.TrustedHeight)
|
||||||
height = tmConsensusState2.Height
|
}
|
||||||
|
// Unmarshal consensus bytes into clientexported.ConensusState
|
||||||
|
consensusState2 := clienttypes.MustUnmarshalConsensusState(cdc, consBytes2)
|
||||||
|
// Cast to tendermint-specific type
|
||||||
|
tmConsensusState2, ok := consensusState2.(*ConsensusState)
|
||||||
|
if !ok {
|
||||||
|
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "invalid consensus state for second header: expected type %T, got %T", &ConsensusState{}, consensusState2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate the age of the misbehaviour evidence
|
// calculate the age of the misbehaviour evidence
|
||||||
infractionHeight := tmEvidence.GetHeight()
|
infractionHeight := tmEvidence.GetHeight()
|
||||||
infractionTime := tmEvidence.GetTime()
|
infractionTime := tmEvidence.GetTime()
|
||||||
ageDuration := currentTimestamp.Sub(infractionTime)
|
ageDuration := ctx.BlockTime().Sub(infractionTime)
|
||||||
ageBlocks := uint64(infractionHeight) - height
|
ageBlocks := int64(cs.LatestHeight) - infractionHeight
|
||||||
|
|
||||||
|
// TODO: Retrieve consensusparams from client state and not context
|
||||||
|
// Issue #6516: https://github.com/cosmos/cosmos-sdk/issues/6516
|
||||||
|
consensusParams := ctx.ConsensusParams()
|
||||||
|
|
||||||
// Reject misbehaviour if the age is too old. Evidence is considered stale
|
// Reject misbehaviour if the age is too old. Evidence is considered stale
|
||||||
// if the difference in time and number of blocks is greater than the allowed
|
// if the difference in time and number of blocks is greater than the allowed
|
||||||
|
@ -76,8 +87,8 @@ func CheckMisbehaviourAndUpdateState(
|
||||||
// use the default values.
|
// use the default values.
|
||||||
if consensusParams != nil &&
|
if consensusParams != nil &&
|
||||||
consensusParams.Evidence != nil &&
|
consensusParams.Evidence != nil &&
|
||||||
ageDuration > consensusParams.Evidence.MaxAgeDuration &&
|
(ageDuration > consensusParams.Evidence.MaxAgeDuration ||
|
||||||
ageBlocks > uint64(consensusParams.Evidence.MaxAgeNumBlocks) {
|
ageBlocks > consensusParams.Evidence.MaxAgeNumBlocks) {
|
||||||
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence,
|
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence,
|
||||||
"age duration (%s) and age blocks (%d) are greater than max consensus params for duration (%s) and block (%d)",
|
"age duration (%s) and age blocks (%d) are greater than max consensus params for duration (%s) and block (%d)",
|
||||||
ageDuration, ageBlocks, consensusParams.Evidence.MaxAgeDuration, consensusParams.Evidence.MaxAgeNumBlocks,
|
ageDuration, ageBlocks, consensusParams.Evidence.MaxAgeDuration, consensusParams.Evidence.MaxAgeNumBlocks,
|
||||||
|
@ -90,24 +101,24 @@ func CheckMisbehaviourAndUpdateState(
|
||||||
// evidence.ValidateBasic by the client keeper and msg.ValidateBasic
|
// evidence.ValidateBasic by the client keeper and msg.ValidateBasic
|
||||||
// by the base application.
|
// by the base application.
|
||||||
if err := checkMisbehaviourHeader(
|
if err := checkMisbehaviourHeader(
|
||||||
tmClientState, tmConsensusState1, tmEvidence.Header1, currentTimestamp,
|
&cs, tmConsensusState1, tmEvidence.Header1, ctx.BlockTime(),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, sdkerrors.Wrap(err, "verifying Header1 in Evidence failed")
|
return nil, sdkerrors.Wrap(err, "verifying Header1 in Evidence failed")
|
||||||
}
|
}
|
||||||
if err := checkMisbehaviourHeader(
|
if err := checkMisbehaviourHeader(
|
||||||
tmClientState, tmConsensusState2, tmEvidence.Header2, currentTimestamp,
|
&cs, tmConsensusState2, tmEvidence.Header2, ctx.BlockTime(),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, sdkerrors.Wrap(err, "verifying Header2 in Evidence failed")
|
return nil, sdkerrors.Wrap(err, "verifying Header2 in Evidence failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
tmClientState.FrozenHeight = uint64(tmEvidence.GetHeight())
|
cs.FrozenHeight = uint64(tmEvidence.GetHeight())
|
||||||
return tmClientState, nil
|
return &cs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkMisbehaviourHeader checks that a Header in Misbehaviour is valid evidence given
|
// checkMisbehaviourHeader checks that a Header in Misbehaviour is valid evidence given
|
||||||
// a trusted ConsensusState
|
// a trusted ConsensusState
|
||||||
func checkMisbehaviourHeader(
|
func checkMisbehaviourHeader(
|
||||||
clientState *types.ClientState, consState *types.ConsensusState, header types.Header, currentTimestamp time.Time,
|
clientState *ClientState, consState *ConsensusState, header Header, currentTimestamp time.Time,
|
||||||
) error {
|
) error {
|
||||||
// check the trusted fields for the header against ConsensusState
|
// check the trusted fields for the header against ConsensusState
|
||||||
if err := checkTrustedHeader(header, consState); err != nil {
|
if err := checkTrustedHeader(header, consState); err != nil {
|
||||||
|
@ -117,7 +128,7 @@ func checkMisbehaviourHeader(
|
||||||
// assert that the timestamp is not from more than an unbonding period ago
|
// assert that the timestamp is not from more than an unbonding period ago
|
||||||
if currentTimestamp.Sub(consState.Timestamp) >= clientState.UnbondingPeriod {
|
if currentTimestamp.Sub(consState.Timestamp) >= clientState.UnbondingPeriod {
|
||||||
return sdkerrors.Wrapf(
|
return sdkerrors.Wrapf(
|
||||||
types.ErrUnbondingPeriodExpired,
|
ErrUnbondingPeriodExpired,
|
||||||
"current timestamp minus the latest consensus state timestamp is greater than or equal to the unbonding period (%s >= %s)",
|
"current timestamp minus the latest consensus state timestamp is greater than or equal to the unbonding period (%s >= %s)",
|
||||||
currentTimestamp.Sub(consState.Timestamp), clientState.UnbondingPeriod,
|
currentTimestamp.Sub(consState.Timestamp), clientState.UnbondingPeriod,
|
||||||
)
|
)
|
|
@ -1,16 +1,15 @@
|
||||||
package tendermint_test
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
fmt "fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/simapp"
|
"github.com/cosmos/cosmos-sdk/simapp"
|
||||||
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
tendermint "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
"github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
||||||
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
||||||
)
|
)
|
||||||
|
@ -48,7 +47,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
consensusState1 clientexported.ConsensusState
|
consensusState1 clientexported.ConsensusState
|
||||||
consensusState2 clientexported.ConsensusState
|
consensusState2 clientexported.ConsensusState
|
||||||
evidence clientexported.Misbehaviour
|
evidence clientexported.Misbehaviour
|
||||||
consensusParams *abci.ConsensusParams
|
|
||||||
timestamp time.Time
|
timestamp time.Time
|
||||||
expPass bool
|
expPass bool
|
||||||
}{
|
}{
|
||||||
|
@ -63,7 +61,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
@ -78,7 +75,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
@ -93,7 +89,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
@ -108,7 +103,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
@ -123,7 +117,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -138,22 +131,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"invalid tendermint client state",
|
|
||||||
nil,
|
|
||||||
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
|
||||||
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
|
||||||
types.Evidence{
|
|
||||||
Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners),
|
|
||||||
Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, altValSet, bothSigners),
|
|
||||||
ChainID: chainID,
|
|
||||||
ClientID: chainID,
|
|
||||||
},
|
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -168,22 +145,20 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"invalid tendermint consensus state",
|
"trusted consensus state does not exist",
|
||||||
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()),
|
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()),
|
||||||
nil,
|
nil, // consensus state for trusted height - 1 does not exist in store
|
||||||
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
||||||
types.Evidence{
|
types.Evidence{
|
||||||
Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners),
|
Header1: types.CreateTestHeader(chainID, height, height-1, suite.now, bothValSet, bothValSet, bothSigners),
|
||||||
Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners),
|
Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners),
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -193,27 +168,37 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
||||||
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
||||||
nil,
|
nil,
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rejected misbehaviour due to expired age",
|
"rejected misbehaviour due to expired age duration",
|
||||||
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()),
|
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()),
|
||||||
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
||||||
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
||||||
types.Evidence{
|
types.Evidence{
|
||||||
Header1: types.CreateTestHeader(chainID, int64(2*height+uint64(simapp.DefaultConsensusParams.Evidence.MaxAgeNumBlocks)), height,
|
Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners),
|
||||||
suite.now, bothValSet, bothValSet, bothSigners),
|
Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners),
|
||||||
Header2: types.CreateTestHeader(chainID, int64(2*height+uint64(simapp.DefaultConsensusParams.Evidence.MaxAgeNumBlocks)), height,
|
|
||||||
suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners),
|
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now.Add(2 * time.Minute).Add(simapp.DefaultConsensusParams.Evidence.MaxAgeDuration),
|
suite.now.Add(2 * time.Minute).Add(simapp.DefaultConsensusParams.Evidence.MaxAgeDuration),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rejected misbehaviour due to expired block duration",
|
||||||
|
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, uint64(height+simapp.DefaultConsensusParams.Evidence.MaxAgeNumBlocks+1), commitmenttypes.GetSDKSpecs()),
|
||||||
|
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
||||||
|
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
||||||
|
types.Evidence{
|
||||||
|
Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners),
|
||||||
|
Header2: types.CreateTestHeader(chainID, height, height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners),
|
||||||
|
ChainID: chainID,
|
||||||
|
ClientID: chainID,
|
||||||
|
},
|
||||||
|
suite.now.Add(time.Hour),
|
||||||
|
false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"provided height > header height",
|
"provided height > header height",
|
||||||
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()),
|
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()),
|
||||||
|
@ -225,14 +210,13 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unbonding period expired",
|
"unbonding period expired",
|
||||||
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()),
|
types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()),
|
||||||
types.ConsensusState{Timestamp: time.Time{}, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), NextValidatorsHash: bothValsHash},
|
&types.ConsensusState{Timestamp: time.Time{}, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), NextValidatorsHash: bothValsHash},
|
||||||
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash),
|
||||||
types.Evidence{
|
types.Evidence{
|
||||||
Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners),
|
Header1: types.CreateTestHeader(chainID, height, height, suite.now, bothValSet, bothValSet, bothSigners),
|
||||||
|
@ -240,8 +224,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
suite.now.Add(ubdPeriod),
|
||||||
suite.now,
|
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -255,7 +238,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -270,7 +252,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -285,7 +266,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -300,7 +280,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
ClientID: chainID,
|
ClientID: chainID,
|
||||||
},
|
},
|
||||||
simapp.DefaultConsensusParams,
|
|
||||||
suite.now,
|
suite.now,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -308,8 +287,34 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
|
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
suite.Run(fmt.Sprintf("Case: %s", tc.name), func() {
|
||||||
|
|
||||||
clientState, err := tendermint.CheckMisbehaviourAndUpdateState(tc.clientState, tc.consensusState1, tc.consensusState2, tc.evidence, tc.timestamp, tc.consensusParams)
|
if i != 10 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset suite to create fresh application state
|
||||||
|
suite.SetupTest()
|
||||||
|
|
||||||
|
// Set current timestamp in context
|
||||||
|
ctx := suite.chainA.GetContext().WithBlockTime(tc.timestamp)
|
||||||
|
ctx = ctx.WithConsensusParams(simapp.DefaultConsensusParams)
|
||||||
|
|
||||||
|
// Set trusted consensus states in client store
|
||||||
|
|
||||||
|
if tc.consensusState1 != nil {
|
||||||
|
suite.chainA.App.IBCKeeper.ClientKeeper.SetClientConsensusState(ctx, clientID, tc.consensusState1.GetHeight(), tc.consensusState1)
|
||||||
|
}
|
||||||
|
if tc.consensusState2 != nil {
|
||||||
|
suite.chainA.App.IBCKeeper.ClientKeeper.SetClientConsensusState(ctx, clientID, tc.consensusState2.GetHeight(), tc.consensusState2)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientState, err := tc.clientState.CheckMisbehaviourAndUpdateState(
|
||||||
|
ctx,
|
||||||
|
suite.cdc,
|
||||||
|
suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(ctx, clientID), // pass in clientID prefixed clientStore
|
||||||
|
tc.evidence,
|
||||||
|
)
|
||||||
|
|
||||||
if tc.expPass {
|
if tc.expPass {
|
||||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
||||||
|
@ -321,5 +326,6 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() {
|
||||||
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
|
||||||
suite.Require().Nil(clientState, "invalid test case %d passed: %s", i, tc.name)
|
suite.Require().Nil(clientState, "invalid test case %d passed: %s", i, tc.name)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -148,6 +148,14 @@ func (msg MsgCreateClient) GetConsensusState() clientexported.ConsensusState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitializeFromMsg creates a tendermint client state from a CreateClientMsg
|
||||||
|
func (msg MsgCreateClient) InitializeClientState() clientexported.ClientState {
|
||||||
|
return NewClientState(msg.Header.ChainID, msg.TrustLevel,
|
||||||
|
msg.TrustingPeriod, msg.UnbondingPeriod, msg.MaxClockDrift,
|
||||||
|
uint64(msg.Header.Height), msg.ProofSpecs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// MsgUpdateClient defines a message to update an IBC client
|
// MsgUpdateClient defines a message to update an IBC client
|
||||||
type MsgUpdateClient struct {
|
type MsgUpdateClient struct {
|
||||||
ClientID string `json:"client_id" yaml:"client_id"`
|
ClientID string `json:"client_id" yaml:"client_id"`
|
||||||
|
|
|
@ -44,6 +44,8 @@ type TendermintTestSuite struct {
|
||||||
valsHash tmbytes.HexBytes
|
valsHash tmbytes.HexBytes
|
||||||
header ibctmtypes.Header
|
header ibctmtypes.Header
|
||||||
now time.Time
|
now time.Time
|
||||||
|
headerTime time.Time
|
||||||
|
clientTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *TendermintTestSuite) SetupTest() {
|
func (suite *TendermintTestSuite) SetupTest() {
|
||||||
|
@ -58,7 +60,13 @@ func (suite *TendermintTestSuite) SetupTest() {
|
||||||
suite.aminoCdc = app.LegacyAmino()
|
suite.aminoCdc = app.LegacyAmino()
|
||||||
suite.cdc = app.AppCodec()
|
suite.cdc = app.AppCodec()
|
||||||
|
|
||||||
|
// now is the time of the current chain, must be after the updating header
|
||||||
|
// mocks ctx.BlockTime()
|
||||||
suite.now = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
|
suite.now = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.clientTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
// Header time is intended to be time for any new header used for updates
|
||||||
|
suite.headerTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
suite.privVal = tmtypes.NewMockPV()
|
suite.privVal = tmtypes.NewMockPV()
|
||||||
|
|
||||||
pubKey, err := suite.privVal.GetPubKey()
|
pubKey, err := suite.privVal.GetPubKey()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package tendermint
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -7,14 +7,16 @@ import (
|
||||||
lite "github.com/tendermint/tendermint/lite2"
|
lite "github.com/tendermint/tendermint/lite2"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
|
||||||
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
|
||||||
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
||||||
|
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckValidityAndUpdateState checks if the provided header is valid, and if valid it will:
|
// CheckHeaderAndUpdateState checks if the provided header is valid, and if valid it will:
|
||||||
// create the consensus state for the header.Height
|
// create the consensus state for the header.Height
|
||||||
// and update the client state if the header height is greater than the latest client state height
|
// and update the client state if the header height is greater than the latest client state height
|
||||||
// It returns an error if:
|
// It returns an error if:
|
||||||
|
@ -33,44 +35,47 @@ import (
|
||||||
// the new latest height
|
// the new latest height
|
||||||
// Tendermint client validity checking uses the bisection algorithm described
|
// Tendermint client validity checking uses the bisection algorithm described
|
||||||
// in the [Tendermint spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md).
|
// in the [Tendermint spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md).
|
||||||
func CheckValidityAndUpdateState(
|
func (cs ClientState) CheckHeaderAndUpdateState(
|
||||||
clientState clientexported.ClientState, consState clientexported.ConsensusState,
|
ctx sdk.Context, cdc codec.BinaryMarshaler, clientStore sdk.KVStore,
|
||||||
header clientexported.Header, currentTimestamp time.Time,
|
header clientexported.Header,
|
||||||
) (clientexported.ClientState, clientexported.ConsensusState, error) {
|
) (clientexported.ClientState, clientexported.ConsensusState, error) {
|
||||||
tmClientState, ok := clientState.(*types.ClientState)
|
tmHeader, ok := header.(Header)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, sdkerrors.Wrapf(
|
return nil, nil, sdkerrors.Wrapf(
|
||||||
clienttypes.ErrInvalidClientType, "expected type %T, got %T", types.ClientState{}, clientState,
|
clienttypes.ErrInvalidHeader, "expected type %T, got %T", Header{}, header,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmConsState, ok := consState.(*types.ConsensusState)
|
// Get consensus bytes from clientStore
|
||||||
|
consBytes := clientStore.Get(host.KeyConsensusState(tmHeader.TrustedHeight))
|
||||||
|
if consBytes == nil {
|
||||||
|
return nil, nil, sdkerrors.Wrapf(
|
||||||
|
clienttypes.ErrConsensusStateNotFound, "consensus state not found for trusted height %d", tmHeader.TrustedHeight,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Unmarshal consensus bytes into clientexported.ConensusState
|
||||||
|
consState := clienttypes.MustUnmarshalConsensusState(cdc, consBytes)
|
||||||
|
// Cast to tendermint-specific type
|
||||||
|
tmConsState, ok := consState.(*ConsensusState)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, sdkerrors.Wrapf(
|
return nil, nil, sdkerrors.Wrapf(
|
||||||
clienttypes.ErrInvalidConsensus, "expected type %T, got %T", types.ConsensusState{}, consState,
|
clienttypes.ErrInvalidConsensus, "expected type %T, got %T", ConsensusState{}, consState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmHeader, ok := header.(types.Header)
|
if err := checkValidity(&cs, tmConsState, tmHeader, ctx.BlockTime()); err != nil {
|
||||||
if !ok {
|
|
||||||
return nil, nil, sdkerrors.Wrapf(
|
|
||||||
clienttypes.ErrInvalidHeader, "expected type %T, got %T", types.Header{}, header,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkValidity(tmClientState, tmConsState, tmHeader, currentTimestamp); err != nil {
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmClientState, consensusState := update(tmClientState, tmHeader)
|
newClientState, consensusState := update(&cs, tmHeader)
|
||||||
return tmClientState, consensusState, nil
|
return newClientState, consensusState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkTrustedHeader checks that consensus state matches trusted fields of Header
|
// checkTrustedHeader checks that consensus state matches trusted fields of Header
|
||||||
func checkTrustedHeader(header types.Header, consState *types.ConsensusState) error {
|
func checkTrustedHeader(header Header, consState *ConsensusState) error {
|
||||||
if header.TrustedHeight != consState.Height {
|
if header.TrustedHeight != consState.Height {
|
||||||
return sdkerrors.Wrapf(
|
return sdkerrors.Wrapf(
|
||||||
types.ErrInvalidHeaderHeight,
|
ErrInvalidHeaderHeight,
|
||||||
"trusted header height %d does not match consensus state height %d",
|
"trusted header height %d does not match consensus state height %d",
|
||||||
header.TrustedHeight, consState.Height,
|
header.TrustedHeight, consState.Height,
|
||||||
)
|
)
|
||||||
|
@ -80,7 +85,7 @@ func checkTrustedHeader(header types.Header, consState *types.ConsensusState) er
|
||||||
tvalHash := header.TrustedValidators.Hash()
|
tvalHash := header.TrustedValidators.Hash()
|
||||||
if !bytes.Equal(consState.NextValidatorsHash, tvalHash) {
|
if !bytes.Equal(consState.NextValidatorsHash, tvalHash) {
|
||||||
return sdkerrors.Wrapf(
|
return sdkerrors.Wrapf(
|
||||||
types.ErrInvalidValidatorSet,
|
ErrInvalidValidatorSet,
|
||||||
"trusted validators %s, does not hash to latest trusted validators. Expected: %X, got: %X",
|
"trusted validators %s, does not hash to latest trusted validators. Expected: %X, got: %X",
|
||||||
header.TrustedValidators, consState.NextValidatorsHash, tvalHash,
|
header.TrustedValidators, consState.NextValidatorsHash, tvalHash,
|
||||||
)
|
)
|
||||||
|
@ -91,8 +96,8 @@ func checkTrustedHeader(header types.Header, consState *types.ConsensusState) er
|
||||||
// checkValidity checks if the Tendermint header is valid.
|
// checkValidity checks if the Tendermint header is valid.
|
||||||
// CONTRACT: consState.Height == header.TrustedHeight
|
// CONTRACT: consState.Height == header.TrustedHeight
|
||||||
func checkValidity(
|
func checkValidity(
|
||||||
clientState *types.ClientState, consState *types.ConsensusState,
|
clientState *ClientState, consState *ConsensusState,
|
||||||
header types.Header, currentTimestamp time.Time,
|
header Header, currentTimestamp time.Time,
|
||||||
) error {
|
) error {
|
||||||
if err := checkTrustedHeader(header, consState); err != nil {
|
if err := checkTrustedHeader(header, consState); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -134,11 +139,11 @@ func checkValidity(
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the consensus state from a new header
|
// update the consensus state from a new header
|
||||||
func update(clientState *types.ClientState, header types.Header) (*types.ClientState, *types.ConsensusState) {
|
func update(clientState *ClientState, header Header) (*ClientState, *ConsensusState) {
|
||||||
if uint64(header.Height) > clientState.LatestHeight {
|
if uint64(header.Height) > clientState.LatestHeight {
|
||||||
clientState.LatestHeight = uint64(header.Height)
|
clientState.LatestHeight = uint64(header.Height)
|
||||||
}
|
}
|
||||||
consensusState := &types.ConsensusState{
|
consensusState := &ConsensusState{
|
||||||
Height: uint64(header.Height),
|
Height: uint64(header.Height),
|
||||||
Timestamp: header.Time,
|
Timestamp: header.Time,
|
||||||
Root: commitmenttypes.NewMerkleRoot(header.AppHash),
|
Root: commitmenttypes.NewMerkleRoot(header.AppHash),
|
|
@ -1,4 +1,4 @@
|
||||||
package tendermint_test
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -6,12 +6,11 @@ import (
|
||||||
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
tendermint "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint"
|
|
||||||
types "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
types "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
|
||||||
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (suite *TendermintTestSuite) TestCheckValidity() {
|
func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() {
|
||||||
var (
|
var (
|
||||||
clientState *types.ClientState
|
clientState *types.ClientState
|
||||||
consensusState *types.ConsensusState
|
consensusState *types.ConsensusState
|
||||||
|
@ -192,6 +191,12 @@ func (suite *TendermintTestSuite) TestCheckValidity() {
|
||||||
// setup test
|
// setup test
|
||||||
tc.setup()
|
tc.setup()
|
||||||
|
|
||||||
|
// Set current timestamp in context
|
||||||
|
ctx := suite.chainA.GetContext().WithBlockTime(currentTime)
|
||||||
|
|
||||||
|
// Set trusted consensus state in client store
|
||||||
|
suite.chainA.App.IBCKeeper.ClientKeeper.SetClientConsensusState(ctx, clientID, consensusState.Height, consensusState)
|
||||||
|
|
||||||
expectedConsensus := &types.ConsensusState{
|
expectedConsensus := &types.ConsensusState{
|
||||||
Height: uint64(newHeader.Height),
|
Height: uint64(newHeader.Height),
|
||||||
Timestamp: newHeader.Time,
|
Timestamp: newHeader.Time,
|
||||||
|
@ -199,7 +204,12 @@ func (suite *TendermintTestSuite) TestCheckValidity() {
|
||||||
NextValidatorsHash: newHeader.NextValidatorsHash,
|
NextValidatorsHash: newHeader.NextValidatorsHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
newClientState, consensusState, err := tendermint.CheckValidityAndUpdateState(clientState, consensusState, newHeader, currentTime)
|
newClientState, consensusState, err := clientState.CheckHeaderAndUpdateState(
|
||||||
|
ctx,
|
||||||
|
suite.cdc,
|
||||||
|
suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID), // pass in clientID prefixed clientStore
|
||||||
|
newHeader,
|
||||||
|
)
|
||||||
|
|
||||||
if tc.expPass {
|
if tc.expPass {
|
||||||
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)
|
|
@ -72,6 +72,25 @@ func (cs ClientState) GetProofSpecs() []*ics23.ProofSpec {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckHeaderAndUpdateState updates the localhost client. It only needs access to the context
|
||||||
|
func (cs ClientState) CheckHeaderAndUpdateState(
|
||||||
|
ctx sdk.Context, _ codec.BinaryMarshaler, _ sdk.KVStore, _ clientexported.Header,
|
||||||
|
) (clientexported.ClientState, clientexported.ConsensusState, error) {
|
||||||
|
return NewClientState(
|
||||||
|
ctx.ChainID(), // use the chain ID from context since the client is from the running chain (i.e self).
|
||||||
|
ctx.BlockHeight(),
|
||||||
|
), nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckMisbehaviourAndUpdateState implements ClientState
|
||||||
|
// Since localhost is the client of the running chain, misbehaviour cannot be submitted to it
|
||||||
|
// Thus, CheckMisbehaviourAndUpdateState returns an error for localhost
|
||||||
|
func (cs ClientState) CheckMisbehaviourAndUpdateState(
|
||||||
|
_ sdk.Context, _ codec.BinaryMarshaler, _ sdk.KVStore, _ clientexported.Misbehaviour,
|
||||||
|
) (clientexported.ClientState, error) {
|
||||||
|
return nil, sdkerrors.Wrap(clienttypes.ErrInvalidEvidence, "cannot submit misbehaviour to localhost client")
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyClientConsensusState returns an error since a local host client does not store consensus
|
// VerifyClientConsensusState returns an error since a local host client does not store consensus
|
||||||
// states.
|
// states.
|
||||||
func (cs ClientState) VerifyClientConsensusState(
|
func (cs ClientState) VerifyClientConsensusState(
|
||||||
|
|
|
@ -65,3 +65,10 @@ func (msg MsgCreateClient) GetClientType() string {
|
||||||
func (msg MsgCreateClient) GetConsensusState() clientexported.ConsensusState {
|
func (msg MsgCreateClient) GetConsensusState() clientexported.ConsensusState {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitializeClientState implements clientexported.MsgCreateClient
|
||||||
|
// localhost is a special case that require the running chain's context to initialize
|
||||||
|
// the client state, thus this function is a no-op
|
||||||
|
func (msg MsgCreateClient) InitializeClientState() clientexported.ClientState {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -333,16 +333,19 @@ func (chain *TestChain) GetFirstTestConnection(clientID, counterpartyClientID st
|
||||||
return chain.ConstructNextTestConnection(clientID, counterpartyClientID)
|
return chain.ConstructNextTestConnection(clientID, counterpartyClientID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTMClient will construct and execute a 07-tendermint MsgCreateClient. A counterparty
|
func (chain *TestChain) ConstructMsgCreateClient(counterparty *TestChain, clientID string) clientexported.MsgCreateClient {
|
||||||
// client will be created on the (target) chain.
|
return ibctmtypes.NewMsgCreateClient(
|
||||||
func (chain *TestChain) CreateTMClient(counterparty *TestChain, clientID string) error {
|
|
||||||
// construct MsgCreateClient using counterparty
|
|
||||||
msg := ibctmtypes.NewMsgCreateClient(
|
|
||||||
clientID, counterparty.LastHeader,
|
clientID, counterparty.LastHeader,
|
||||||
DefaultTrustLevel, TrustingPeriod, UnbondingPeriod, MaxClockDrift,
|
DefaultTrustLevel, TrustingPeriod, UnbondingPeriod, MaxClockDrift,
|
||||||
commitmenttypes.GetSDKSpecs(), chain.SenderAccount.GetAddress(),
|
commitmenttypes.GetSDKSpecs(), chain.SenderAccount.GetAddress(),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTMClient will construct and execute a 07-tendermint MsgCreateClient. A counterparty
|
||||||
|
// client will be created on the (target) chain.
|
||||||
|
func (chain *TestChain) CreateTMClient(counterparty *TestChain, clientID string) error {
|
||||||
|
// construct MsgCreateClient using counterparty
|
||||||
|
msg := chain.ConstructMsgCreateClient(counterparty, clientID)
|
||||||
return chain.SendMsgs(msg)
|
return chain.SendMsgs(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue