Repository: cosmos/relayer
Pull request #946 (2022-08-24 16:57:24)

create client for substrate chains 
This PR implements the required provider and query methods to create light clients across substrate and cosmos chains. 


Patch:
diff --git a/Makefile b/Makefile
index 3ef81f8..3dc2344 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//')
 COMMIT  := $(shell git log -1 --format='%H')
-GAIA_VERSION := v7.0.1
+GAIA_VERSION := beefy_client
 AKASH_VERSION := v0.16.3
 OSMOSIS_VERSION := v8.0.0
 WASMD_VERSION := v0.25.0
@@ -93,7 +93,8 @@ lint:
 
 get-gaia:
 	@mkdir -p ./chain-code/
-	@git clone --branch $(GAIA_VERSION) --depth=1 https://github.com/cosmos/gaia.git ./chain-code/gaia
+	# @git clone --depth=1 https://github.com/cosmos/gaia.git ./chain-code/gaia
+	@git clone --depth=1 --branch=$(GAIA_VERSION) https://github.com/oshorefueled/gaia.git ./chain-code/gaia
 
 build-gaia:
 	@./examples/demo/scripts/build-gaia
diff --git a/cmd/config.go b/cmd/config.go
index bbd2e74..51caaa8 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -31,6 +31,7 @@ import (
 
 	"github.com/cosmos/relayer/v2/relayer"
 	"github.com/cosmos/relayer/v2/relayer/chains/cosmos"
+	"github.com/cosmos/relayer/v2/relayer/chains/substrate"
 	"github.com/cosmos/relayer/v2/relayer/provider"
 	"github.com/spf13/cobra"
 	"go.uber.org/zap"
@@ -340,7 +341,8 @@ type ProviderConfigYAMLWrapper struct {
 // NOTE: Add new ProviderConfig types in the map here with the key set equal to the type of ChainProvider (e.g. cosmos, substrate, etc.)
 func (pcw *ProviderConfigWrapper) UnmarshalJSON(data []byte) error {
 	customTypes := map[string]reflect.Type{
-		"cosmos": reflect.TypeOf(cosmos.CosmosProviderConfig{}),
+		"cosmos":    reflect.TypeOf(cosmos.CosmosProviderConfig{}),
+		"substrate": reflect.TypeOf(substrate.SubstrateProviderConfig{}),
 	}
 	val, err := UnmarshalJSONProviderConfig(data, customTypes)
 	if err != nil {
@@ -393,6 +395,8 @@ func (iw *ProviderConfigYAMLWrapper) UnmarshalYAML(n *yaml.Node) error {
 	switch iw.Type {
 	case "cosmos":
 		iw.Value = new(cosmos.CosmosProviderConfig)
+	case "substrate":
+		iw.Value = new(substrate.SubstrateProviderConfig)
 	default:
 		return fmt.Errorf("%s is an invalid chain type, check your config file", iw.Type)
 	}
diff --git a/configs/x-demo/chains/ibc-0.json b/configs/x-demo/chains/ibc-0.json
new file mode 100644
index 0000000..1cdb73a
--- /dev/null
+++ b/configs/x-demo/chains/ibc-0.json
@@ -0,0 +1,19 @@
+{
+  "type": "cosmos",
+  "value": {
+    "key": "testkey",
+    "chain-id": "ibc-0",
+    "rpc-addr": "http://localhost:26657",
+    "grpc-addr": "",
+    "account-prefix": "cosmos",
+    "keyring-backend": "test",
+    "gas-adjustment": 1.5,
+    "gas-prices": "0.025stake",
+    "min-gas-amount": 1,
+    "debug": false,
+    "timeout": "10s",
+    "output-format": "json",
+    "sign-mode": "direct"
+  }
+}
+
diff --git a/configs/x-demo/chains/ibc-1.json b/configs/x-demo/chains/ibc-1.json
new file mode 100644
index 0000000..9b589e2
--- /dev/null
+++ b/configs/x-demo/chains/ibc-1.json
@@ -0,0 +1,18 @@
+{
+  "type": "substrate",
+  "value": {
+    "key": "testkey",
+    "chain-name": "ibc-1",
+    "chain-id": "ibc-1",
+    "rpc-addr": "ws://127.0.0.1:9988",
+    "relay-rpc-addr": "ws://127.0.0.1:9944",
+    "account-prefix": "",
+    "keyring-backend": "test",
+    "key-directory": "/tmp",
+    "debug": false,
+    "timeout": "10s",
+    "network": 49,
+    "para-id": 2000,
+    "beefy-activation-block": 0
+  }
+}
diff --git a/configs/x-demo/paths/demo.json b/configs/x-demo/paths/demo.json
new file mode 100644
index 0000000..14175ce
--- /dev/null
+++ b/configs/x-demo/paths/demo.json
@@ -0,0 +1,12 @@
+{
+  "src": {
+    "chain-id": "ibc-0"
+  },
+  "dst": {
+    "chain-id": "ibc-1"
+  },
+  "src-channel-filter": {
+    "rule": null,
+    "channel-list": []
+  }
+}
diff --git a/examples/demo/scripts/dev-env-sub b/examples/demo/scripts/dev-env-sub
new file mode 100755
index 0000000..ca4f29a
--- /dev/null
+++ b/examples/demo/scripts/dev-env-sub
@@ -0,0 +1,62 @@
+#!/bin/bash -e
+RELAYER_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+RELAYER_CONF="$HOME/.relayer"
+GAIA_CONF="$(pwd)/data"
+GAIA_DATA="$(pwd)/data"
+
+
+
+# Ensure user understands what will be deleted
+if ([[ -d $RELAYER_CONF ]] || [[ -d $GAIA_CONF ]]) && [[ ! "$1" == "skip" ]]; then
+  read -p "$0 will delete \$HOME/.relayer and \$(pwd)/data folder. Do you wish to continue? (y/n): " -n 1 -r
+  echo
+  if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+      exit 1
+  fi
+fi
+set -exu
+
+cd $RELAYER_DIR
+rm -rf $RELAYER_CONF &> /dev/null
+pwd
+bash scripts/one-chain gaiad ibc-0 ./data 26657 26656 6060 9090
+
+[ -f $GAIA_DATA/ibc-0.log ] && echo "ibc-0 initialized. Watch file $GAIA_DATA/ibc-0.log to see its execution."
+
+cd $RELAYER_DIR
+
+echo "Building Relayer..."
+make install
+
+echo "Generating rly configurations..."
+rly config init
+rly chains add-dir configs/x-demo/chains
+
+SEED0=$(jq -r '.mnemonic' $GAIA_DATA/ibc-0/key_seed.json)
+echo "Key $(rly keys restore ibc-0 testkey "$SEED0") imported from ibc-0 to relayer..."
+echo "Key $(rly keys restore ibc-1 testkey "//Alice") imported from ibc-1 to relayer..."
+
+rly paths add-dir configs/x-demo/paths
+
+echo "waiting for blocks..."
+sleep 3
+
+rly tx link demo -d -t 3s
+sleep 2
+echo "Initial balances"
+echo "balance 0 $(rly q bal ibc-0)"
+echo "balance 1 $(rly q bal ibc-1)"
+rly tx transfer ibc-0 ibc-1 100000samoleans "$(rly keys show ibc-1)" channel-0 -d
+rly tx transfer ibc-1 ibc-0 100000samoleans "$(rly keys show ibc-0)" channel-0 -d
+sleep 5
+rly tx relay-packets demo channel-0 -d
+sleep 5
+rly tx relay-acknowledgements demo channel-0 -d
+sleep 5
+echo "Balances after packets are sent"
+echo "balance 0 $(rly q bal ibc-0)"
+echo "balance 1 $(rly q bal ibc-1)"
+
+
+
+#curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "state_getMetadata", "params":[]}' wss://0.0.0.0:9989\n
diff --git a/examples/demo/scripts/one-chain b/examples/demo/scripts/one-chain
index ebb2913..3b39724 100755
--- a/examples/demo/scripts/one-chain
+++ b/examples/demo/scripts/one-chain
@@ -68,19 +68,19 @@ fi
 coins="100000000000$DENOM,100000000000$BASEDENOM"
 delegate="100000000000$DENOM"
 
-redirect $BINARY --home $CHAINDIR/$CHAINID --chain-id $CHAINID init $CHAINID 
+redirect $BINARY --home $CHAINDIR/$CHAINID --chain-id $CHAINID init $CHAINID
 sleep 1
 $BINARY --home $CHAINDIR/$CHAINID keys add validator $KEYRING --output json > $CHAINDIR/$CHAINID/validator_seed.json 2>&1
 sleep 1
 $BINARY --home $CHAINDIR/$CHAINID keys add user $KEYRING --output json > $CHAINDIR/$CHAINID/key_seed.json 2>&1
 sleep 1
-redirect $BINARY --home $CHAINDIR/$CHAINID add-genesis-account $($BINARY --home $CHAINDIR/$CHAINID keys $KEYRING show user -a) $coins 
+redirect $BINARY --home $CHAINDIR/$CHAINID add-genesis-account $($BINARY --home $CHAINDIR/$CHAINID keys $KEYRING show user -a) $coins
 sleep 1
-redirect $BINARY --home $CHAINDIR/$CHAINID add-genesis-account $($BINARY --home $CHAINDIR/$CHAINID keys $KEYRING show validator -a) $coins 
+redirect $BINARY --home $CHAINDIR/$CHAINID add-genesis-account $($BINARY --home $CHAINDIR/$CHAINID keys $KEYRING show validator -a) $coins
 sleep 1
 redirect $BINARY --home $CHAINDIR/$CHAINID gentx validator $delegate $KEYRING --chain-id $CHAINID
 sleep 1
-redirect $BINARY --home $CHAINDIR/$CHAINID collect-gentxs 
+redirect $BINARY --home $CHAINDIR/$CHAINID collect-gentxs
 sleep 1
 
 # Check platform
@@ -113,4 +113,4 @@ else
 fi
 
 # Start the gaia
-$BINARY --home $CHAINDIR/$CHAINID start --pruning=nothing --grpc-web.enable=false --grpc.address="0.0.0.0:$GRPCPORT" > $CHAINDIR/$CHAINID.log 2>&1 &
+$BINARY --home $CHAINDIR/$CHAINID start --minimum-gas-prices="0.025stake" --pruning=nothing --grpc-web.enable=false --grpc.address="0.0.0.0:$GRPCPORT" > $CHAINDIR/$CHAINID.log 2>&1 &
diff --git a/relayer/chains/substrate/construct.go b/relayer/chains/substrate/construct.go
index c3f8c94..7ff2fff 100644
--- a/relayer/chains/substrate/construct.go
+++ b/relayer/chains/substrate/construct.go
@@ -128,7 +128,7 @@ func (sp *SubstrateProvider) fetchParaIds(blockHash rpcclienttypes.Hash) ([]uint
 
 func (sp *SubstrateProvider) parachainHeaderKey() ([]byte, error) {
 	keyPrefix := rpcclienttypes.CreateStorageKeyPrefix(prefixParas, methodHeads)
-	encodedParaId, err := Encode(sp.Config.ParaID)
+	encodedParaId, err := rpcclienttypes.Encode(sp.Config.ParaID)
 	if err != nil {
 		return nil, err
 	}
@@ -308,7 +308,7 @@ func (sp *SubstrateProvider) parachainHeaderKeys(
 	keyPrefix := rpcclienttypes.CreateStorageKeyPrefix(prefixParas, methodHeads)
 	// so we can query all blocks from lastfinalized to latestBeefyHeight
 	for _, paraId := range paraIds {
-		encodedParaId, err := Encode(paraId)
+		encodedParaId, err := rpcclienttypes.Encode(paraId)
 		if err != nil {
 			return nil, err
 		}
@@ -439,20 +439,20 @@ func (sp *SubstrateProvider) constructExtrinsics(
 		return nil, nil, nil
 	}
 
-	timestampExtrinsic, err = Encode(exts[0])
+	timestampExtrinsic, err = rpcclienttypes.Encode(exts[0])
 	if err != nil {
 		return nil, nil, err
 	}
 
 	t := trie.NewEmptyTrie()
 	for i := 0; i < len(exts); i++ {
-		ext, err := Encode(exts[i])
+		ext, err := rpcclienttypes.Encode(exts[i])
 		if err != nil {
 			return nil, nil, err
 		}
 
 		key := rpcclienttypes.NewUCompactFromUInt(uint64(i))
-		encodedKey, err := Encode(key)
+		encodedKey, err := rpcclienttypes.Encode(key)
 		if err != nil {
 			return nil, nil, err
 		}
@@ -471,7 +471,7 @@ func (sp *SubstrateProvider) constructExtrinsics(
 	}
 
 	timestampKey := rpcclienttypes.NewUCompactFromUInt(uint64(0))
-	encodedTPKey, err := Encode(timestampKey)
+	encodedTPKey, err := rpcclienttypes.Encode(timestampKey)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/relayer/chains/substrate/provider.go b/relayer/chains/substrate/provider.go
index 7f2e8e4..1406182 100644
--- a/relayer/chains/substrate/provider.go
+++ b/relayer/chains/substrate/provider.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"math"
 	"path"
 	"time"
 
@@ -92,7 +93,19 @@ func (sp *SubstrateProvider) Init() error {
 		return err
 	}
 
+	client, err := rpcclient.NewSubstrateAPI(sp.Config.RPCAddr)
+	if err != nil {
+		return err
+	}
+
+	relaychainClient, err := rpcclient.NewSubstrateAPI(sp.Config.RelayRPCAddr)
+	if err != nil {
+		return err
+	}
+
 	sp.Keybase = keybase
+	sp.RPCClient = client
+	sp.RelayChainRPCClient = relaychainClient
 	return nil
 }
 
@@ -103,8 +116,9 @@ type SubstrateProvider struct {
 	Memdb   *chaindb.BadgerDB
 	Input   io.Reader
 
-	RPCClient           *rpcclient.SubstrateAPI
-	RelayChainRPCClient *rpcclient.SubstrateAPI
+	RPCClient                     *rpcclient.SubstrateAPI
+	RelayChainRPCClient           *rpcclient.SubstrateAPI
+	LatestQueriedRelayChainHeight int64
 }
 
 type SubstrateIBCHeader struct {
@@ -112,12 +126,6 @@ type SubstrateIBCHeader struct {
 	SignedHeader *beefyclienttypes.Header
 }
 
-// noop to implement processor.IBCHeader
-func (h SubstrateIBCHeader) IBCHeaderIndicator() {
-	//TODO implement me
-	panic("implement me")
-}
-
 func (h SubstrateIBCHeader) Height() uint64 {
 	return h.height
 }
@@ -132,43 +140,41 @@ func (sp *SubstrateProvider) BlockTime(ctx context.Context, height int64) (time.
 }
 
 func (sp *SubstrateProvider) ChainName() string {
-	//TODO implement me
-	panic("implement me")
+	return sp.Config.ChainName
 }
 
 func (sp *SubstrateProvider) ChainId() string {
-	//TODO implement me
-	panic("implement me")
+	return sp.Config.ChainID
 }
 
 func (sp *SubstrateProvider) Type() string {
-	//TODO implement me
-	panic("implement me")
+	return "substrate"
 }
 
 func (sp *SubstrateProvider) ProviderConfig() provider.ProviderConfig {
-	//TODO implement me
-	panic("implement me")
+	return sp.Config
 }
 
 func (sp *SubstrateProvider) Key() string {
-	//TODO implement me
-	panic("implement me")
+	return sp.Config.Key
 }
 
 func (sp *SubstrateProvider) Address() (string, error) {
-	//TODO implement me
-	panic("implement me")
+	info, err := sp.Keybase.Key(sp.Key())
+	if err != nil {
+		return "", nil
+	}
+
+	return info.GetAddress(), nil
 }
 
 func (sp *SubstrateProvider) Timeout() string {
-	//TODO implement me
-	panic("implement me")
+	return sp.Config.Timeout
 }
 
 func (sp *SubstrateProvider) TrustingPeriod(ctx context.Context) (time.Duration, error) {
-	//TODO implement me
-	panic("implement me")
+	// TODO: implement a proper trusting period
+	return time.Duration(math.MaxInt), nil
 }
 
 func (sp *SubstrateProvider) WaitForNBlocks(ctx context.Context, n int64) error {
diff --git a/relayer/chains/substrate/query.go b/relayer/chains/substrate/query.go
index 154ecf5..c5a37a1 100644
--- a/relayer/chains/substrate/query.go
+++ b/relayer/chains/substrate/query.go
@@ -7,6 +7,8 @@ import (
 	"strings"
 	"time"
 
+	beefyclienttypes "github.com/ComposableFi/ics11-beefy/types"
+
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	transfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
 	clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types"
@@ -441,18 +443,28 @@ func (sp *SubstrateProvider) QueryPacketReceipt(ctx context.Context, height int6
 }
 
 func (sp *SubstrateProvider) QueryLatestHeight(ctx context.Context) (int64, error) {
-	// TODO: should the latest height be the latest relayer height or the latest parachain height?
 	signedHash, err := sp.RelayChainRPCClient.RPC.Beefy.GetFinalizedHead()
 	if err != nil {
 		return 0, err
 	}
 
-	signedBlock, err := sp.RelayChainRPCClient.RPC.Chain.GetBlock(signedHash)
+	block, err := sp.RelayChainRPCClient.RPC.Chain.GetBlock(signedHash)
+	if err != nil {
+		return 0, err
+	}
+
+	header, err := sp.constructBeefyHeader(signedHash, nil)
+	if err != nil {
+		return 0, err
+	}
+
+	decodedHeader, err := beefyclienttypes.DecodeParachainHeader(header.HeadersWithProof.Headers[0].ParachainHeader)
 	if err != nil {
 		return 0, err
 	}
 
-	return int64(signedBlock.Block.Header.Number), nil
+	sp.LatestQueriedRelayChainHeight = int64(block.Block.Header.Number)
+	return int64(decodedHeader.Number), nil
 }
 
 // QueryHeaderAtHeight returns the header at a given height
@@ -479,6 +491,56 @@ func (sp *SubstrateProvider) QueryHeaderAtHeight(ctx context.Context, height int
 	return sp.constructBeefyHeader(blockHash, nil)
 }
 
+// QueryIBCHeader returns the result of QueryLatestIBCHeader. It is only applicable when creating clients.
+// QueryIBCHeaderOverBlocks should be used in the chain processor when querying headers to update the client.
+func (sp *SubstrateProvider) QueryIBCHeader(ctx context.Context, h int64) (provider.IBCHeader, error) {
+	if h <= 0 {
+		return nil, fmt.Errorf("must pass in valid height, %d not valid", h)
+	}
+
+	latestFinalizedHeight, err := sp.QueryLatestHeight(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	if h > latestFinalizedHeight {
+		return nil, fmt.Errorf("queried height is not finalized")
+	}
+
+	return sp.QueryLatestIBCHeader()
+}
+
+// QueryLatestIBCHeader returns the IBCHeader at the latest finalized relay chain height. It uses the relay chain height
+// which is set in the SubstrateProvider when the QueryLatestHeight method is called.
+func (sp *SubstrateProvider) QueryLatestIBCHeader() (provider.IBCHeader, error) {
+	if sp.LatestQueriedRelayChainHeight == 0 {
+		return nil, fmt.Errorf("latest finalized parachain height needs to be queried first")
+	}
+
+	relayChainHeight := uint64(sp.LatestQueriedRelayChainHeight)
+	blockHash, err := sp.RelayChainRPCClient.RPC.Chain.GetBlockHash(relayChainHeight)
+	if err != nil {
+		return nil, err
+	}
+
+	header, err := sp.constructBeefyHeader(blockHash, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// reset latest queried relay height after IBC Header is queried
+	sp.LatestQueriedRelayChainHeight = 0
+	return SubstrateIBCHeader{
+		height:       uint64(header.MMRUpdateProof.SignedCommitment.Commitment.BlockNumber),
+		SignedHeader: header,
+	}, nil
+}
+
+func (sp *SubstrateProvider) QueryIBCHeaderOverBlocks(finalizedHeight, previouslyFinalized int64) (provider.IBCHeader, error) {
+	// TODO
+	return nil, nil
+}
+
 // QueryDenomTrace takes a denom from IBC and queries the information about it
 func (sp *SubstrateProvider) QueryDenomTrace(ctx context.Context, denom string) (*transfertypes.DenomTrace, error) {
 	res, err := sp.RPCClient.RPC.IBC.QueryDenomTrace(ctx, denom)
diff --git a/relayer/chains/substrate/tx.go b/relayer/chains/substrate/tx.go
index cb81dc9..c533078 100644
--- a/relayer/chains/substrate/tx.go
+++ b/relayer/chains/substrate/tx.go
@@ -167,7 +167,6 @@ func (sp *SubstrateProvider) SendMessages(ctx context.Context, msgs []provider.R
 	rlyRes := &provider.RelayerTxResponse{
 		// TODO: pass in a proper block height
 		TxHash: fmt.Sprintf("0x%x", extHash[:]),
-		Code:   0, // if the process is reached this line, so there is no error
 		Events: events,
 	}
 
@@ -834,42 +833,6 @@ func (sp *SubstrateProvider) AcknowledgementFromSequence(ctx context.Context, ds
 	return msg, nil
 }
 
-// QueryIBCHeader returns the IBC compatible block header (SubstrateIBCHeader) at a specific height.
-func (sp *SubstrateProvider) QueryIBCHeader(ctx context.Context, h int64) (provider.IBCHeader, error) {
-	if h <= 0 {
-		return nil, fmt.Errorf("must pass in valid height, %d not valid", h)
-	}
-
-	latestBeefyBlockHash, err := sp.RPCClient.RPC.Beefy.GetFinalizedHead()
-	if err != nil {
-		return nil, err
-	}
-
-	latestBeefyHeight, err := sp.RPCClient.RPC.Chain.GetBlock(latestBeefyBlockHash)
-	if err != nil {
-		return nil, err
-	}
-
-	if h > int64(latestBeefyHeight.Block.Header.Number) {
-		return nil, fmt.Errorf("queried block is not finalized")
-	}
-
-	blockHash, err := sp.RPCClient.RPC.Chain.GetBlockHash(uint64(h))
-	if err != nil {
-		return nil, err
-	}
-
-	header, err := sp.constructBeefyHeader(latestBeefyBlockHash, &blockHash)
-	if err != nil {
-		return nil, err
-	}
-
-	return SubstrateIBCHeader{
-		height:       uint64(h),
-		SignedHeader: header,
-	}, nil
-}
-
 // InjectTrustedFields injects the necessary trusted fields for a header to update a light
 // client stored on the destination chain, using the information provided by the source
 // chain.
diff --git a/relayer/chains/substrate/utils.go b/relayer/chains/substrate/utils.go
index d4d925e..cbe4ff1 100644
--- a/relayer/chains/substrate/utils.go
+++ b/relayer/chains/substrate/utils.go
@@ -1,37 +1,11 @@
 package substrate
 
 import (
-	"bytes"
-
-	"github.com/ComposableFi/go-substrate-rpc-client/v4/scale"
 	clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types"
 	conntypes "github.com/cosmos/ibc-go/v5/modules/core/03-connection/types"
 	chantypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
 )
 
-// Encode scale encodes a data type and returns the scale encoded data as a byte type.
-func Encode(data any) ([]byte, error) {
-	var buf bytes.Buffer
-	enc := scale.NewEncoder(&buf)
-	err := enc.Encode(data)
-	if err != nil {
-		return nil, err
-	}
-	return buf.Bytes(), nil
-}
-
-// Decode decodes an encoded type to a target type. It takes encoded bytes and target interface as arguments and
-// returns decoded data as the target type.
-func Decode(source []byte, target any) error {
-	dec := scale.NewDecoder(bytes.NewReader(source))
-	err := dec.Decode(target)
-	if err != nil {
-		return err
-	}
-	return nil
-
-}
-
 func intoIBCEventType(substrateType SubstrateEventType) string {
 	switch substrateType {
 	case CreateClient:
diff --git a/relayer/provider/matcher.go b/relayer/provider/matcher.go
index 7c2527e..5993771 100644
--- a/relayer/provider/matcher.go
+++ b/relayer/provider/matcher.go
@@ -6,6 +6,7 @@ import (
 	"reflect"
 	"time"
 
+	beefyclienttypes "github.com/ComposableFi/ics11-beefy/types"
 	clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types"
 	ibcexported "github.com/cosmos/ibc-go/v5/modules/core/exported"
 	tmclient "github.com/cosmos/ibc-go/v5/modules/light-clients/07-tendermint/types"
@@ -28,6 +29,9 @@ func ClientsMatch(ctx context.Context, src, dst ChainProvider, existingClient cl
 	case *tmclient.ClientState:
 		nc := newClient.(*tmclient.ClientState)
 		return tendermintMatcher(ctx, src, dst, existingClient.ClientId, ec, nc)
+	case *beefyclienttypes.ClientState:
+		nc := newClient.(*beefyclienttypes.ClientState)
+		return beefyMatcher(ctx, src, dst, existingClient.ClientId, ec, nc)
 	}
 
 	return "", nil
@@ -118,3 +122,69 @@ func isMatchingTendermintClient(a, b tmclient.ClientState) bool {
 func isMatchingTendermintConsensusState(a, b *tmclient.ConsensusState) bool {
 	return reflect.DeepEqual(*a, *b)
 }
+
+// beefyMatcher determines if there is an existing light client on the src chain, tracking the dst chain,
+// with a state which matches a proposed new client state constructed from the dst chain.
+func beefyMatcher(ctx context.Context, src, dst ChainProvider, existingClientID string, existingClient, newClient ibcexported.ClientState) (string, error) {
+	newClientState, ok := newClient.(*beefyclienttypes.ClientState)
+	if !ok {
+		return "", fmt.Errorf("got type(%T) expected type(*beefyclienttypes.ClientState)", newClient)
+	}
+
+	existingClientState, ok := existingClient.(*beefyclienttypes.ClientState)
+	if !ok {
+		return "", fmt.Errorf("got type(%T) expected type(*beefyclienttypes.ClientState)", existingClient)
+	}
+
+	// Check if the client states match.
+	// NOTE: FrozenHeight.IsZero() is a sanity check, the client to be created should always
+	// have a zero frozen height and therefore should never match with a frozen client.
+	if reflect.DeepEqual(*newClientState, *existingClientState) && existingClientState.FrozenHeight == 0 {
+		srch, err := src.QueryLatestHeight(ctx)
+		if err != nil {
+			return "", err
+		}
+
+		// Query the src chain for the latest consensus state of the potential matching client.
+		consensusStateResp, err := src.QueryClientConsensusState(ctx, srch, existingClientID, existingClientState.GetLatestHeight())
+		if err != nil {
+			return "", err
+		}
+
+		exportedConsState, err := clienttypes.UnpackConsensusState(consensusStateResp.ConsensusState)
+		if err != nil {
+			return "", err
+		}
+
+		existingConsensusState, ok := exportedConsState.(*beefyclienttypes.ConsensusState)
+		if !ok {
+			return "", fmt.Errorf("got type(%T) expected type(*beefyclient.ConsensusState)", exportedConsState)
+		}
+
+		// If the existing client state has not been updated within the trusting period,
+		// we do not want to use the existing client since it's in an expired state.
+		// TODO: beefy client spec doesn't have trusting period as part of its field yet
+		//if existingClientState.IsExpired(existingConsensusState.Timestamp, time.Now()) {
+		//	return "", tmclient.ErrTrustingPeriodExpired
+		//}
+
+		// Construct a header for the consensus state of the counterparty chain.
+		ibcHeader, err := dst.QueryIBCHeader(ctx, int64(existingClientState.GetLatestHeight().GetRevisionHeight()))
+		if err != nil {
+			return "", err
+		}
+
+		consensusState, ok := ibcHeader.ConsensusState().(*beefyclienttypes.ConsensusState)
+		if !ok {
+			return "", fmt.Errorf("got type(%T) expected type(*beefyclient.ConsensusState)", consensusState)
+		}
+
+		// Determine if the existing consensus state on src for the potential matching client is identical
+		// to the consensus state of the counterparty chain.
+		if reflect.DeepEqual(existingConsensusState, consensusState) {
+			return existingClientID, nil // found matching client
+		}
+	}
+
+	return "", nil
+}


Test patch:
diff --git a/ibctest/go.mod b/ibctest/go.mod
index 1a6c320..cc7ee35 100644
--- a/ibctest/go.mod
+++ b/ibctest/go.mod
@@ -25,10 +25,18 @@ require (
 	github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
 	github.com/99designs/keyring v1.2.1 // indirect
 	github.com/BurntSushi/toml v1.2.0 // indirect
+	github.com/ChainSafe/chaindb v0.1.5-0.20220322154826-c0d431995732 // indirect
 	github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
 	github.com/ChainSafe/go-schnorrkel/1 v0.0.0-00010101000000-000000000000 // indirect
+	github.com/ChainSafe/gossamer v0.6.1-0.20220406182257-98400b30ca00 // indirect
+	github.com/ChainSafe/log15 v1.0.0 // indirect
+	github.com/ComposableFi/go-merkle-trees v0.0.0-20220505132313-e976260288cc // indirect
+	github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420 // indirect
+	github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220921072213-b36dd716026d // indirect
+	github.com/ComposableFi/ics11-beefy v0.0.0-20220915123708-0965f4b89a73 // indirect
 	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/Microsoft/hcsshim v0.9.3 // indirect
+	github.com/OneOfOne/xxhash v1.2.8 // indirect
 	github.com/StirlingMarketingGroup/go-namecase v1.0.0 // indirect
 	github.com/armon/go-metrics v0.4.0 // indirect
 	github.com/avast/retry-go/v4 v4.1.0 // indirect
@@ -120,6 +128,7 @@ require (
 	github.com/libp2p/go-libp2p-core v0.15.1 // indirect
 	github.com/libp2p/go-openssl v0.0.7 // indirect
 	github.com/magiconair/properties v1.8.6 // indirect
+	github.com/mattn/go-colorable v0.1.12 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
 	github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
@@ -152,6 +161,7 @@ require (
 	github.com/prometheus/client_model v0.2.0 // indirect
 	github.com/prometheus/common v0.34.0 // indirect
 	github.com/prometheus/procfs v0.7.3 // indirect
+	github.com/qdm12/gotree v0.2.0 // indirect
 	github.com/rakyll/statik v0.1.7 // indirect
 	github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
 	github.com/regen-network/cosmos-proto v0.3.1 // indirect
diff --git a/ibctest/go.sum b/ibctest/go.sum
index 23f1412..bf6a16c 100644
--- a/ibctest/go.sum
+++ b/ibctest/go.sum
@@ -86,11 +86,26 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
 github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/ChainSafe/chaindb v0.1.5-0.20220322154826-c0d431995732 h1:dOdDfWtgfucQHXder8eSItN03JgQzY1V0BiKJgNERnE=
+github.com/ChainSafe/chaindb v0.1.5-0.20220322154826-c0d431995732/go.mod h1:P01m9E6xj6Mps1rtf7SurEX9oOcy1jYEyccZQAEw9+4=
 github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg=
 github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4=
 github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM=
 github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4=
+github.com/ChainSafe/gossamer v0.6.1-0.20220406182257-98400b30ca00 h1:eetbMPdFfK09+WYo+snwcq2VeadwT/CrrGPHtAjQrZc=
+github.com/ChainSafe/gossamer v0.6.1-0.20220406182257-98400b30ca00/go.mod h1:YPRks5U+FzhTT7ajfCntyHPjQFZtH+8ED/Qr/eUT99U=
+github.com/ChainSafe/log15 v1.0.0 h1:vRDVtWtVwIH5uSCBvgTTZh6FA58UBJ6+QiiypaZfBf8=
+github.com/ChainSafe/log15 v1.0.0/go.mod h1:5v1+ALHtdW0NfAeeoYyKmzCAMcAeqkdhIg4uxXWIgOg=
+github.com/ComposableFi/go-merkle-trees v0.0.0-20220505132313-e976260288cc h1:5tpwyiPkFooPS7cG+bOVfCwVwhf91icRgs8EmRfqUEs=
+github.com/ComposableFi/go-merkle-trees v0.0.0-20220505132313-e976260288cc/go.mod h1:h5z3TSbg27qqA6h62OH5t70MAoCUUiHPrkwjm9uYjus=
+github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420 h1:oknQF/iIhf5lVjbwjsVDzDByupRhga8nhA3NAmwyHDA=
+github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420/go.mod h1:KYkiMX5AbOlXXYfxkrYPrRPV6EbVUALTQh5ptUOJzu8=
+github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220921072213-b36dd716026d h1:dQbJo6PMzGltlkUgtYvB/K9/7eVR8tGTKIdL3Rq23lY=
+github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220921072213-b36dd716026d/go.mod h1:swRgltPLuxQuM1AnrlRRXkbtkcHN0BjlMvGC0iEkNjk=
+github.com/ComposableFi/ics11-beefy v0.0.0-20220915123708-0965f4b89a73 h1:hhTKNQFywkrdPc6txNjShNqiROyHUNEkQt7dldb/29c=
+github.com/ComposableFi/ics11-beefy v0.0.0-20220915123708-0965f4b89a73/go.mod h1:abQmZdVblL6RhFPAB7uAx4K7y4+hYexcOlVXk36N5YM=
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
 github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
@@ -116,6 +131,7 @@ github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:m
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
+github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
@@ -384,8 +400,10 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
 github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
+github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM=
 github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
 github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
+github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
 github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
 github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
 github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
@@ -544,6 +562,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -763,8 +782,11 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@@ -979,6 +1001,8 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
+github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
 github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
 github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
@@ -1330,6 +1354,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1420,6 +1445,7 @@ golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/relayer/chains/substrate/key_test.go b/relayer/chains/substrate/key_test.go
index fd303b0..4f98ea7 100644
--- a/relayer/chains/substrate/key_test.go
+++ b/relayer/chains/substrate/key_test.go
@@ -1,6 +1,7 @@
 package substrate_test
 
 import (
+	"github.com/cosmos/relayer/v2/relayer/chains/substrate"
 	"testing"
 
 	"github.com/cosmos/relayer/v2/relayer/chains/substrate/keystore"
@@ -14,12 +15,12 @@ func TestKeyRestoreAndRetrieve(t *testing.T) {
 	mnemonic := "blind master acoustic speak victory lend kiss grab glad help demand hood roast zone lend sponsor level cheap truck kingdom apology token hover reunion"
 	expectedAddress := "5Hn67YZ75F3XrHiJAtiscJMGQ4zFNw9e45CNfLuxL6vEVYz8"
 
-	provider, err := getTestProvider()
-	require.Nil(t, err)
-
+	provider := substrate.SubstrateProvider{}
 	config := getSubstrateConfig(homePath, 42)
-	provider.Keybase, err = keystore.New(config.ChainName, config.KeyringBackend, config.KeyDirectory, nil)
+	provider.Config = config
+	keybase, err := keystore.New(config.ChainName, config.KeyringBackend, config.KeyDirectory, nil)
 	require.Nil(t, err)
+	provider.Keybase = keybase
 
 	if provider.KeyExists(keyName) {
 		err = provider.DeleteKey(keyName) // Delete if test is being run again
diff --git a/relayer/chains/substrate/provider_test.go b/relayer/chains/substrate/provider_test.go
index ded7ce7..4e149b4 100644
--- a/relayer/chains/substrate/provider_test.go
+++ b/relayer/chains/substrate/provider_test.go
@@ -9,12 +9,14 @@ const homePath = "/tmp"
 
 func getSubstrateConfig(keyDir string, network uint16) *substrate.SubstrateProviderConfig {
 	return &substrate.SubstrateProviderConfig{
-		Key:            "default",
-		ChainID:        "substrate-test",
-		KeyringBackend: keystore.BackendTest,
-		KeyDirectory:   keyDir,
-		Timeout:        "20s",
-		Network:        network,
+		Key:                  "default",
+		ChainID:              "substrate-test",
+		KeyringBackend:       keystore.BackendTest,
+		KeyDirectory:         keyDir,
+		Timeout:              "20s",
+		Network:              network,
+		BeefyActivationBlock: 0,
+		ParaID:               2000,
 	}
 }