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 }