Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client identifier reuse #358

Merged
merged 14 commits into from
Jan 20, 2021
23 changes: 21 additions & 2 deletions cmd/raw.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ package cmd
import (
"fmt"
"strings"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/07-tendermint/types"
"github.com/cosmos/relayer/relayer"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/light"
)

////////////////////////////////////////
Expand Down Expand Up @@ -107,8 +112,22 @@ $ %s tx raw clnt ibc-1 ibc-0 ibconeclient`, appName, appName)),
return err
}

return sendAndPrint([]sdk.Msg{chains[src].PathEnd.CreateClient(dstHeader,
chains[dst].GetTrustingPeriod(), ubdPeriod, chains[src].MustGetAddress())},
clientState := ibctmtypes.NewClientState(
dstHeader.GetHeader().GetChainID(),
ibctmtypes.NewFractionFromTm(light.DefaultTrustLevel),
chains[dst].GetTrustingPeriod(),
ubdPeriod,
time.Minute*10,
dstHeader.GetHeight().(clienttypes.Height),
commitmenttypes.GetSDKSpecs(),
relayer.DefaultUpgradePath,
false,
false,
)

return sendAndPrint([]sdk.Msg{chains[src].PathEnd.CreateClient(
clientState, dstHeader,
chains[src].MustGetAddress())},
chains[src], cmd)
},
}
Expand Down
217 changes: 178 additions & 39 deletions relayer/client-tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ package relayer

import (
"fmt"
"reflect"
"time"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
clientutils "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/client/utils"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/07-tendermint/types"
"github.com/tendermint/tendermint/light"
)

// CreateClients creates clients for src on dst and dst on src if the client ids are unspecified.
// TODO: de-duplicate code
func (c *Chain) CreateClients(dst *Chain) (modified bool, err error) {
// Handle off chain light clients
if err := c.ValidateLightInitialized(); err != nil {
Expand All @@ -32,28 +40,49 @@ func (c *Chain) CreateClients(dst *Chain) (modified bool, err error) {
if err != nil {
return modified, err
}
msgs := []sdk.Msg{
c.PathEnd.CreateClient(
dstH,
dst.GetTrustingPeriod(),
ubdPeriod,
c.MustGetAddress(),
),
}
res, success, err := c.SendMsgs(msgs)
if err != nil {
return modified, err
}
if !success {
return modified, fmt.Errorf("tx failed: %s", res.RawLog)
}

// update the client identifier
// use index 0, the transaction only has one message
clientID, err := ParseClientIDFromEvents(res.Logs[0].Events)
if err != nil {
return modified, err
// Create the ClientState we want on 'c' tracking 'dst'
clientState := ibctmtypes.NewClientState(
dstH.GetHeader().GetChainID(),
ibctmtypes.NewFractionFromTm(light.DefaultTrustLevel),
dst.GetTrustingPeriod(),
ubdPeriod,
time.Minute*10,
dstH.GetHeight().(clienttypes.Height),
commitmenttypes.GetSDKSpecs(),
DefaultUpgradePath,
false,
false,
)

// Check if an identical light client already exists
clientID, found := FindMatchingClient(c, dst, clientState)
if !found {
msgs := []sdk.Msg{
c.PathEnd.CreateClient(
clientState,
dstH,
c.MustGetAddress(),
),
}

// if a matching client does not exist, create one
res, success, err := c.SendMsgs(msgs)
if err != nil {
return modified, err
}
if !success {
return modified, fmt.Errorf("tx failed: %s", res.RawLog)
}

// update the client identifier
// use index 0, the transaction only has one message
clientID, err = ParseClientIDFromEvents(res.Logs[0].Events)
if err != nil {
return modified, err
}
}

c.PathEnd.ClientID = clientID
modified = true

Expand All @@ -74,26 +103,47 @@ func (c *Chain) CreateClients(dst *Chain) (modified bool, err error) {
if err != nil {
return modified, err
}
msgs := []sdk.Msg{
dst.PathEnd.CreateClient(
srcH,
c.GetTrustingPeriod(),
ubdPeriod,
dst.MustGetAddress(),
),
}
res, success, err := dst.SendMsgs(msgs)
if err != nil {
return modified, err
}
if !success {
return modified, fmt.Errorf("tx failed: %s", res.RawLog)
}
// Create the ClientState we want on 'dst' tracking 'c'
clientState := ibctmtypes.NewClientState(
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
srcH.GetHeader().GetChainID(),
ibctmtypes.NewFractionFromTm(light.DefaultTrustLevel),
c.GetTrustingPeriod(),
ubdPeriod,
time.Minute*10,
srcH.GetHeight().(clienttypes.Height),
commitmenttypes.GetSDKSpecs(),
DefaultUpgradePath,
false,
false,
)

// update client identifier
clientID, err := ParseClientIDFromEvents(res.Logs[0].Events)
if err != nil {
return modified, err
// Check if an identical light client already exists
// NOTE: we pass in 'dst' as the source and 'c' as the
// counterparty.
clientID, found := FindMatchingClient(dst, c, clientState)
if !found {
msgs := []sdk.Msg{
dst.PathEnd.CreateClient(
clientState,
srcH,
dst.MustGetAddress(),
),
}

// if a matching client does not exist, create one
res, success, err := dst.SendMsgs(msgs)
if err != nil {
return modified, err
}
if !success {
return modified, fmt.Errorf("tx failed: %s", res.RawLog)
}

// update client identifier
clientID, err = ParseClientIDFromEvents(res.Logs[0].Events)
if err != nil {
return modified, err
}
}
dst.PathEnd.ClientID = clientID
modified = true
Expand Down Expand Up @@ -194,3 +244,92 @@ func (c *Chain) UpgradeClients(dst *Chain, height int64) error {

return nil
}

// FindMatchingClient will determine if there exists a client with identical client and consensus states
// to the client which would have been created. Source is the chain that would be adding a client
// which would track the counterparty. Therefore we query source for the existing clients
// and check if any match the counterparty. The counterparty must have a matching consensus state
// to the latest consensus state of a potential match. The provided client state is the client
// state that will be created if there exist no matches.
func FindMatchingClient(source, counterparty *Chain, clientState *ibctmtypes.ClientState) (string, bool) {
// TODO: add appropriate offset and limits, along with retries
clientsResp, err := source.QueryClients(0, 1000)
if err != nil {
if source.debug {
source.Log(fmt.Sprintf("Error: querying clients on %s failed: %v", source.PathEnd.ChainID, err))
}
return "", false
}

for _, identifiedClientState := range clientsResp.ClientStates {
// unpack any into ibc tendermint client state
clientStateExported, err := clienttypes.UnpackClientState(identifiedClientState.ClientState)
if err != nil {
return "", false
}

// cast from interface to concrete type
existingClientState, ok := clientStateExported.(*ibctmtypes.ClientState)
if !ok {
return "", false
}

// check if the client states match
if IsMatchingClient(*clientState, *existingClientState) {

// query the latest consensus state of the potential matching client
consensusStateResp, err := clientutils.QueryConsensusStateABCI(source.CLIContext(0), identifiedClientState.ClientId, existingClientState.GetLatestHeight())
if err != nil {
if source.debug {
source.Log(fmt.Sprintf("Error: failed to query latest consensus state for existing client on chain %s: %v", source.PathEnd.ChainID, err))
}
continue
}

header, err := counterparty.QueryHeaderAtHeight(int64(existingClientState.GetLatestHeight().GetRevisionHeight()))
if err != nil {
if source.debug {
source.Log(fmt.Sprintf("Error: failed to query header for chain %s at height %d: %v", counterparty.PathEnd.ChainID, existingClientState.GetLatestHeight().GetRevisionHeight(), err))
}
continue
}

if IsMatchingConsensusState(consensusStateResp.ConsensusState, header.ConsensusState()) {
// matching client found
return identifiedClientState.ClientId, true
}
}
}

return "", false
}

// IsMatchingClient determines if the two provided clients match in all fields
// except latest height. They are assumed to be IBC tendermint light clients.
// NOTE: we don't pass in a pointer so upstream references don't have a modified
// latest height set to zero.
func IsMatchingClient(clientStateA, clientStateB ibctmtypes.ClientState) bool {
// zero out latest client height since this is determined and incremented
// by on-chain updates. Changing the latest height does not fundamentally
// change the client. The associated consensus state at the latest height
// determines this last check
clientStateA.LatestHeight = clienttypes.ZeroHeight()
clientStateB.LatestHeight = clienttypes.ZeroHeight()

return reflect.DeepEqual(clientStateA, clientStateB)
}

// IsMatchingConsensusState determines if the two provided consensus states are
// identical. They are assumed to be IBC tendermint light clients.
func IsMatchingConsensusState(anyConsState *codectypes.Any, consensusStateB *ibctmtypes.ConsensusState) bool {
exportedConsState, err := clienttypes.UnpackConsensusState(anyConsState)
if err != nil {
return false
}
consensusStateA, ok := exportedConsState.(*ibctmtypes.ConsensusState)
if !ok {
return false
}

return reflect.DeepEqual(*consensusStateA, *consensusStateB)
}
21 changes: 3 additions & 18 deletions relayer/pathEnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package relayer

import (
"strings"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
transfertypes "github.com/cosmos/cosmos-sdk/x/ibc/applications/transfer/types"
Expand All @@ -11,7 +10,6 @@ import (
chantypes "github.com/cosmos/cosmos-sdk/x/ibc/core/04-channel/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types"
tmclient "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/07-tendermint/types"
"github.com/tendermint/tendermint/light"
)

// TODO: migrate all message construction methods to msgs.go and use the chain
Expand All @@ -21,7 +19,7 @@ import (
var (
defaultChainPrefix = commitmenttypes.NewMerklePrefix([]byte("ibc"))
defaultDelayPeriod = uint64(0)
defaultUpgradePath = []string{"upgrade", "upgradedIBCState"}
DefaultUpgradePath = []string{"upgrade", "upgradedIBCState"}
)

// PathEnd represents the local connection identifers for a relay path
Expand Down Expand Up @@ -71,27 +69,14 @@ func UnmarshalChain(pe PathEnd) *Chain {

// CreateClient creates an sdk.Msg to update the client on src with consensus state from dst
func (pe *PathEnd) CreateClient(
clientState *tmclient.ClientState,
dstHeader *tmclient.Header,
trustingPeriod, unbondingPeriod time.Duration,
signer sdk.AccAddress) sdk.Msg {

if err := dstHeader.ValidateBasic(); err != nil {
panic(err)
}

// Blank Client State
clientState := tmclient.NewClientState(
dstHeader.GetHeader().GetChainID(),
tmclient.NewFractionFromTm(light.DefaultTrustLevel),
trustingPeriod,
unbondingPeriod,
time.Minute*10,
dstHeader.GetHeight().(clienttypes.Height),
commitmenttypes.GetSDKSpecs(),
defaultUpgradePath,
false,
false,
)

msg, err := clienttypes.NewMsgCreateClient(
clientState,
dstHeader.ConsensusState(),
Expand Down