Skip to content

Commit

Permalink
Namada support v2
Browse files Browse the repository at this point in the history
draft

Signed-off-by: Hwangjae Lee <[email protected]>
  • Loading branch information
meetrick committed Mar 11, 2024
1 parent 4692fb1 commit 346aae6
Show file tree
Hide file tree
Showing 2 changed files with 359 additions and 0 deletions.
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(penumbra.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
}

0 comments on commit 346aae6

Please sign in to comment.