374 lines
11 KiB
Go
374 lines
11 KiB
Go
package autocli
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/spf13/cobra"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
grpcinsecure "google.golang.org/grpc/credentials/insecure"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
|
|
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
|
"cosmossdk.io/client/v2/autocli/config"
|
|
"cosmossdk.io/client/v2/autocli/keyring"
|
|
"cosmossdk.io/client/v2/broadcast/comet"
|
|
clientcontext "cosmossdk.io/client/v2/context"
|
|
"cosmossdk.io/client/v2/internal/flags"
|
|
"cosmossdk.io/client/v2/internal/print"
|
|
"cosmossdk.io/client/v2/internal/util"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
)
|
|
|
|
type cmdType int
|
|
|
|
const (
|
|
queryCmdType cmdType = iota
|
|
msgCmdType
|
|
)
|
|
|
|
func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions, exec func(cmd *cobra.Command, input protoreflect.Message) error) (*cobra.Command, error) {
|
|
if options == nil {
|
|
// use the defaults
|
|
options = &autocliv1.RpcCommandOptions{}
|
|
}
|
|
|
|
short := options.Short
|
|
if short == "" {
|
|
short = fmt.Sprintf("Execute the %s RPC method", descriptor.Name())
|
|
}
|
|
|
|
inputDesc := descriptor.Input()
|
|
inputType := util.ResolveMessageType(b.TypeResolver, inputDesc)
|
|
|
|
use := options.Use
|
|
if use == "" {
|
|
use = protoNameToCliName(descriptor.Name())
|
|
}
|
|
|
|
cmd := &cobra.Command{
|
|
SilenceUsage: false,
|
|
Use: use,
|
|
Long: options.Long,
|
|
Short: short,
|
|
Example: options.Example,
|
|
Aliases: options.Alias,
|
|
SuggestFor: options.SuggestFor,
|
|
Deprecated: options.Deprecated,
|
|
Version: options.Version,
|
|
}
|
|
|
|
// we need to use a pointer to the context as the correct context is set in the RunE function
|
|
// however we need to set the flags before the RunE function is called
|
|
ctx := cmd.Context()
|
|
binder, err := b.AddMessageFlags(&ctx, cmd.Flags(), inputType, options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd.Args = binder.CobraArgs
|
|
|
|
cmd.PreRunE = b.preRunE()
|
|
|
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
|
ctx, err = b.getContext(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
input, err := binder.BuildMessage(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// signer related logic, triggers only when there is a signer defined
|
|
if binder.SignerInfo.FieldName != "" {
|
|
if binder.SignerInfo.IsFlag {
|
|
// the client context uses the from flag to determine the signer.
|
|
// this sets the signer flags to the from flag value if a custom signer flag is set.
|
|
// marks the custom flag as required.
|
|
if binder.SignerInfo.FlagName != flags.FlagFrom {
|
|
if err := cmd.MarkFlagRequired(binder.SignerInfo.FlagName); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := cmd.Flags().Set(flags.FlagFrom, cmd.Flag(binder.SignerInfo.FlagName).Value.String()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
// if the signer is not a flag, it is a positional argument
|
|
// we need to get the correct positional arguments
|
|
if err := cmd.Flags().Set(flags.FlagFrom, args[binder.SignerInfo.PositionalArgIndex]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return exec(cmd, input)
|
|
}
|
|
|
|
return cmd, nil
|
|
}
|
|
|
|
// enhanceCommandCommon enhances the provided query or msg command with either generated commands based on the provided module
|
|
// options or the provided custom commands for each module. If the provided query command already contains a command
|
|
// for a module, that command is not over-written by this method. This allows a graceful addition of autocli to
|
|
// automatically fill in missing commands.
|
|
func (b *Builder) enhanceCommandCommon(
|
|
cmd *cobra.Command,
|
|
cmdType cmdType,
|
|
appOptions AppOptions,
|
|
customCmds map[string]*cobra.Command,
|
|
) error {
|
|
moduleOptions := appOptions.ModuleOptions
|
|
if len(moduleOptions) == 0 {
|
|
moduleOptions = make(map[string]*autocliv1.ModuleOptions)
|
|
}
|
|
for name, module := range appOptions.Modules {
|
|
if _, ok := moduleOptions[name]; !ok {
|
|
if module, ok := module.(HasAutoCLIConfig); ok {
|
|
moduleOptions[name] = module.AutoCLIOptions()
|
|
} else {
|
|
moduleOptions[name] = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
for moduleName, modOpts := range moduleOptions {
|
|
hasModuleOptions := modOpts != nil
|
|
|
|
// if we have an existing command skip adding one here
|
|
if subCmd := findSubCommand(cmd, moduleName); subCmd != nil {
|
|
if hasModuleOptions { // check if we need to enhance the existing command
|
|
if err := enhanceCustomCmd(b, subCmd, cmdType, modOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// if we have a custom command use that instead of generating one
|
|
if custom, ok := customCmds[moduleName]; ok {
|
|
// Custom may not be called the same as its module, so we need to have a separate check here
|
|
if subCmd := findSubCommand(cmd, custom.Name()); subCmd != nil {
|
|
if hasModuleOptions { // check if we need to enhance the existing command
|
|
if err := enhanceCustomCmd(b, subCmd, cmdType, modOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if hasModuleOptions { // check if we need to enhance the new command
|
|
if err := enhanceCustomCmd(b, custom, cmdType, modOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
cmd.AddCommand(custom)
|
|
continue
|
|
}
|
|
|
|
// if we don't have module options, skip adding a command as we don't have anything to add
|
|
if !hasModuleOptions {
|
|
continue
|
|
}
|
|
|
|
switch cmdType {
|
|
case queryCmdType:
|
|
if err := enhanceQuery(b, moduleName, cmd, modOpts); err != nil {
|
|
return err
|
|
}
|
|
case msgCmdType:
|
|
if err := enhanceMsg(b, moduleName, cmd, modOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// enhanceQuery enhances the provided query command with the autocli commands for a module.
|
|
func enhanceQuery(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error {
|
|
if queryCmdDesc := modOpts.Query; queryCmdDesc != nil {
|
|
short := queryCmdDesc.Short
|
|
if short == "" {
|
|
short = fmt.Sprintf("Querying commands for the %s module", moduleName)
|
|
}
|
|
subCmd := topLevelCmd(cmd.Context(), moduleName, short)
|
|
if err := builder.AddQueryServiceCommands(subCmd, queryCmdDesc); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.AddCommand(subCmd)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// enhanceMsg enhances the provided msg command with the autocli commands for a module.
|
|
func enhanceMsg(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error {
|
|
if txCmdDesc := modOpts.Tx; txCmdDesc != nil {
|
|
short := txCmdDesc.Short
|
|
if short == "" {
|
|
short = fmt.Sprintf("Transactions commands for the %s module", moduleName)
|
|
}
|
|
subCmd := topLevelCmd(cmd.Context(), moduleName, short)
|
|
if err := builder.AddMsgServiceCommands(subCmd, txCmdDesc); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.AddCommand(subCmd)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// enhanceCustomCmd enhances the provided custom query or msg command autocli commands for a module.
|
|
func enhanceCustomCmd(builder *Builder, cmd *cobra.Command, cmdType cmdType, modOpts *autocliv1.ModuleOptions) error {
|
|
switch cmdType {
|
|
case queryCmdType:
|
|
if modOpts.Query != nil && modOpts.Query.EnhanceCustomCommand {
|
|
if err := builder.AddQueryServiceCommands(cmd, modOpts.Query); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case msgCmdType:
|
|
if modOpts.Tx != nil && modOpts.Tx.EnhanceCustomCommand {
|
|
if err := builder.AddMsgServiceCommands(cmd, modOpts.Tx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// outOrStdoutFormat formats the output based on the output flag and writes it to the command's output stream.
|
|
func (b *Builder) outOrStdoutFormat(cmd *cobra.Command, out []byte) error {
|
|
p, err := print.NewPrinter(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return p.PrintBytes(out)
|
|
}
|
|
|
|
// getContext creates and returns a new context.Context with an autocli.Context value.
|
|
// It initializes a printer and, if necessary, a keyring based on command flags.
|
|
func (b *Builder) getContext(cmd *cobra.Command) (context.Context, error) {
|
|
// if the command uses the keyring this must be set
|
|
var (
|
|
k keyring.Keyring
|
|
err error
|
|
)
|
|
if cmd.Flags().Lookup(flags.FlagKeyringDir) != nil && cmd.Flags().Lookup(flags.FlagKeyringBackend) != nil {
|
|
k, err = keyring.NewKeyringFromFlags(cmd.Flags(), b.AddressCodec, cmd.InOrStdin(), b.Cdc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
k = keyring.NoKeyring{}
|
|
}
|
|
|
|
clientCtx := clientcontext.Context{
|
|
Flags: cmd.Flags(),
|
|
AddressCodec: b.AddressCodec,
|
|
ValidatorAddressCodec: b.ValidatorAddressCodec,
|
|
ConsensusAddressCodec: b.ConsensusAddressCodec,
|
|
Cdc: b.Cdc,
|
|
Keyring: k,
|
|
EnabledSignModes: b.EnabledSignModes,
|
|
}
|
|
|
|
return clientcontext.SetInContext(cmd.Context(), clientCtx), nil
|
|
}
|
|
|
|
// preRunE returns a function that sets flags from the configuration before running a command.
|
|
// It is used as a PreRunE hook for cobra commands to ensure flags are properly initialized
|
|
// from the configuration before command execution.
|
|
func (b *Builder) preRunE() func(cmd *cobra.Command, args []string) error {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
err := b.setFlagsFromConfig(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// setFlagsFromConfig sets command flags from the provided configuration.
|
|
// It only sets flags that haven't been explicitly changed by the user.
|
|
func (b *Builder) setFlagsFromConfig(cmd *cobra.Command) error {
|
|
conf, err := config.CreateClientConfigFromFlags(cmd.Flags())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
flagsToSet := map[string]string{
|
|
flags.FlagChainID: conf.ChainID,
|
|
flags.FlagKeyringBackend: conf.KeyringBackend,
|
|
flags.FlagFrom: conf.KeyringDefaultKeyName,
|
|
flags.FlagOutput: conf.Output,
|
|
flags.FlagNode: conf.Node,
|
|
flags.FlagBroadcastMode: conf.BroadcastMode,
|
|
flags.FlagGrpcAddress: conf.GRPC.Address,
|
|
flags.FlagGrpcInsecure: strconv.FormatBool(conf.GRPC.Insecure),
|
|
}
|
|
|
|
for flagName, value := range flagsToSet {
|
|
if flag := cmd.Flags().Lookup(flagName); flag != nil && !cmd.Flags().Changed(flagName) {
|
|
if err := cmd.Flags().Set(flagName, value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getQueryClientConn returns a function that creates a gRPC client connection based on command flags.
|
|
// It handles the creation of secure or insecure connections and falls back to a CometBFT broadcaster
|
|
// if no gRPC address is specified.
|
|
func getQueryClientConn(cdc codec.Codec) func(cmd *cobra.Command) (grpc.ClientConnInterface, error) {
|
|
return func(cmd *cobra.Command) (grpc.ClientConnInterface, error) {
|
|
var err error
|
|
creds := grpcinsecure.NewCredentials()
|
|
|
|
insecure := true
|
|
if cmd.Flags().Lookup(flags.FlagGrpcInsecure) != nil {
|
|
insecure, err = cmd.Flags().GetBool(flags.FlagGrpcInsecure)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if !insecure {
|
|
creds = credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12})
|
|
}
|
|
|
|
var addr string
|
|
if cmd.Flags().Lookup(flags.FlagGrpcAddress) != nil {
|
|
addr, err = cmd.Flags().GetString(flags.FlagGrpcAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if addr == "" {
|
|
// if grpc-addr has not been set, use the default clientConn
|
|
// TODO: default is comet
|
|
node, err := cmd.Flags().GetString(flags.FlagNode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return comet.NewCometBFTBroadcaster(node, comet.BroadcastSync, cdc)
|
|
}
|
|
|
|
return grpc.NewClient(addr, []grpc.DialOption{grpc.WithTransportCredentials(creds)}...)
|
|
}
|
|
}
|