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

feat: add autocli multi-chain command #14696

Merged
merged 16 commits into from
Jan 23, 2023
4 changes: 4 additions & 0 deletions client/v2/autocli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (appOptions AppOptions) EnhanceRootCommand(rootCmd *cobra.Command) error {
AddQueryConnFlags: flags.AddQueryFlagsToCmd,
}

return appOptions.EnhanceRootCommandWithBuilder(rootCmd, builder)
}

func (appOptions AppOptions) EnhanceRootCommandWithBuilder(rootCmd *cobra.Command, builder *Builder) error {
moduleOptions := appOptions.ModuleOptions
if moduleOptions == nil {
moduleOptions = map[string]*autocliv1.ModuleOptions{}
Expand Down
43 changes: 43 additions & 0 deletions client/v2/autocli/internal/remote/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package remote

import (
"encoding/json"
"os"
"path"

"github.com/pkg/errors"
)

type Config struct {
Chains map[string]*ChainConfig `json:"chains"`
}

type ChainConfig struct {
TrustedGRPCEndpoints []GRPCEndpoint `json:"trusted_grpc_endpoints"`
}

type GRPCEndpoint struct {
Endpoint string `json:"endpoint"`
Insecure bool `json:"insecure"`
}

func LoadConfig(configDir string) (*Config, error) {
configPath := path.Join(configDir, "config.json")
if _, err := os.Stat(configPath); err != nil {
// file doesn't exist
return &Config{}, nil
}

bz, err := os.ReadFile(configPath)
if err != nil {
return nil, errors.Wrapf(err, "can't read config file: %s", configPath)
}

config := &Config{}
err = json.Unmarshal(bz, config)
if err != nil {
return nil, errors.Wrapf(err, "can't load config file: %s", configPath)
}

return config, err
}
67 changes: 67 additions & 0 deletions client/v2/autocli/internal/remote/load.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package remote

import (
"context"
"crypto/tls"

autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
)

type ChainInfo struct {
ModuleOptions map[string]*autocliv1.ModuleOptions
GRPCClient *grpc.ClientConn
FileDescriptorSet *protoregistry.Files
Context context.Context
}

func LoadChainInfo(chain string, config *ChainConfig, reload bool) (*ChainInfo, error) {
var client *grpc.ClientConn
for _, endpoint := range config.TrustedGRPCEndpoints {
var err error
var creds credentials.TransportCredentials
if endpoint.Insecure {
creds = insecure.NewCredentials()
} else {
creds = credentials.NewTLS(&tls.Config{
MinVersion: tls.VersionTLS12,
})
}
client, err = grpc.Dial(endpoint.Endpoint, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, err
}
}

ctx := context.Background()

autocliQueryClient := autocliv1.NewQueryClient(client)
apppOptionsRes, err := autocliQueryClient.AppOptions(ctx, &autocliv1.AppOptionsRequest{})
if err != nil {
return nil, err
}

reflectionClient := reflectionv1.NewReflectionServiceClient(client)
fdRes, err := reflectionClient.FileDescriptors(ctx, &reflectionv1.FileDescriptorsRequest{})
if err != nil {
return nil, err
}

files, err := protodesc.NewFiles(&descriptorpb.FileDescriptorSet{File: fdRes.Files})
if err != nil {
return nil, err
}

return &ChainInfo{
ModuleOptions: apppOptionsRes.ModuleOptions,
GRPCClient: client,
Context: ctx,
FileDescriptorSet: files,
}, nil
}
72 changes: 72 additions & 0 deletions client/v2/autocli/internal/remote/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package remote

import (
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/manifoldco/promptui"
)

type ChainRegistryEntry struct {
APIs ChainRegistryAPIs `json:"apis"`
}

type ChainRegistryAPIs struct {
GRPC []*APIEntry `json:"grpc"`
}

type APIEntry struct {
Address string
Provider string
}

func GetChainRegistryEntry(chain string) (*ChainRegistryEntry, error) {
res, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/cosmos/chain-registry/master/%v/chain.json", chain))
if err != nil {
return nil, err
}

bz, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}

data := &ChainRegistryEntry{}
err = json.Unmarshal(bz, data)
if err != nil {
return nil, err
}

return data, nil
}

func SelectGRPCEndpoints(chain string) (string, error) {
entry, err := GetChainRegistryEntry(chain)
if err != nil {
return "", err
}

var items []string
for _, apiEntry := range entry.APIs.GRPC {
items = append(items, fmt.Sprintf("%s: %s", apiEntry.Provider, apiEntry.Address))
}
prompt := promptui.SelectWithAdd{
Label: fmt.Sprintf("Select a gRPC endpoint that you trust for the %s network", chain),
Items: items,
AddLabel: "Custom endpoint:",
}

i, ep, err := prompt.Run()
if err != nil {
return "", err
}

// user selected a custom endpoint
if i == -1 {
return ep, nil
}

return entry.APIs.GRPC[i].Address, nil
}
112 changes: 112 additions & 0 deletions client/v2/autocli/remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package autocli

import (
"fmt"
"strings"

"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/dynamicpb"

"cosmossdk.io/client/v2/autocli/flag"
"cosmossdk.io/client/v2/autocli/internal/remote"
)

type RemoteCommandOptions struct {
ConfigDir string
}

func (options RemoteCommandOptions) Command() (*cobra.Command, error) {
cmd := &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
endpoint, err := remote.SelectGRPCEndpoints(args[0])
if err != nil {
return err
}

fmt.Printf("Selected: %v", endpoint)
return nil
},
}

config, err := remote.LoadConfig(options.ConfigDir)
if err != nil {
return nil, err
}

for chain, chainConfig := range config.Chains {
chainInfo, err := remote.LoadChainInfo(chain, chainConfig, false)
if err != nil {
return nil, err
}

appOpts := AppOptions{
ModuleOptions: chainInfo.ModuleOptions,
}

builder := &Builder{
Builder: flag.Builder{
TypeResolver: &dynamicTypeResolver{
files: chainInfo.FileDescriptorSet,
},
FileResolver: chainInfo.FileDescriptorSet,
},
GetClientConn: func(command *cobra.Command) (grpc.ClientConnInterface, error) {
return chainInfo.GRPCClient, nil
},
AddQueryConnFlags: func(command *cobra.Command) {},
}

chainCmd := &cobra.Command{Use: chain}
err = appOpts.EnhanceRootCommandWithBuilder(chainCmd, builder)
if err != nil {
return nil, err
}
}
Fixed Show fixed Hide fixed

return cmd, nil
}

type dynamicTypeResolver struct {
files *protoregistry.Files
}

var _ protoregistry.MessageTypeResolver = dynamicTypeResolver{}
var _ protoregistry.ExtensionTypeResolver = dynamicTypeResolver{}

func (d dynamicTypeResolver) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
desc, err := d.files.FindDescriptorByName(message)
if err != nil {
return nil, err
}

return dynamicpb.NewMessageType(desc.(protoreflect.MessageDescriptor)), nil
}

func (d dynamicTypeResolver) FindMessageByURL(url string) (protoreflect.MessageType, error) {
if i := strings.LastIndexByte(url, '/'); i >= 0 {
url = url[i+len("/"):]
}

desc, err := d.files.FindDescriptorByName(protoreflect.FullName(url))
if err != nil {
return nil, err
}

return dynamicpb.NewMessageType(desc.(protoreflect.MessageDescriptor)), nil
}

func (d dynamicTypeResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
desc, err := d.files.FindDescriptorByName(field)
if err != nil {
return nil, err
}

return dynamicpb.NewExtensionType(desc.(protoreflect.ExtensionTypeDescriptor)), nil
}

func (d dynamicTypeResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
panic("TODO")
}
4 changes: 4 additions & 0 deletions client/v2/cmd/cosmcli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package main

func main() {
}