code.vegaprotocol.io/vega@v0.79.0/cmd/vegawallet/commands/transaction_send.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package cmd 17 18 import ( 19 "context" 20 "encoding/json" 21 "errors" 22 "fmt" 23 "io" 24 "time" 25 26 "code.vegaprotocol.io/vega/cmd/vegawallet/commands/cli" 27 "code.vegaprotocol.io/vega/cmd/vegawallet/commands/flags" 28 "code.vegaprotocol.io/vega/cmd/vegawallet/commands/printer" 29 vgzap "code.vegaprotocol.io/vega/libs/zap" 30 "code.vegaprotocol.io/vega/paths" 31 coreversion "code.vegaprotocol.io/vega/version" 32 "code.vegaprotocol.io/vega/wallet/api" 33 walletnode "code.vegaprotocol.io/vega/wallet/api/node" 34 networkStore "code.vegaprotocol.io/vega/wallet/network/store/v1" 35 "code.vegaprotocol.io/vega/wallet/version" 36 "code.vegaprotocol.io/vega/wallet/wallets" 37 38 "github.com/spf13/cobra" 39 "go.uber.org/zap" 40 "go.uber.org/zap/zapcore" 41 ) 42 43 var ( 44 sendTransactionLong = cli.LongDesc(` 45 Send a transaction to a Vega node via the gRPC API. The transaction can be sent to 46 any node of a registered network or to a specific node address. 47 48 The transaction should be a Vega transaction formatted as a JSON payload, as follows: 49 50 '{"commandName": {"someProperty": "someValue"} }' 51 52 For vote submission, it will look like this: 53 54 '{"voteSubmission": {"proposalId": "some-id", "value": "VALUE_YES"}}' 55 `) 56 57 sendTransactionExample = cli.Examples(` 58 # Send a transaction to a registered network 59 {{.Software}} transaction send --network NETWORK --wallet WALLET --pubkey PUBKEY TRANSACTION 60 61 # Send a transaction to a specific Vega node address 62 {{.Software}} transaction send --node-address ADDRESS --wallet WALLET --pubkey PUBKEY TRANSACTION 63 64 # Send a transaction with a log level set to debug 65 {{.Software}} transaction send --network NETWORK --wallet WALLET --pubkey PUBKEY --level debug TRANSACTION 66 67 # Send a transaction with a maximum of 10 retries 68 {{.Software}} transaction send --network NETWORK --wallet WALLET --pubkey PUBKEY --retries 10 TRANSACTION 69 70 # Send a transaction with a maximum request duration of 10 seconds 71 {{.Software}} transaction send --network NETWORK --wallet WALLET --pubkey PUBKEY --max-request-duration "10s" TRANSACTION 72 73 # Send a transaction to a registered network without verifying network version compatibility 74 {{.Software}} transaction send --network NETWORK --wallet WALLET --pubkey PUBKEY --no-version-check TRANSACTION 75 `) 76 ) 77 78 type SendTransactionHandler func(api.AdminSendTransactionParams, string, *zap.Logger) (api.AdminSendTransactionResult, error) 79 80 func NewCmdSendTransaction(w io.Writer, rf *RootFlags) *cobra.Command { 81 handler := func(params api.AdminSendTransactionParams, passphrase string, log *zap.Logger) (api.AdminSendTransactionResult, error) { 82 ctx := context.Background() 83 84 vegaPaths := paths.New(rf.Home) 85 86 walletStore, err := wallets.InitialiseStore(rf.Home, false) 87 if err != nil { 88 return api.AdminSendTransactionResult{}, fmt.Errorf("couldn't initialise wallets store: %w", err) 89 } 90 defer walletStore.Close() 91 92 ns, err := networkStore.InitialiseStore(vegaPaths) 93 if err != nil { 94 return api.AdminSendTransactionResult{}, fmt.Errorf("couldn't initialise network store: %w", err) 95 } 96 97 if _, errDetails := api.NewAdminUnlockWallet(walletStore).Handle(ctx, api.AdminUnlockWalletParams{ 98 Wallet: params.Wallet, 99 Passphrase: passphrase, 100 }); errDetails != nil { 101 return api.AdminSendTransactionResult{}, errors.New(errDetails.Data) 102 } 103 104 sendTx := api.NewAdminSendTransaction(walletStore, ns, func(hosts []string, retries uint64, requestTTL time.Duration) (walletnode.Selector, error) { 105 return walletnode.BuildRoundRobinSelectorWithRetryingNodes(log, hosts, retries, requestTTL) 106 }) 107 108 rawResult, errDetails := sendTx.Handle(ctx, params) 109 if errDetails != nil { 110 return api.AdminSendTransactionResult{}, errors.New(errDetails.Data) 111 } 112 return rawResult.(api.AdminSendTransactionResult), nil 113 } 114 115 return BuildCmdSendTransaction(w, handler, rf) 116 } 117 118 func BuildCmdSendTransaction(w io.Writer, handler SendTransactionHandler, rf *RootFlags) *cobra.Command { 119 f := &SendTransactionFlags{} 120 121 cmd := &cobra.Command{ 122 Use: "send", 123 Short: "Send a transaction to a Vega node", 124 Long: sendTransactionLong, 125 Example: sendTransactionExample, 126 RunE: func(_ *cobra.Command, args []string) error { 127 if aLen := len(args); aLen == 0 { 128 return flags.ArgMustBeSpecifiedError("transaction") 129 } else if aLen > 1 { 130 return flags.TooManyArgsError("transaction") 131 } 132 f.RawTransaction = args[0] 133 134 req, pass, err := f.Validate() 135 if err != nil { 136 return err 137 } 138 139 log, err := buildCmdLogger(rf.Output, f.LogLevel) 140 if err != nil { 141 return fmt.Errorf("failed to build a logger: %w", err) 142 } 143 144 resp, err := handler(req, pass, log) 145 if err != nil { 146 return err 147 } 148 149 switch rf.Output { 150 case flags.InteractiveOutput: 151 PrintSendTransactionResponse(w, resp, rf) 152 case flags.JSONOutput: 153 return printer.FprintJSON(w, resp) 154 } 155 return nil 156 }, 157 } 158 159 cmd.Flags().StringVarP(&f.Network, 160 "network", "n", 161 "", 162 "Network to which the transaction is sent", 163 ) 164 cmd.Flags().StringVar(&f.NodeAddress, 165 "node-address", 166 "", 167 "Vega node address to which the transaction is sent", 168 ) 169 cmd.Flags().StringVarP(&f.Wallet, 170 "wallet", "w", 171 "", 172 "Wallet holding the public key", 173 ) 174 cmd.Flags().StringVarP(&f.PubKey, 175 "pubkey", "k", 176 "", 177 "Public key of the key pair to use for signing (hex-encoded)", 178 ) 179 cmd.Flags().StringVarP(&f.PassphraseFile, 180 "passphrase-file", "p", 181 "", 182 "Path to the file containing the wallet's passphrase", 183 ) 184 cmd.Flags().StringVar(&f.LogLevel, 185 "level", 186 zapcore.InfoLevel.String(), 187 fmt.Sprintf("Set the log level: %v", vgzap.SupportedLogLevels), 188 ) 189 cmd.Flags().Uint64Var(&f.Retries, 190 "retries", 191 defaultRequestRetryCount, 192 "Number of retries when contacting the Vega node", 193 ) 194 cmd.Flags().DurationVar(&f.MaximumRequestDuration, 195 "max-request-duration", 196 defaultMaxRequestDuration, 197 "Maximum duration the wallet will wait for a node to respond. Supported format: <number>+<time unit>. Valid time units are `s` and `m`.", 198 ) 199 cmd.Flags().BoolVar(&f.NoVersionCheck, 200 "no-version-check", 201 false, 202 "Do not check for network version compatibility", 203 ) 204 205 autoCompleteNetwork(cmd, rf.Home) 206 autoCompleteWallet(cmd, rf.Home, "wallet") 207 autoCompleteLogLevel(cmd) 208 209 return cmd 210 } 211 212 type SendTransactionFlags struct { 213 Network string 214 NodeAddress string 215 Wallet string 216 PubKey string 217 PassphraseFile string 218 Retries uint64 219 LogLevel string 220 RawTransaction string 221 NoVersionCheck bool 222 MaximumRequestDuration time.Duration 223 } 224 225 func (f *SendTransactionFlags) Validate() (api.AdminSendTransactionParams, string, error) { 226 if len(f.Wallet) == 0 { 227 return api.AdminSendTransactionParams{}, "", flags.MustBeSpecifiedError("wallet") 228 } 229 230 if len(f.LogLevel) == 0 { 231 return api.AdminSendTransactionParams{}, "", flags.MustBeSpecifiedError("level") 232 } 233 if err := vgzap.EnsureIsSupportedLogLevel(f.LogLevel); err != nil { 234 return api.AdminSendTransactionParams{}, "", err 235 } 236 237 if len(f.NodeAddress) == 0 && len(f.Network) == 0 { 238 return api.AdminSendTransactionParams{}, "", flags.OneOfFlagsMustBeSpecifiedError("network", "node-address") 239 } 240 241 if len(f.NodeAddress) != 0 && len(f.Network) != 0 { 242 return api.AdminSendTransactionParams{}, "", flags.MutuallyExclusiveError("network", "node-address") 243 } 244 245 if len(f.PubKey) == 0 { 246 return api.AdminSendTransactionParams{}, "", flags.MustBeSpecifiedError("pubkey") 247 } 248 249 if len(f.RawTransaction) == 0 { 250 return api.AdminSendTransactionParams{}, "", flags.ArgMustBeSpecifiedError("transaction") 251 } 252 253 passphrase, err := flags.GetPassphrase(f.PassphraseFile) 254 if err != nil { 255 return api.AdminSendTransactionParams{}, "", err 256 } 257 258 // Encode transaction into a nested structure; this is a bit nasty but mirroring what happens 259 // when our json-rpc library parses a request. There's an issue (6983#) to make the use 260 // json.RawMessage instead. 261 transaction := make(map[string]any) 262 if err := json.Unmarshal([]byte(f.RawTransaction), &transaction); err != nil { 263 return api.AdminSendTransactionParams{}, "", fmt.Errorf("couldn't unmarshal transaction: %w", err) 264 } 265 266 params := api.AdminSendTransactionParams{ 267 Wallet: f.Wallet, 268 PublicKey: f.PubKey, 269 Network: f.Network, 270 NodeAddress: f.NodeAddress, 271 Retries: f.Retries, 272 Transaction: transaction, 273 SendingMode: "TYPE_ASYNC", 274 MaximumRequestDuration: f.MaximumRequestDuration, 275 } 276 277 return params, passphrase, nil 278 } 279 280 func PrintSendTransactionResponse(w io.Writer, res api.AdminSendTransactionResult, rf *RootFlags) { 281 p := printer.NewInteractivePrinter(w) 282 283 if rf.Output == flags.InteractiveOutput && version.IsUnreleased() { 284 str := p.String() 285 str.CrossMark().DangerText("You are running an unreleased version of the Vega wallet (").DangerText(coreversion.Get()).DangerText(").").NextLine() 286 str.Pad().DangerText("Use it at your own risk!").NextSection() 287 p.Print(str) 288 } 289 290 str := p.String() 291 defer p.Print(str) 292 str.CheckMark().SuccessText("Transaction sending successful").NextSection() 293 str.Text("Transaction Hash:").NextLine().WarningText(res.TransactionHash).NextSection() 294 str.Text("Sent at:").NextLine().WarningText(res.SentAt.Format(time.ANSIC)).NextSection() 295 str.Text("Selected node:").NextLine().WarningText(res.Node.Host).NextLine() 296 }