Skip to content

Commit

Permalink
feat: add helper function for parsing clientID from client state path (
Browse files Browse the repository at this point in the history
…#2820)

feat: add `MustParseClientStatePath` which parses the clientID from a client state key path
  • Loading branch information
colin-axner authored Nov 23, 2022
1 parent 86332d2 commit 2180fa4
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

* (core/24-host) [\#2820](https://github.com/cosmos/ibc-go/pull/2820) Add `MustParseClientStatePath` which parses the clientID from a client state key path.
* (apps/27-interchain-accounts) [\#2147](https://github.com/cosmos/ibc-go/pull/2147) Adding a `SubmitTx` gRPC endpoint for the ICS27 Controller module which allows owners of interchain accounts to submit transactions. This replaces the previously existing need for authentication modules to implement this standard functionality.
* (testing/simapp) [\#2190](https://github.com/cosmos/ibc-go/pull/2190) Adding the new `x/group` cosmos-sdk module to simapp.

Expand Down
11 changes: 6 additions & 5 deletions modules/core/02-client/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,15 +359,16 @@ func (k Keeper) IterateClients(ctx sdk.Context, cb func(clientID string, cs expo

defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
keySplit := strings.Split(string(iterator.Key()), "/")
if keySplit[len(keySplit)-1] != host.KeyClientState {
path := string(iterator.Key())
if !strings.Contains(path, host.KeyClientState) {
// skip non client state keys
continue
}

clientID := host.MustParseClientStatePath(path)
clientState := k.MustUnmarshalClientState(iterator.Value())

// key is ibc/{clientid}/clientState
// Thus, keySplit[1] is clientID
if cb(keySplit[1], clientState) {
if cb(clientID, clientState) {
break
}
}
Expand Down
34 changes: 34 additions & 0 deletions modules/core/24-host/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,40 @@ func ParseIdentifier(identifier, prefix string) (uint64, error) {
return sequence, nil
}

// MustParseClientStatePath returns the client ID from a client state path. It panics
// if the provided path is invalid or if the clientID is empty.
func MustParseClientStatePath(path string) string {
clientID, err := parseClientStatePath(path)
if err != nil {
panic(err.Error())
}

return clientID
}

// parseClientStatePath returns the client ID from a client state path. It returns
// an error if the provided path is invalid.
func parseClientStatePath(path string) (string, error) {
split := strings.Split(path, "/")
if len(split) != 3 {
return "", sdkerrors.Wrapf(ErrInvalidPath, "cannot parse client state path %s", path)
}

if split[0] != string(KeyClientStorePrefix) {
return "", sdkerrors.Wrapf(ErrInvalidPath, "path does not begin with client store prefix: expected %s, got %s", KeyClientStorePrefix, split[0])
}

if split[2] != KeyClientState {
return "", sdkerrors.Wrapf(ErrInvalidPath, "path does not end with client state key: expected %s, got %s", KeyClientState, split[2])
}

if strings.TrimSpace(split[1]) == "" {
return "", sdkerrors.Wrap(ErrInvalidPath, "clientID is empty")
}

return split[1], nil
}

// ParseConnectionPath returns the connection ID from a full path. It returns
// an error if the provided path is invalid.
func ParseConnectionPath(path string) (string, error) {
Expand Down
31 changes: 31 additions & 0 deletions modules/core/24-host/parse_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package host_test

import (
"fmt"
"math"
"testing"

"github.com/stretchr/testify/require"

connectiontypes "github.com/cosmos/ibc-go/v6/modules/core/03-connection/types"
host "github.com/cosmos/ibc-go/v6/modules/core/24-host"
ibctesting "github.com/cosmos/ibc-go/v6/testing"
)

func TestParseIdentifier(t *testing.T) {
Expand Down Expand Up @@ -46,3 +48,32 @@ func TestParseIdentifier(t *testing.T) {
}
}
}

func TestMustParseClientStatePath(t *testing.T) {
testCases := []struct {
name string
path string
expPass bool
}{
{"valid", host.FullClientStatePath(ibctesting.FirstClientID), true},
{"path too large", fmt.Sprintf("clients/clients/%s/clientState", ibctesting.FirstClientID), false},
{"path too small", fmt.Sprintf("clients/%s", ibctesting.FirstClientID), false},
{"path does not begin with client store", fmt.Sprintf("cli/%s/%s", ibctesting.FirstClientID, host.KeyClientState), false},
{"path does not end with client state key", fmt.Sprintf("%s/%s/consensus", string(host.KeyClientStorePrefix), ibctesting.FirstClientID), false},
{"client ID is empty", host.FullClientStatePath(""), false},
{"client ID is only spaces", host.FullClientStatePath(" "), false},
}

for _, tc := range testCases {
if tc.expPass {
require.NotPanics(t, func() {
clientID := host.MustParseClientStatePath(tc.path)
require.Equal(t, ibctesting.FirstClientID, clientID)
})
} else {
require.Panics(t, func() {
host.MustParseClientStatePath(tc.path)
})
}
}
}

0 comments on commit 2180fa4

Please sign in to comment.