code.vegaprotocol.io/vega@v0.79.0/cmd/vegawallet/commands/transaction_check.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 checkTransactionLong = cli.LongDesc(` 45 Check a transaction 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 checkTransactionExample = cli.Examples(` 58 # Check a transaction and send it to a registered network 59 {{.Software}} transaction check --network NETWORK --wallet WALLET --pubkey PUBKEY TRANSACTION 60 61 # Check a transaction and send it to a specific Vega node address 62 {{.Software}} transaction check --node-address ADDRESS --wallet WALLET --pubkey PUBKEY TRANSACTION 63 64 # Check a transaction with a log level set to debug 65 {{.Software}} transaction check --network NETWORK --wallet WALLET --pubkey PUBKEY --level debug TRANSACTION 66 67 # Check a transaction with a maximum of 10 retries 68 {{.Software}} transaction check --network NETWORK --wallet WALLET --pubkey PUBKEY --retries 10 TRANSACTION 69 70 # Check a transaction with a maximum request duration of 10 seconds 71 {{.Software}} transaction check --network NETWORK --wallet WALLET --pubkey PUBKEY --max-request-duration "10s" TRANSACTION 72 73 # Check a transaction and send it to a registered network without verifying network version compatibility 74 {{.Software}} transaction check --network NETWORK --wallet WALLET --pubkey PUBKEY --no-version-check TRANSACTION 75 `) 76 ) 77 78 type CheckTransactionHandler func(api.AdminCheckTransactionParams, string, *zap.Logger) (api.AdminCheckTransactionResult, error) 79 80 func NewCmdCheckTransaction(w io.Writer, rf *RootFlags) *cobra.Command { 81 handler := func(params api.AdminCheckTransactionParams, passphrase string, log *zap.Logger) (api.AdminCheckTransactionResult, 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.AdminCheckTransactionResult{}, 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.AdminCheckTransactionResult{}, 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.AdminCheckTransactionResult{}, errors.New(errDetails.Data) 102 } 103 104 checkTx := api.NewAdminCheckTransaction(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 := checkTx.Handle(ctx, params) 109 if errDetails != nil { 110 return api.AdminCheckTransactionResult{}, errors.New(errDetails.Data) 111 } 112 return rawResult.(api.AdminCheckTransactionResult), nil 113 } 114 115 return BuildCmdCheckTransaction(w, handler, rf) 116 } 117 118 func BuildCmdCheckTransaction(w io.Writer, handler CheckTransactionHandler, rf *RootFlags) *cobra.Command { 119 f := &CheckTransactionFlags{} 120 121 cmd := &cobra.Command{ 122 Use: "check", 123 Short: "Check a transaction and send it to a Vega node", 124 Long: checkTransactionLong, 125 Example: checkTransactionExample, 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 PrintCheckTransactionResponse(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 send the transaction to after it is checked", 163 ) 164 cmd.Flags().StringVar(&f.NodeAddress, 165 "node-address", 166 "", 167 "Vega node address to which the transaction is sent after it is checked", 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 CheckTransactionFlags 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 *CheckTransactionFlags) Validate() (api.AdminCheckTransactionParams, string, error) { 226 if len(f.Wallet) == 0 { 227 return api.AdminCheckTransactionParams{}, "", flags.MustBeSpecifiedError("wallet") 228 } 229 230 if len(f.LogLevel) == 0 { 231 return api.AdminCheckTransactionParams{}, "", flags.MustBeSpecifiedError("level") 232 } 233 if err := vgzap.EnsureIsSupportedLogLevel(f.LogLevel); err != nil { 234 return api.AdminCheckTransactionParams{}, "", err 235 } 236 237 if len(f.NodeAddress) == 0 && len(f.Network) == 0 { 238 return api.AdminCheckTransactionParams{}, "", flags.OneOfFlagsMustBeSpecifiedError("network", "node-address") 239 } 240 241 if len(f.NodeAddress) != 0 && len(f.Network) != 0 { 242 return api.AdminCheckTransactionParams{}, "", flags.MutuallyExclusiveError("network", "node-address") 243 } 244 245 if len(f.PubKey) == 0 { 246 return api.AdminCheckTransactionParams{}, "", flags.MustBeSpecifiedError("pubkey") 247 } 248 249 if len(f.RawTransaction) == 0 { 250 return api.AdminCheckTransactionParams{}, "", flags.ArgMustBeSpecifiedError("transaction") 251 } 252 253 passphrase, err := flags.GetPassphrase(f.PassphraseFile) 254 if err != nil { 255 return api.AdminCheckTransactionParams{}, "", 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.AdminCheckTransactionParams{}, "", fmt.Errorf("could not unmarshal transaction: %w", err) 264 } 265 266 params := api.AdminCheckTransactionParams{ 267 Wallet: f.Wallet, 268 PublicKey: f.PubKey, 269 Network: f.Network, 270 NodeAddress: f.NodeAddress, 271 Retries: f.Retries, 272 MaximumRequestDuration: f.MaximumRequestDuration, 273 Transaction: transaction, 274 } 275 276 return params, passphrase, nil 277 } 278 279 func PrintCheckTransactionResponse(w io.Writer, res api.AdminCheckTransactionResult, rf *RootFlags) { 280 p := printer.NewInteractivePrinter(w) 281 282 if rf.Output == flags.InteractiveOutput && version.IsUnreleased() { 283 str := p.String() 284 str.CrossMark().DangerText("You are running an unreleased version of the Vega wallet (").DangerText(coreversion.Get()).DangerText(").").NextLine() 285 str.Pad().DangerText("Use it at your own risk!").NextSection() 286 p.Print(str) 287 } 288 289 str := p.String() 290 defer p.Print(str) 291 str.CheckMark().SuccessText("Transaction checking successful").NextSection() 292 str.Text("Sent at:").NextLine().WarningText(res.SentAt.Format(time.ANSIC)).NextSection() 293 str.Text("Selected node:").NextLine().WarningText(res.Node.Host).NextLine() 294 }