cosmossdk.io/client/v2@v2.0.0-beta.1/autocli/msg.go (about)

     1  package autocli
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/cockroachdb/errors"
     8  	"github.com/spf13/cobra"
     9  	"google.golang.org/protobuf/proto"
    10  	"google.golang.org/protobuf/reflect/protoreflect"
    11  	"google.golang.org/protobuf/types/dynamicpb"
    12  
    13  	autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
    14  	"cosmossdk.io/client/v2/autocli/flag"
    15  	"cosmossdk.io/client/v2/internal/util"
    16  
    17  	"github.com/cosmos/cosmos-sdk/client"
    18  	clienttx "github.com/cosmos/cosmos-sdk/client/tx"
    19  	"github.com/cosmos/cosmos-sdk/codec"
    20  	"github.com/cosmos/cosmos-sdk/types/tx/signing"
    21  	authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
    22  	authtxconfig "github.com/cosmos/cosmos-sdk/x/auth/tx/config"
    23  )
    24  
    25  // BuildMsgCommand builds the msg commands for all the provided modules. If a custom command is provided for a
    26  // module, this is used instead of any automatically generated CLI commands. This allows apps to a fully dynamic client
    27  // with a more customized experience if a binary with custom commands is downloaded.
    28  func (b *Builder) BuildMsgCommand(appOptions AppOptions, customCmds map[string]*cobra.Command) (*cobra.Command, error) {
    29  	msgCmd := topLevelCmd("tx", "Transaction subcommands")
    30  	if err := b.enhanceCommandCommon(msgCmd, msgCmdType, appOptions, customCmds); err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	return msgCmd, nil
    35  }
    36  
    37  // AddMsgServiceCommands adds a sub-command to the provided command for each
    38  // method in the specified service and returns the command. This can be used in
    39  // order to add auto-generated commands to an existing command.
    40  func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autocliv1.ServiceCommandDescriptor) error {
    41  	for cmdName, subCmdDescriptor := range cmdDescriptor.SubCommands {
    42  		subCmd := findSubCommand(cmd, cmdName)
    43  		if subCmd == nil {
    44  			subCmd = topLevelCmd(cmdName, fmt.Sprintf("Tx commands for the %s service", subCmdDescriptor.Service))
    45  		}
    46  
    47  		// Add recursive sub-commands if there are any. This is used for nested services.
    48  		if err := b.AddMsgServiceCommands(subCmd, subCmdDescriptor); err != nil {
    49  			return err
    50  		}
    51  
    52  		cmd.AddCommand(subCmd)
    53  	}
    54  
    55  	if cmdDescriptor.Service == "" {
    56  		// skip empty command descriptor
    57  		return nil
    58  	}
    59  
    60  	descriptor, err := b.FileResolver.FindDescriptorByName(protoreflect.FullName(cmdDescriptor.Service))
    61  	if err != nil {
    62  		return errors.Errorf("can't find service %s: %v", cmdDescriptor.Service, err)
    63  	}
    64  	service := descriptor.(protoreflect.ServiceDescriptor)
    65  	methods := service.Methods()
    66  
    67  	rpcOptMap := map[protoreflect.Name]*autocliv1.RpcCommandOptions{}
    68  	for _, option := range cmdDescriptor.RpcCommandOptions {
    69  		methodName := protoreflect.Name(option.RpcMethod)
    70  		// validate that methods exist
    71  		if m := methods.ByName(methodName); m == nil {
    72  			return fmt.Errorf("rpc method %q not found for service %q", methodName, service.FullName())
    73  		}
    74  		rpcOptMap[methodName] = option
    75  
    76  	}
    77  
    78  	for i := 0; i < methods.Len(); i++ {
    79  		methodDescriptor := methods.Get(i)
    80  		methodOpts, ok := rpcOptMap[methodDescriptor.Name()]
    81  		if !ok {
    82  			methodOpts = &autocliv1.RpcCommandOptions{}
    83  		}
    84  
    85  		if methodOpts.Skip {
    86  			continue
    87  		}
    88  
    89  		if !util.IsSupportedVersion(util.DescriptorDocs(methodDescriptor)) {
    90  			continue
    91  		}
    92  
    93  		methodCmd, err := b.BuildMsgMethodCommand(methodDescriptor, methodOpts)
    94  		if err != nil {
    95  			return err
    96  		}
    97  
    98  		if findSubCommand(cmd, methodCmd.Name()) != nil {
    99  			// do not overwrite existing commands
   100  			// we do not display a warning because you may want to overwrite an autocli command
   101  			continue
   102  		}
   103  
   104  		if methodCmd != nil {
   105  			cmd.AddCommand(methodCmd)
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  // BuildMsgMethodCommand returns a command that outputs the JSON representation of the message.
   113  func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions) (*cobra.Command, error) {
   114  	cmd, err := b.buildMethodCommandCommon(descriptor, options, func(cmd *cobra.Command, input protoreflect.Message) error {
   115  		cmd.SetContext(context.WithValue(context.Background(), client.ClientContextKey, &b.ClientCtx))
   116  
   117  		clientCtx, err := client.GetClientTxContext(cmd)
   118  		if err != nil {
   119  			return err
   120  		}
   121  
   122  		clientCtx = clientCtx.WithCmdContext(cmd.Context())
   123  		clientCtx = clientCtx.WithOutput(cmd.OutOrStdout())
   124  
   125  		// enable sign mode textual
   126  		// the config is always overwritten as we need to have set the flags to the client context
   127  		// this ensures that the context has the correct client.
   128  		if !clientCtx.Offline {
   129  			b.TxConfigOpts.EnabledSignModes = append(b.TxConfigOpts.EnabledSignModes, signing.SignMode_SIGN_MODE_TEXTUAL)
   130  			b.TxConfigOpts.TextualCoinMetadataQueryFn = authtxconfig.NewGRPCCoinMetadataQueryFn(clientCtx)
   131  
   132  			txConfig, err := authtx.NewTxConfigWithOptions(
   133  				codec.NewProtoCodec(clientCtx.InterfaceRegistry),
   134  				b.TxConfigOpts,
   135  			)
   136  			if err != nil {
   137  				return err
   138  			}
   139  
   140  			clientCtx = clientCtx.WithTxConfig(txConfig)
   141  		}
   142  
   143  		// set signer to signer field if empty
   144  		fd := input.Descriptor().Fields().ByName(protoreflect.Name(flag.GetSignerFieldName(input.Descriptor())))
   145  		if addr := input.Get(fd).String(); addr == "" {
   146  			addressCodec := b.Builder.AddressCodec
   147  
   148  			scalarType, ok := flag.GetScalarType(fd)
   149  			if ok {
   150  				// override address codec if validator or consensus address
   151  				switch scalarType {
   152  				case flag.ValidatorAddressStringScalarType:
   153  					addressCodec = b.Builder.ValidatorAddressCodec
   154  				case flag.ConsensusAddressStringScalarType:
   155  					addressCodec = b.Builder.ConsensusAddressCodec
   156  				}
   157  			}
   158  
   159  			signerFromFlag := clientCtx.GetFromAddress()
   160  			signer, err := addressCodec.BytesToString(signerFromFlag.Bytes())
   161  			if err != nil {
   162  				return fmt.Errorf("failed to set signer on message, got %v: %w", signerFromFlag, err)
   163  			}
   164  
   165  			input.Set(fd, protoreflect.ValueOfString(signer))
   166  		}
   167  
   168  		// AutoCLI uses protov2 messages, while the SDK only supports proto v1 messages.
   169  		// Here we use dynamicpb, to create a proto v1 compatible message.
   170  		// The SDK codec will handle protov2 -> protov1 (marshal)
   171  		msg := dynamicpb.NewMessage(input.Descriptor())
   172  		proto.Merge(msg, input.Interface())
   173  
   174  		return clienttx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   175  	})
   176  
   177  	if b.AddTxConnFlags != nil {
   178  		b.AddTxConnFlags(cmd)
   179  	}
   180  
   181  	// silence usage only for inner txs & queries commands
   182  	if cmd != nil {
   183  		cmd.SilenceUsage = true
   184  	}
   185  
   186  	return cmd, err
   187  }