cosmos-sdk/x/ibc/03-connection/keeper/handshake.go

245 lines
9.8 KiB
Go

package keeper
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
"github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
)
// ConnOpenInit initialises a connection attempt on chain A.
//
// NOTE: Identifiers are checked on msg validation.
func (k Keeper) ConnOpenInit(
ctx sdk.Context,
connectionID, // identifier
clientID string,
counterparty types.Counterparty, // desiredCounterpartyConnectionIdentifier, counterpartyPrefix, counterpartyClientIdentifier
) error {
_, found := k.GetConnection(ctx, connectionID)
if found {
return sdkerrors.Wrap(types.ErrConnectionExists, "cannot initialize connection")
}
// connection defines chain A's ConnectionEnd
connection := types.NewConnectionEnd(types.INIT, connectionID, clientID, counterparty, types.GetCompatibleVersions())
k.SetConnection(ctx, connectionID, connection)
if err := k.addConnectionToClient(ctx, clientID, connectionID); err != nil {
return sdkerrors.Wrap(err, "cannot initialize connection")
}
k.Logger(ctx).Info(fmt.Sprintf("connection %s state updated: NONE -> INIT", connectionID))
return nil
}
// ConnOpenTry relays notice of a connection attempt on chain A to chain B (this
// code is executed on chain B).
//
// NOTE:
// - Here chain A acts as the counterparty
// - Identifiers are checked on msg validation
func (k Keeper) ConnOpenTry(
ctx sdk.Context,
connectionID string, // desiredIdentifier
counterparty types.Counterparty, // counterpartyConnectionIdentifier, counterpartyPrefix and counterpartyClientIdentifier
clientID string, // clientID of chainA
counterpartyVersions []string, // supported versions of chain A
proofInit []byte, // proof that chainA stored connectionEnd in state (on ConnOpenInit)
proofConsensus []byte, // proof that chainA stored chainB's consensus state at consensus height
proofHeight uint64, // height at which relayer constructs proof of A storing connectionEnd in state
consensusHeight uint64, // latest height of chain B which chain A has stored in its chain B client
) error {
if consensusHeight > uint64(ctx.BlockHeight()) {
return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "invalid consensus height")
}
expectedConsensusState, found := k.clientKeeper.GetSelfConsensusState(ctx, consensusHeight)
if !found {
return clienttypes.ErrSelfConsensusStateNotFound
}
// expectedConnection defines Chain A's ConnectionEnd
// NOTE: chain A's counterparty is chain B (i.e where this code is executed)
prefix := k.GetCommitmentPrefix()
expectedCounterparty := types.NewCounterparty(clientID, connectionID, commitmenttypes.NewMerklePrefix(prefix.Bytes()))
expectedConnection := types.NewConnectionEnd(types.INIT, counterparty.ConnectionID, counterparty.ClientID, expectedCounterparty, counterpartyVersions)
// chain B picks a version from Chain A's available versions that is compatible
// with the supported IBC versions
version, err := types.PickVersion(counterpartyVersions)
if err != nil {
return err
}
// connection defines chain B's ConnectionEnd
connection := types.NewConnectionEnd(types.UNINITIALIZED, connectionID, clientID, counterparty, []string{version})
// Check that ChainA committed expectedConnectionEnd to its state
if err := k.VerifyConnectionState(
ctx, connection, proofHeight, proofInit, counterparty.ConnectionID,
expectedConnection,
); err != nil {
return err
}
// Check that ChainA stored the correct ConsensusState of chainB at the given consensusHeight
if err := k.VerifyClientConsensusState(
ctx, connection, proofHeight, consensusHeight, proofConsensus, expectedConsensusState,
); err != nil {
return err
}
// If connection already exists for connectionID, ensure that the existing connection's counterparty
// is chainA and connection is on INIT stage
// Check that existing connection version is on desired version of current handshake
previousConnection, found := k.GetConnection(ctx, connectionID)
if found && !(previousConnection.State == types.INIT &&
previousConnection.Counterparty.ConnectionID == counterparty.ConnectionID &&
bytes.Equal(previousConnection.Counterparty.Prefix.Bytes(), counterparty.Prefix.Bytes()) &&
previousConnection.ClientID == clientID &&
previousConnection.Counterparty.ClientID == counterparty.ClientID &&
previousConnection.Versions[0] == version) {
return sdkerrors.Wrap(types.ErrInvalidConnection, "cannot relay connection attempt")
}
// Set connection state to TRYOPEN and store in chainB state
connection.State = types.TRYOPEN
if err := k.addConnectionToClient(ctx, clientID, connectionID); err != nil {
return sdkerrors.Wrap(err, "cannot relay connection attempt")
}
k.SetConnection(ctx, connectionID, connection)
k.Logger(ctx).Info(fmt.Sprintf("connection %s state updated: NONE -> TRYOPEN ", connectionID))
return nil
}
// ConnOpenAck relays acceptance of a connection open attempt from chain B back
// to chain A (this code is executed on chain A).
//
// NOTE: Identifiers are checked on msg validation.
func (k Keeper) ConnOpenAck(
ctx sdk.Context,
connectionID string,
version string, // version that ChainB chose in ConnOpenTry
proofTry []byte, // proof that connectionEnd was added to ChainB state in ConnOpenTry
proofConsensus []byte, // proof that chainB has stored ConsensusState of chainA on its client
proofHeight uint64, // height that relayer constructed proofTry
consensusHeight uint64, // latest height of chainA that chainB has stored on its chainA client
) error {
// Check that chainB client hasn't stored invalid height
if consensusHeight > uint64(ctx.BlockHeight()) {
return sdkerrors.Wrap(sdkerrors.ErrInvalidHeight, "invalid consensus height")
}
// Retrieve connection
connection, found := k.GetConnection(ctx, connectionID)
if !found {
return sdkerrors.Wrap(types.ErrConnectionNotFound, "cannot relay ACK of open attempt")
}
// Check connection on ChainA is on correct state: INIT or TRYOPEN
if connection.State != types.INIT && connection.State != types.TRYOPEN {
return sdkerrors.Wrapf(
types.ErrInvalidConnectionState,
"connection state is not INIT (got %s)", connection.State.String(),
)
}
// Check that ChainB's proposed version number is supported by chainA
supportedVersion, found := types.FindSupportedVersion(version, types.GetCompatibleVersions())
if !found {
return sdkerrors.Wrapf(
types.ErrVersionNegotiationFailed,
"connection version provided (%s) is not supported (%s)", version, types.GetCompatibleVersions(),
)
}
// Check that ChainB's proposed feature set is supported by chainA
if !types.VerifyProposedFeatureSet(version, supportedVersion) {
return sdkerrors.Wrapf(
types.ErrVersionNegotiationFailed,
"connection version feature set provided (%s) is not supported (%s)", version, types.GetCompatibleVersions(),
)
}
// Retrieve chainA's consensus state at consensusheight
expectedConsensusState, found := k.clientKeeper.GetSelfConsensusState(ctx, consensusHeight)
if !found {
return clienttypes.ErrSelfConsensusStateNotFound
}
prefix := k.GetCommitmentPrefix()
expectedCounterparty := types.NewCounterparty(connection.ClientID, connectionID, commitmenttypes.NewMerklePrefix(prefix.Bytes()))
expectedConnection := types.NewConnectionEnd(types.TRYOPEN, connection.Counterparty.ConnectionID, connection.Counterparty.ClientID, expectedCounterparty, []string{version})
// Ensure that ChainB stored expected connectionEnd in its state during ConnOpenTry
if err := k.VerifyConnectionState(
ctx, connection, proofHeight, proofTry, connection.Counterparty.ConnectionID,
expectedConnection,
); err != nil {
return err
}
// Ensure that ChainB has stored the correct ConsensusState for chainA at the consensusHeight
if err := k.VerifyClientConsensusState(
ctx, connection, proofHeight, consensusHeight, proofConsensus, expectedConsensusState,
); err != nil {
return err
}
// Update connection state to Open
connection.State = types.OPEN
connection.Versions = []string{version}
k.SetConnection(ctx, connectionID, connection)
k.Logger(ctx).Info(fmt.Sprintf("connection %s state updated: INIT -> OPEN ", connectionID))
return nil
}
// ConnOpenConfirm confirms opening of a connection on chain A to chain B, after
// which the connection is open on both chains (this code is executed on chain B).
//
// NOTE: Identifiers are checked on msg validation.
func (k Keeper) ConnOpenConfirm(
ctx sdk.Context,
connectionID string,
proofAck []byte, // proof that connection opened on ChainA during ConnOpenAck
proofHeight uint64, // height that relayer constructed proofAck
) error {
// Retrieve connection
connection, found := k.GetConnection(ctx, connectionID)
if !found {
return sdkerrors.Wrap(types.ErrConnectionNotFound, "cannot relay ACK of open attempt")
}
// Check that connection state on ChainB is on state: TRYOPEN
if connection.State != types.TRYOPEN {
return sdkerrors.Wrapf(
types.ErrInvalidConnectionState,
"connection state is not TRYOPEN (got %s)", connection.State.String(),
)
}
prefix := k.GetCommitmentPrefix()
expectedCounterparty := types.NewCounterparty(connection.ClientID, connectionID, commitmenttypes.NewMerklePrefix(prefix.Bytes()))
expectedConnection := types.NewConnectionEnd(types.OPEN, connection.Counterparty.ConnectionID, connection.Counterparty.ClientID, expectedCounterparty, connection.Versions)
// Check that connection on ChainA is open
if err := k.VerifyConnectionState(
ctx, connection, proofHeight, proofAck, connection.Counterparty.ConnectionID,
expectedConnection,
); err != nil {
return err
}
// Update ChainB's connection to Open
connection.State = types.OPEN
k.SetConnection(ctx, connectionID, connection)
k.Logger(ctx).Info(fmt.Sprintf("connection %s state updated: TRYOPEN -> OPEN ", connectionID))
return nil
}