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

Namada support v2 #1419

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

"github.com/cosmos/relayer/v2/relayer"
"github.com/cosmos/relayer/v2/relayer/chains/cosmos"
"github.com/cosmos/relayer/v2/relayer/chains/namada"
"github.com/cosmos/relayer/v2/relayer/chains/penumbra"
"github.com/cosmos/relayer/v2/relayer/provider"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -377,6 +378,7 @@ func (pcw *ProviderConfigWrapper) UnmarshalJSON(data []byte) error {
customTypes := map[string]reflect.Type{
"cosmos": reflect.TypeOf(cosmos.CosmosProviderConfig{}),
"penumbra": reflect.TypeOf(penumbra.PenumbraProviderConfig{}),
"namada": reflect.TypeOf(namada.NamadaProviderConfig{}),
}
val, err := UnmarshalJSONProviderConfig(data, customTypes)
if err != nil {
Expand Down Expand Up @@ -435,6 +437,8 @@ func (iw *ProviderConfigYAMLWrapper) UnmarshalYAML(n *yaml.Node) error {
iw.Value = new(cosmos.CosmosProviderConfig)
case "penumbra":
iw.Value = new(penumbra.PenumbraProviderConfig)
case "namada":
iw.Value = new(namada.NamadaProviderConfig)
default:
return fmt.Errorf("%s is an invalid chain type, check your config file", iw.Type)
}
Expand Down
355 changes: 355 additions & 0 deletions relayer/chains/namada/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
package cosmos

import (
"context"
"fmt"
"io"
"os"
"path"
"sync"
"time"

provtypes "github.com/cometbft/cometbft/light/provider"
prov "github.com/cometbft/cometbft/light/provider/http"
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
libclient "github.com/cometbft/cometbft/rpc/jsonrpc/client"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/gogoproto/proto"
commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"
cwrapper "github.com/cosmos/relayer/v2/client"
"github.com/cosmos/relayer/v2/relayer/codecs/ethermint"
"github.com/cosmos/relayer/v2/relayer/processor"
"github.com/cosmos/relayer/v2/relayer/provider"
"github.com/strangelove-ventures/cometbft-client/client"
"go.uber.org/zap"
)

var (
_ provider.ChainProvider = &NamadaProvider{}

Check failure on line 29 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

cannot use &NamadaProvider{} (value of type *NamadaProvider) as "github.com/cosmos/relayer/v2/relayer/provider".ChainProvider value in variable declaration: *NamadaProvider does not implement "github.com/cosmos/relayer/v2/relayer/provider".ChainProvider (missing method AcknowledgementFromSequence)
_ provider.KeyProvider = &NamadaProvider{}

Check failure on line 30 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

cannot use &NamadaProvider{} (value of type *NamadaProvider) as "github.com/cosmos/relayer/v2/relayer/provider".KeyProvider value in variable declaration: *NamadaProvider does not implement "github.com/cosmos/relayer/v2/relayer/provider".KeyProvider (missing method AddKey)
_ provider.ProviderConfig = &NamadaProviderConfig{}
)

type NamadaProviderConfig struct {
KeyDirectory string `json:"key-directory" yaml:"key-directory"`
Key string `json:"key" yaml:"key"`
ChainName string `json:"-" yaml:"-"`
ChainID string `json:"chain-id" yaml:"chain-id"`
RPCAddr string `json:"rpc-addr" yaml:"rpc-addr"`
AccountPrefix string `json:"account-prefix" yaml:"account-prefix"`
KeyringBackend string `json:"keyring-backend" yaml:"keyring-backend"`
GasAdjustment float64 `json:"gas-adjustment" yaml:"gas-adjustment"`
GasPrices string `json:"gas-prices" yaml:"gas-prices"`
MinGasAmount uint64 `json:"min-gas-amount" yaml:"min-gas-amount"`
MaxGasAmount uint64 `json:"max-gas-amount" yaml:"max-gas-amount"`
Debug bool `json:"debug" yaml:"debug"`
Timeout string `json:"timeout" yaml:"timeout"`
BlockTimeout string `json:"block-timeout" yaml:"block-timeout"`
OutputFormat string `json:"output-format" yaml:"output-format"`
SignModeStr string `json:"sign-mode" yaml:"sign-mode"`
ExtraCodecs []string `json:"extra-codecs" yaml:"extra-codecs"`
Modules []module.AppModuleBasic `json:"-" yaml:"-"`
Slip44 *int `json:"coin-type" yaml:"coin-type"`
Broadcast provider.BroadcastMode `json:"broadcast-mode" yaml:"broadcast-mode"`
MinLoopDuration time.Duration `json:"min-loop-duration" yaml:"min-loop-duration"`
ExtensionOptions []provider.ExtensionOption `json:"extension-options" yaml:"extension-options"`
}

func (pc NamadaProviderConfig) Validate() error {
if _, err := time.ParseDuration(pc.Timeout); err != nil {
return fmt.Errorf("invalid Timeout: %w", err)
}
return nil
}

func (pc NamadaProviderConfig) BroadcastMode() provider.BroadcastMode {
return pc.Broadcast
}

// NewProvider validates the CosmosProviderConfig, instantiates a ChainClient and then instantiates a CosmosProvider
func (pc NamadaProviderConfig) NewProvider(log *zap.Logger, homepath string, debug bool, chainName string) (provider.ChainProvider, error) {
if err := pc.Validate(); err != nil {
return nil, err
}

pc.KeyDirectory = keysDir(homepath, pc.ChainID)

pc.ChainName = chainName
pc.Modules = append([]module.AppModuleBasic{}, ModuleBasics...)

Check failure on line 79 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

undefined: ModuleBasics

if pc.Broadcast == "" {
pc.Broadcast = provider.BroadcastModeBatch
}

cp := &NamadaProvider{
log: log,
PCfg: pc,
KeyringOptions: []keyring.Option{ethermint.EthSecp256k1Option()},
Input: os.Stdin,
Output: os.Stdout,
walletStateMap: map[string]*WalletState{},

// TODO: this is a bit of a hack, we should probably have a better way to inject modules
Cdc: MakeCodec(pc.Modules, pc.ExtraCodecs, pc.AccountPrefix, pc.AccountPrefix+"valoper"),
}

return cp, nil
}

type NamadaIBCHeader struct {
SignedHeader *tmtypes.SignedHeader

Check failure on line 101 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

undefined: tmtypes
ValidatorSet *tmtypes.ValidatorSet

Check failure on line 102 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

undefined: tmtypes
}

func (h NamadaIBCHeader) Height() uint64 {
return uint64(h.SignedHeader.Height)
}

func (h NamadaIBCHeader) ConsensusState() ibcexported.ConsensusState {

Check failure on line 109 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

undefined: ibcexported
return &tmclient.ConsensusState{
Timestamp: h.SignedHeader.Time,
Root: commitmenttypes.NewMerkleRoot(h.SignedHeader.AppHash),
NextValidatorsHash: h.ValidatorSet.Hash(),
}
}

type NamadaProvider struct {
log *zap.Logger

PCfg NamadaProviderConfig
Keybase keyring.Keyring
KeyringOptions []keyring.Option
RPCClient cwrapper.RPCClient
LightProvider provtypes.Provider
Input io.Reader
Output io.Writer
Codec Codec

Check failure on line 127 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

undefined: Codec
RPCCaller jsonrpcclient.Caller

Check failure on line 128 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

undefined: jsonrpcclient
}

type WalletState struct {
NextAccountSequence uint64
Mu sync.Mutex
}

func (cc *NamadaProvider) ProviderConfig() provider.ProviderConfig {
return cc.PCfg
}

func (cc *NamadaProvider) ChainId() string {
return cc.PCfg.ChainID
}

func (cc *NamadaProvider) ChainName() string {
return cc.PCfg.ChainName
}

func (cc *NamadaProvider) Type() string {
return "namada"
}

func (cc *NamadaProvider) Key() string {
return cc.PCfg.Key
}

func (cc *NamadaProvider) Timeout() string {
return cc.PCfg.Timeout
}

// CommitmentPrefix returns the commitment prefix for Cosmos
func (cc *NamadaProvider) CommitmentPrefix() commitmenttypes.MerklePrefix {
return defaultChainPrefix
}

// Address returns the chains configured address as a string
func (cc *NamadaProvider) Address() (string, error) {
info, err := cc.Keybase.Key(cc.PCfg.Key)
if err != nil {
return "", err
}

acc, err := info.GetAddress()
if err != nil {
return "", err
}

out, err := cc.EncodeBech32AccAddr(acc)
if err != nil {
return "", err
}

return out, err
}

func (cc *NamadaProvider) MustEncodeAccAddr(addr sdk.AccAddress) string {

Check failure on line 185 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

undefined: sdk
enc, err := cc.EncodeBech32AccAddr(addr)
if err != nil {
panic(err)
}
return enc
}

// AccountFromKeyOrAddress returns an account from either a key or an address.
// If 'keyOrAddress' is the empty string, this returns the default key's address.
func (cc *NamadaProvider) AccountFromKeyOrAddress(keyOrAddress string) (out sdk.AccAddress, err error) {

Check failure on line 195 in relayer/chains/namada/provider.go

View workflow job for this annotation

GitHub Actions / build

undefined: sdk
switch {
case keyOrAddress == "":
out, err = cc.GetKeyAddress(cc.PCfg.Key)
case cc.KeyExists(keyOrAddress):
out, err = cc.GetKeyAddress(keyOrAddress)
default:
out, err = sdk.GetFromBech32(keyOrAddress, cc.PCfg.AccountPrefix)
}
return
}

func (cc *NamadaProvider) TrustingPeriod(ctx context.Context, overrideUnbondingPeriod time.Duration, percentage int64) (time.Duration, error) {

unbondingTime := overrideUnbondingPeriod
var err error
if unbondingTime == 0 {
unbondingTime, err = cc.QueryUnbondingPeriod(ctx)
if err != nil {
return 0, err
}
}

// We want the trusting period to be `percentage` of the unbonding time.
// Go mentions that the time.Duration type can track approximately 290 years.
// We don't want to lose precision if the duration is a very long duration
// by converting int64 to float64.
// Use integer math the whole time, first reducing by a factor of 100
// and then re-growing by the `percentage` param.
tp := time.Duration(int64(unbondingTime) / 100 * percentage)

// And we only want the trusting period to be whole hours.
// But avoid rounding if the time is less than 1 hour
// (otherwise the trusting period will go to 0)
if tp > time.Hour {
tp = tp.Truncate(time.Hour)
}
return tp, nil
}

// Sprint returns the json representation of the specified proto message.
func (cc *NamadaProvider) Sprint(toPrint proto.Message) (string, error) {
out, err := cc.Cdc.Marshaler.MarshalJSON(toPrint)
if err != nil {
return "", err
}
return string(out), nil
}

// SetPCAddr sets the rpc-addr for the chain.
// It will fail if the rpcAddr is invalid(not a url).
func (cc *NamadaProvider) SetRpcAddr(rpcAddr string) error {
cc.PCfg.RPCAddr = rpcAddr
return nil
}

// Init initializes the keystore, RPC client, amd light client provider.
// Once initialization is complete an attempt to query the underlying node's tendermint version is performed.
// NOTE: Init must be called after creating a new instance of CosmosProvider.
func (cc *NamadaProvider) Init(ctx context.Context) error {
keybase, err := keyring.New(
cc.PCfg.ChainID,
cc.PCfg.KeyringBackend,
cc.PCfg.KeyDirectory,
cc.Input,
cc.Cdc.Marshaler,
cc.KeyringOptions...,
)
if err != nil {
return err
}
// TODO: figure out how to deal with input or maybe just make all keyring backends test?

timeout, err := time.ParseDuration(cc.PCfg.Timeout)
if err != nil {
return err
}

c, err := client.NewClient(cc.PCfg.RPCAddr, timeout)
if err != nil {
return err
}

lightprovider, err := prov.New(cc.PCfg.ChainID, cc.PCfg.RPCAddr)
if err != nil {
return err
}

rpcClient := cwrapper.NewRPCClient(c)

cc.RPCClient = rpcClient
cc.LightProvider = lightprovider
cc.Keybase = keybase

return nil
}

// WaitForNBlocks blocks until the next block on a given chain
func (cc *NamadaProvider) WaitForNBlocks(ctx context.Context, n int64) error {
var initial int64
h, err := cc.RPCClient.Status(ctx)
if err != nil {
return err
}
if h.SyncInfo.CatchingUp {
return fmt.Errorf("chain catching up")
}
initial = h.SyncInfo.LatestBlockHeight
for {
h, err = cc.RPCClient.Status(ctx)
if err != nil {
return err
}
if h.SyncInfo.LatestBlockHeight > initial+n {
return nil
}
select {
case <-time.After(10 * time.Millisecond):
// Nothing to do.
case <-ctx.Done():
return ctx.Err()
}
}
}

func (cc *NamadaProvider) BlockTime(ctx context.Context, height int64) (time.Time, error) {
resultBlock, err := cc.RPCClient.Block(ctx, &height)
if err != nil {
return time.Time{}, err
}
return resultBlock.Block.Time, nil
}

func (cc *NamadaProvider) SetMetrics(m *processor.PrometheusMetrics) {
cc.metrics = m
}

func (cc *NamadaProvider) updateNextAccountSequence(sequenceGuard *WalletState, seq uint64) {
if seq > sequenceGuard.NextAccountSequence {
sequenceGuard.NextAccountSequence = seq
}
}

// keysDir returns a string representing the path on the local filesystem where the keystore will be initialized.
func keysDir(home, chainID string) string {
return path.Join(home, "keys", chainID)
}

// NewRPCClient initializes a new tendermint RPC client connected to the specified address.
func NewRPCClient(addr string, timeout time.Duration) (*rpchttp.HTTP, error) {
httpClient, err := libclient.DefaultHTTPClient(addr)
if err != nil {
return nil, err
}
httpClient.Timeout = timeout
rpcClient, err := rpchttp.NewWithClient(addr, "/websocket", httpClient)
if err != nil {
return nil, err
}
return rpcClient, nil
}
Loading