code.vegaprotocol.io/vega@v0.79.0/cmd/vegawallet/commands/raw_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/base64" 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 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 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 36 "github.com/golang/protobuf/proto" 37 "github.com/spf13/cobra" 38 "go.uber.org/zap" 39 "go.uber.org/zap/zapcore" 40 ) 41 42 var ( 43 sendTxLong = cli.LongDesc(` 44 Send a signed 'raw' transaction to a Vega node via the gRPC API. The command can be sent to 45 any node of a registered network or to a specific node address. 46 47 The transaction should be base64-encoded. 48 `) 49 50 sendTxExample = cli.Examples(` 51 # Send a transaction to a registered network 52 {{.Software}} raw_transaction send --network NETWORK BASE64_TRANSACTION 53 54 # Send a transaction to a specific Vega node address 55 {{.Software}} raw_transaction send --node-address ADDRESS BASE64_TRANSACTION 56 57 # Send a transaction with a log level set to debug 58 {{.Software}} raw_transaction send --network NETWORK --level debug BASE64_TRANSACTION 59 60 # Send a transaction with a maximum of 10 retries 61 {{.Software}} raw_transaction send --network NETWORK --retries 10 BASE64_TRANSACTION 62 63 # Send a transaction with a maximum request duration of 10 seconds 64 {{.Software}} raw_transaction send --network NETWORK --max-request-duration "10s" BASE64_TRANSACTION 65 66 # Send a transaction without verifying network version compatibility 67 {{.Software}} raw_transaction send --network NETWORK --retries 10 BASE64_TRANSACTION --no-version-check 68 `) 69 ) 70 71 type SendRawTransactionHandler func(api.AdminSendRawTransactionParams, *zap.Logger) (api.AdminSendRawTransactionResult, error) 72 73 func NewCmdRawTransactionSend(w io.Writer, rf *RootFlags) *cobra.Command { 74 h := func(params api.AdminSendRawTransactionParams, log *zap.Logger) (api.AdminSendRawTransactionResult, error) { 75 vegaPaths := paths.New(rf.Home) 76 77 netStore, err := networkStore.InitialiseStore(vegaPaths) 78 if err != nil { 79 return api.AdminSendRawTransactionResult{}, fmt.Errorf("could not initialise network store: %w", err) 80 } 81 82 sendTransaction := api.NewAdminSendRawTransaction(netStore, func(hosts []string, retries uint64, requestTTL time.Duration) (walletnode.Selector, error) { 83 return walletnode.BuildRoundRobinSelectorWithRetryingNodes(log, hosts, retries, requestTTL) 84 }) 85 rawResult, errorDetails := sendTransaction.Handle(context.Background(), params) 86 if errorDetails != nil { 87 return api.AdminSendRawTransactionResult{}, errors.New(errorDetails.Data) 88 } 89 return rawResult.(api.AdminSendRawTransactionResult), nil 90 } 91 return BuildCmdRawTransactionSend(w, h, rf) 92 } 93 94 func BuildCmdRawTransactionSend(w io.Writer, handler SendRawTransactionHandler, rf *RootFlags) *cobra.Command { 95 f := &SendRawTransactionFlags{} 96 97 cmd := &cobra.Command{ 98 Use: "send", 99 Short: "Send a raw transaction to a Vega node", 100 Long: sendTxLong, 101 Example: sendTxExample, 102 RunE: func(_ *cobra.Command, args []string) error { 103 if aLen := len(args); aLen == 0 { 104 return flags.ArgMustBeSpecifiedError("transaction") 105 } else if aLen > 1 { 106 return flags.TooManyArgsError("transaction") 107 } 108 f.RawTx = args[0] 109 110 req, err := f.Validate() 111 if err != nil { 112 return err 113 } 114 115 log, err := buildCmdLogger(rf.Output, f.LogLevel) 116 if err != nil { 117 return fmt.Errorf("could not initialise logger: %w", err) 118 } 119 defer vgzap.Sync(log) 120 121 resp, err := handler(req, log) 122 if err != nil { 123 return err 124 } 125 126 switch rf.Output { 127 case flags.InteractiveOutput: 128 PrintTXSendResponse(w, resp) 129 case flags.JSONOutput: 130 return printer.FprintJSON(w, resp) 131 } 132 133 return nil 134 }, 135 } 136 137 cmd.Flags().StringVarP(&f.Network, 138 "network", "n", 139 "", 140 "Network to which the command is sent", 141 ) 142 cmd.Flags().StringVar(&f.NodeAddress, 143 "node-address", 144 "", 145 "Vega node address to which the command is sent", 146 ) 147 cmd.Flags().StringVar(&f.LogLevel, 148 "level", 149 zapcore.InfoLevel.String(), 150 fmt.Sprintf("Set the log level: %v", vgzap.SupportedLogLevels), 151 ) 152 cmd.Flags().Uint64Var(&f.Retries, 153 "retries", 154 defaultRequestRetryCount, 155 "Number of retries when contacting the Vega node", 156 ) 157 cmd.Flags().DurationVar(&f.MaximumRequestDuration, 158 "max-request-duration", 159 defaultMaxRequestDuration, 160 "Maximum duration the wallet will wait for a node to respond. Supported format: <number>+<time unit>. Valid time units are `s` and `m`.", 161 ) 162 cmd.Flags().BoolVar(&f.NoVersionCheck, 163 "no-version-check", 164 false, 165 "Do not check for network version compatibility", 166 ) 167 168 autoCompleteNetwork(cmd, rf.Home) 169 autoCompleteLogLevel(cmd) 170 return cmd 171 } 172 173 type SendRawTransactionFlags struct { 174 Network string 175 NodeAddress string 176 Retries uint64 177 LogLevel string 178 RawTx string 179 NoVersionCheck bool 180 MaximumRequestDuration time.Duration 181 } 182 183 func (f *SendRawTransactionFlags) Validate() (api.AdminSendRawTransactionParams, error) { 184 req := api.AdminSendRawTransactionParams{ 185 Retries: f.Retries, 186 MaximumRequestDuration: f.MaximumRequestDuration, 187 } 188 189 if len(f.LogLevel) == 0 { 190 return api.AdminSendRawTransactionParams{}, flags.MustBeSpecifiedError("level") 191 } 192 if err := vgzap.EnsureIsSupportedLogLevel(f.LogLevel); err != nil { 193 return api.AdminSendRawTransactionParams{}, err 194 } 195 196 if len(f.NodeAddress) == 0 && len(f.Network) == 0 { 197 return api.AdminSendRawTransactionParams{}, flags.OneOfFlagsMustBeSpecifiedError("network", "node-address") 198 } 199 if len(f.NodeAddress) != 0 && len(f.Network) != 0 { 200 return api.AdminSendRawTransactionParams{}, flags.MutuallyExclusiveError("network", "node-address") 201 } 202 req.NodeAddress = f.NodeAddress 203 req.Network = f.Network 204 req.SendingMode = "TYPE_ASYNC" 205 206 if len(f.RawTx) == 0 { 207 return api.AdminSendRawTransactionParams{}, flags.ArgMustBeSpecifiedError("transaction") 208 } 209 decodedTx, err := base64.StdEncoding.DecodeString(f.RawTx) 210 if err != nil { 211 return api.AdminSendRawTransactionParams{}, flags.MustBase64EncodedError("transaction") 212 } 213 tx := &commandspb.Transaction{} 214 if err := proto.Unmarshal(decodedTx, tx); err != nil { 215 return api.AdminSendRawTransactionParams{}, fmt.Errorf("could not unmarshal transaction: %w", err) 216 } 217 req.EncodedTransaction = f.RawTx 218 219 return req, nil 220 } 221 222 func PrintTXSendResponse(w io.Writer, res api.AdminSendRawTransactionResult) { 223 p := printer.NewInteractivePrinter(w) 224 225 str := p.String() 226 defer p.Print(str) 227 str.CheckMark().SuccessText("Transaction successfully sent").NextSection() 228 str.Text("Transaction Hash:").NextLine().WarningText(res.TransactionHash).NextSection() 229 str.Text("Sent at:").NextLine().WarningText(res.SentAt.Format(time.ANSIC)).NextSection() 230 str.Text("Selected node:").NextLine().WarningText(res.Node.Host).NextLine() 231 }