-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Expand file tree
/
Copy pathmsg.go
More file actions
230 lines (188 loc) · 7.51 KB
/
Copy pathmsg.go
File metadata and controls
230 lines (188 loc) · 7.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package autocli
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/dynamicpb"
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
"cosmossdk.io/client/v2/autocli/flag"
"cosmossdk.io/client/v2/internal/flags"
"cosmossdk.io/client/v2/internal/util"
addresscodec "cosmossdk.io/core/address"
// the following will be extracted to a separate module
// https://github.com/cosmos/cosmos-sdk/issues/14403
authtypes "cosmossdk.io/x/auth/types"
govcli "cosmossdk.io/x/gov/client/cli"
govtypes "cosmossdk.io/x/gov/types"
"github.com/cosmos/cosmos-sdk/client"
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
)
// BuildMsgCommand builds the msg commands for all the provided modules. If a custom command is provided for a
// module, this is used instead of any automatically generated CLI commands. This allows apps to a fully dynamic client
// with a more customized experience if a binary with custom commands is downloaded.
func (b *Builder) BuildMsgCommand(ctx context.Context, appOptions AppOptions, customCmds map[string]*cobra.Command) (*cobra.Command, error) {
msgCmd := topLevelCmd(ctx, "tx", "Transaction subcommands")
if err := b.enhanceCommandCommon(msgCmd, msgCmdType, appOptions, customCmds); err != nil {
return nil, err
}
return msgCmd, nil
}
// AddMsgServiceCommands adds a sub-command to the provided command for each
// method in the specified service and returns the command. This can be used in
// order to add auto-generated commands to an existing command.
func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autocliv1.ServiceCommandDescriptor) error {
for cmdName, subCmdDescriptor := range cmdDescriptor.SubCommands {
subCmd := findSubCommand(cmd, cmdName)
if subCmd == nil {
subCmd = topLevelCmd(cmd.Context(), cmdName, fmt.Sprintf("Tx commands for the %s service", subCmdDescriptor.Service))
}
// Add recursive sub-commands if there are any. This is used for nested services.
if err := b.AddMsgServiceCommands(subCmd, subCmdDescriptor); err != nil {
return err
}
cmd.AddCommand(subCmd)
}
if cmdDescriptor.Service == "" {
// skip empty command descriptor
return nil
}
descriptor, err := b.FileResolver.FindDescriptorByName(protoreflect.FullName(cmdDescriptor.Service))
if err != nil {
return errors.Errorf("can't find service %s: %v", cmdDescriptor.Service, err)
}
service := descriptor.(protoreflect.ServiceDescriptor)
methods := service.Methods()
rpcOptMap := map[protoreflect.Name]*autocliv1.RpcCommandOptions{}
for _, option := range cmdDescriptor.RpcCommandOptions {
methodName := protoreflect.Name(option.RpcMethod)
// validate that methods exist
if m := methods.ByName(methodName); m == nil {
return fmt.Errorf("rpc method %q not found for service %q", methodName, service.FullName())
}
rpcOptMap[methodName] = option
}
for i := 0; i < methods.Len(); i++ {
methodDescriptor := methods.Get(i)
methodOpts, ok := rpcOptMap[methodDescriptor.Name()]
if !ok {
methodOpts = &autocliv1.RpcCommandOptions{}
}
if methodOpts.Skip {
continue
}
if !util.IsSupportedVersion(methodDescriptor) {
continue
}
methodCmd, err := b.BuildMsgMethodCommand(methodDescriptor, methodOpts)
if err != nil {
return err
}
if findSubCommand(cmd, methodCmd.Name()) != nil {
// do not overwrite existing commands
// we do not display a warning because you may want to overwrite an autocli command
continue
}
if methodCmd != nil {
cmd.AddCommand(methodCmd)
}
}
return nil
}
// BuildMsgMethodCommand returns a command that outputs the JSON representation of the message.
func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions) (*cobra.Command, error) {
execFunc := func(cmd *cobra.Command, input protoreflect.Message) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
clientCtx = clientCtx.WithCmdContext(cmd.Context())
clientCtx = clientCtx.WithOutput(cmd.OutOrStdout())
fd := input.Descriptor().Fields().ByName(protoreflect.Name(flag.GetSignerFieldName(input.Descriptor())))
addressCodec := b.Builder.AddressCodec
// handle gov proposals commands
skipProposal, _ := cmd.Flags().GetBool(flags.FlagNoProposal)
if options.GovProposal && !skipProposal {
return b.handleGovProposal(options, cmd, input, clientCtx, addressCodec, fd)
}
// set signer to signer field if empty
if addr := input.Get(fd).String(); addr == "" {
scalarType, ok := flag.GetScalarType(fd)
if ok {
// override address codec if validator or consensus address
switch scalarType {
case flag.ValidatorAddressStringScalarType:
addressCodec = b.Builder.ValidatorAddressCodec
case flag.ConsensusAddressStringScalarType:
addressCodec = b.Builder.ConsensusAddressCodec
}
}
signerFromFlag := clientCtx.GetFromAddress()
signer, err := addressCodec.BytesToString(signerFromFlag.Bytes())
if err != nil {
return fmt.Errorf("failed to set signer on message, got %v: %w", signerFromFlag, err)
}
input.Set(fd, protoreflect.ValueOfString(signer))
}
// AutoCLI uses protov2 messages, while the SDK only supports proto v1 messages.
// Here we use dynamicpb, to create a proto v1 compatible message.
// The SDK codec will handle protov2 -> protov1 (marshal)
msg := dynamicpb.NewMessage(input.Descriptor())
proto.Merge(msg, input.Interface())
return clienttx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
}
cmd, err := b.buildMethodCommandCommon(descriptor, options, execFunc)
if err != nil {
return nil, err
}
if b.AddTxConnFlags != nil {
b.AddTxConnFlags(cmd)
}
// silence usage only for inner txs & queries commands
if cmd != nil {
cmd.SilenceUsage = true
}
// set gov proposal flags if command is a gov proposal
if options.GovProposal {
govcli.AddGovPropFlagsToCmd(cmd)
cmd.Flags().Bool(flags.FlagNoProposal, false, "Skip gov proposal and submit a normal transaction")
}
return cmd, nil
}
// handleGovProposal sets the authority field of the message to the gov module address and creates a gov proposal.
func (b *Builder) handleGovProposal(
options *autocliv1.RpcCommandOptions,
cmd *cobra.Command,
input protoreflect.Message,
clientCtx client.Context,
addressCodec addresscodec.Codec,
fd protoreflect.FieldDescriptor,
) error {
govAuthority := authtypes.NewModuleAddress(govtypes.ModuleName)
authority, err := addressCodec.BytesToString(govAuthority.Bytes())
if err != nil {
return fmt.Errorf("failed to convert gov authority: %w", err)
}
input.Set(fd, protoreflect.ValueOfString(authority))
signerFromFlag := clientCtx.GetFromAddress()
signer, err := addressCodec.BytesToString(signerFromFlag.Bytes())
if err != nil {
return fmt.Errorf("failed to set signer on message, got %q: %w", signerFromFlag, err)
}
proposal, err := govcli.ReadGovPropCmdFlags(signer, cmd.Flags())
if err != nil {
return err
}
// AutoCLI uses protov2 messages, while the SDK only supports proto v1 messages.
// Here we use dynamicpb, to create a proto v1 compatible message.
// The SDK codec will handle protov2 -> protov1 (marshal)
msg := dynamicpb.NewMessage(input.Descriptor())
proto.Merge(msg, input.Interface())
if err := proposal.SetMsgs([]gogoproto.Message{msg}); err != nil {
return fmt.Errorf("failed to set msg in proposal %w", err)
}
return clienttx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), proposal)
}