From d7cab3b82c8e5e551b7e558a791b53f06ccc7429 Mon Sep 17 00:00:00 2001 From: Ekaterina Pavlova Date: Mon, 4 Dec 2023 17:02:44 +0300 Subject: [PATCH] cli: move wallet-related flags handling to options package Move GetAccFromContext, GetUnlockedAccount, ReadWalletConfig handling into options package to reuse this code from all CLI handlers. getDecryptedAccount is replaced by GetUnlockedAccount. Signed-off-by: Ekaterina Pavlova --- cli/options/options.go | 98 +++++++++++++++++++++++++++++ cli/smartcontract/manifest.go | 3 +- cli/smartcontract/smart_contract.go | 90 +++----------------------- cli/util/cancel.go | 3 +- cli/wallet/legacy.go | 3 +- cli/wallet/multisig.go | 9 +-- cli/wallet/nep17.go | 4 +- cli/wallet/validator.go | 32 +--------- cli/wallet/wallet.go | 27 +------- 9 files changed, 119 insertions(+), 150 deletions(-) diff --git a/cli/options/options.go b/cli/options/options.go index de7e5e24d..61e13e353 100644 --- a/cli/options/options.go +++ b/cli/options/options.go @@ -11,18 +11,24 @@ import ( "os" "runtime" "strconv" + "strings" "time" + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/urfave/cli" "go.uber.org/zap" "go.uber.org/zap/zapcore" + "gopkg.in/yaml.v3" ) // DefaultTimeout is the default timeout used for RPC requests. @@ -97,6 +103,8 @@ var Debug = cli.BoolFlag{ var errNoEndpoint = errors.New("no RPC endpoint specified, use option '--" + RPCEndpointFlag + "' or '-r'") var errInvalidHistoric = errors.New("invalid 'historic' parameter, neither a block number, nor a block/state hash") +var errNoWallet = errors.New("no wallet parameter found, specify it with the '--wallet' or '-w' flag or specify wallet config file with the '--wallet-config' flag") +var errConflictingWalletFlags = errors.New("--wallet flag conflicts with --wallet-config flag, please, provide one of them to specify wallet location") // GetNetwork examines Context's flags and returns the appropriate network. It // defaults to PrivNet if no flags are given. @@ -279,3 +287,93 @@ func HandleLoggingParams(debug bool, cfg config.ApplicationConfiguration) (*zap. log, err := cc.Build() return log, &cc.Level, _winfileSinkCloser, err } + +// GetAccFromContext returns account and wallet from context. If address is not set, default address is used. +func GetAccFromContext(ctx *cli.Context) (*wallet.Account, *wallet.Wallet, error) { + var addr util.Uint160 + + wPath := ctx.String("wallet") + walletConfigPath := ctx.String("wallet-config") + if len(wPath) != 0 && len(walletConfigPath) != 0 { + return nil, nil, errConflictingWalletFlags + } + if len(wPath) == 0 && len(walletConfigPath) == 0 { + return nil, nil, errNoWallet + } + var pass *string + if len(walletConfigPath) != 0 { + cfg, err := ReadWalletConfig(walletConfigPath) + if err != nil { + return nil, nil, err + } + wPath = cfg.Path + pass = &cfg.Password + } + + wall, err := wallet.NewWalletFromFile(wPath) + if err != nil { + return nil, nil, err + } + addrFlag := ctx.Generic("address").(*flags.Address) + if addrFlag.IsSet { + addr = addrFlag.Uint160() + } else { + addr = wall.GetChangeAddress() + if addr.Equals(util.Uint160{}) { + return nil, wall, errors.New("can't get default address") + } + } + + acc, err := GetUnlockedAccount(wall, addr, pass) + return acc, wall, err +} + +// GetUnlockedAccount returns account from wallet, address and uses pass to unlock specified account if given. +// If the password is not given, then it is requested from user. +func GetUnlockedAccount(wall *wallet.Wallet, addr util.Uint160, pass *string) (*wallet.Account, error) { + acc := wall.GetAccount(addr) + if acc == nil { + return nil, fmt.Errorf("wallet contains no account for '%s'", address.Uint160ToString(addr)) + } + + if acc.CanSign() || acc.EncryptedWIF == "" { + return acc, nil + } + + if pass == nil { + rawPass, err := input.ReadPassword( + fmt.Sprintf("Enter account %s password > ", address.Uint160ToString(addr))) + if err != nil { + return nil, fmt.Errorf("Error reading password: %w", err) + } + trimmed := strings.TrimRight(string(rawPass), "\n") + pass = &trimmed + } + err := acc.Decrypt(*pass, wall.Scrypt) + if err != nil { + return nil, err + } + return acc, nil +} + +// ReadWalletConfig reads wallet config from the given path. +func ReadWalletConfig(configPath string) (*config.Wallet, error) { + file, err := os.Open(configPath) + if err != nil { + return nil, err + } + defer file.Close() + + configData, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("unable to read wallet config: %w", err) + } + + cfg := &config.Wallet{} + + err = yaml.Unmarshal(configData, &cfg) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal wallet config YAML: %w", err) + } + return cfg, nil +} diff --git a/cli/smartcontract/manifest.go b/cli/smartcontract/manifest.go index 6c4e623a9..e940b333b 100644 --- a/cli/smartcontract/manifest.go +++ b/cli/smartcontract/manifest.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" @@ -37,7 +38,7 @@ func manifestAddGroup(ctx *cli.Context) error { h := state.CreateContractHash(sender, nf.Checksum, m.Name) - gAcc, w, err := GetAccFromContext(ctx) + gAcc, w, err := options.GetAccFromContext(ctx) if err != nil { return cli.NewExitError(fmt.Errorf("can't get account to sign group with: %w", err), 1) } diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index dc811fb1b..0dcb81b7e 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -11,14 +11,11 @@ import ( "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/flags" - "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/txctx" - cliwallet "github.com/nspcc-dev/neo-go/cli/wallet" "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" @@ -39,16 +36,14 @@ import ( const addressFlagName = "address, a" var ( - errNoInput = errors.New("no input file was found, specify an input file with the '--in or -i' flag") - errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag") - errNoManifestFile = errors.New("no manifest file was found, specify manifest file with '--manifest' or '-m' flag") - errNoMethod = errors.New("no method specified for function invocation command") - errNoWallet = errors.New("no wallet parameter found, specify it with the '--wallet' or '-w' flag or specify wallet config file with the '--wallet-config' flag") - errConflictingWalletFlags = errors.New("--wallet flag conflicts with --wallet-config flag, please, provide one of them to specify wallet location") - errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument") - errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag") - errFileExist = errors.New("A file with given smart-contract name already exists") - addressFlag = flags.AddressFlag{ + errNoInput = errors.New("no input file was found, specify an input file with the '--in or -i' flag") + errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag") + errNoManifestFile = errors.New("no manifest file was found, specify manifest file with '--manifest' or '-m' flag") + errNoMethod = errors.New("no method specified for function invocation command") + errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument") + errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag") + errFileExist = errors.New("A file with given smart-contract name already exists") + addressFlag = flags.AddressFlag{ Name: addressFlagName, Usage: "address to use as transaction signee (and gas source)", } @@ -570,7 +565,7 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { w *wallet.Wallet ) if signAndPush { - acc, w, err = GetAccFromContext(ctx) + acc, w, err = options.GetAccFromContext(ctx) if err != nil { return cli.NewExitError(err, 1) } @@ -746,71 +741,6 @@ func inspect(ctx *cli.Context) error { return nil } -// GetAccFromContext returns account and wallet from context. If address is not set, default address is used. -func GetAccFromContext(ctx *cli.Context) (*wallet.Account, *wallet.Wallet, error) { - var addr util.Uint160 - - wPath := ctx.String("wallet") - walletConfigPath := ctx.String("wallet-config") - if len(wPath) != 0 && len(walletConfigPath) != 0 { - return nil, nil, errConflictingWalletFlags - } - if len(wPath) == 0 && len(walletConfigPath) == 0 { - return nil, nil, errNoWallet - } - var pass *string - if len(walletConfigPath) != 0 { - cfg, err := cliwallet.ReadWalletConfig(walletConfigPath) - if err != nil { - return nil, nil, err - } - wPath = cfg.Path - pass = &cfg.Password - } - - wall, err := wallet.NewWalletFromFile(wPath) - if err != nil { - return nil, nil, err - } - addrFlag := ctx.Generic("address").(*flags.Address) - if addrFlag.IsSet { - addr = addrFlag.Uint160() - } else { - addr = wall.GetChangeAddress() - } - - acc, err := GetUnlockedAccount(wall, addr, pass) - return acc, wall, err -} - -// GetUnlockedAccount returns account from wallet, address and uses pass to unlock specified account if given. -// If the password is not given, then it is requested from user. -func GetUnlockedAccount(wall *wallet.Wallet, addr util.Uint160, pass *string) (*wallet.Account, error) { - acc := wall.GetAccount(addr) - if acc == nil { - return nil, fmt.Errorf("wallet contains no account for '%s'", address.Uint160ToString(addr)) - } - - if acc.CanSign() { - return acc, nil - } - - if pass == nil { - rawPass, err := input.ReadPassword( - fmt.Sprintf("Enter account %s password > ", address.Uint160ToString(addr))) - if err != nil { - return nil, fmt.Errorf("Error reading password: %w", err) - } - trimmed := strings.TrimRight(string(rawPass), "\n") - pass = &trimmed - } - err := acc.Decrypt(*pass, wall.Scrypt) - if err != nil { - return nil, err - } - return acc, nil -} - // contractDeploy deploys contract. func contractDeploy(ctx *cli.Context) error { nefFile, f, err := readNEFFile(ctx.String("in")) @@ -836,7 +766,7 @@ func contractDeploy(ctx *cli.Context) error { appCallParams = append(appCallParams, data[0]) } - acc, w, err := GetAccFromContext(ctx) + acc, w, err := options.GetAccFromContext(ctx) if err != nil { return cli.NewExitError(fmt.Errorf("can't get sender address: %w", err), 1) } diff --git a/cli/util/cancel.go b/cli/util/cancel.go index 4dd5e3ef8..ccbe7e63f 100644 --- a/cli/util/cancel.go +++ b/cli/util/cancel.go @@ -6,7 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/options" - "github.com/nspcc-dev/neo-go/cli/smartcontract" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" @@ -40,7 +39,7 @@ func cancelTx(ctx *cli.Context) error { if mainTx != nil && !mainTx.Blockhash.Equals(util.Uint256{}) { return cli.NewExitError(fmt.Errorf("transaction %s is already accepted at block %s", txHash, mainTx.Blockhash.StringLE()), 1) } - acc, w, err := smartcontract.GetAccFromContext(ctx) + acc, w, err = options.GetAccFromContext(ctx) if err != nil { return cli.NewExitError(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1) } diff --git a/cli/wallet/legacy.go b/cli/wallet/legacy.go index c490dbf72..ec7dc4e60 100644 --- a/cli/wallet/legacy.go +++ b/cli/wallet/legacy.go @@ -8,6 +8,7 @@ import ( "errors" "os" + "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -46,7 +47,7 @@ func newWalletV2FromFile(path string, configPath string) (*walletV2, *string, er } var pass *string if len(configPath) != 0 { - cfg, err := ReadWalletConfig(configPath) + cfg, err := options.ReadWalletConfig(configPath) if err != nil { return nil, nil, err } diff --git a/cli/wallet/multisig.go b/cli/wallet/multisig.go index c69663da4..337064e77 100644 --- a/cli/wallet/multisig.go +++ b/cli/wallet/multisig.go @@ -21,11 +21,6 @@ func signStoredTransaction(ctx *cli.Context) error { if err := cmdargs.EnsureNone(ctx); err != nil { return err } - wall, pass, err := readWallet(ctx) - if err != nil { - return cli.NewExitError(err, 1) - } - defer wall.Close() pc, err := paramcontext.Read(ctx.String("in")) if err != nil { @@ -35,9 +30,7 @@ func signStoredTransaction(ctx *cli.Context) error { if !addrFlag.IsSet { return cli.NewExitError("address was not provided", 1) } - - var ch = addrFlag.Uint160() - acc, err := getDecryptedAccount(wall, ch, pass) + acc, _, err := options.GetAccFromContext(ctx) if err != nil { return cli.NewExitError(err, 1) } diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index bb3173792..bb013d264 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -522,7 +522,7 @@ func multiTransferNEP17(ctx *cli.Context) error { if err != nil { return cli.NewExitError(err, 1) } - acc, err := getDecryptedAccount(wall, from, pass) + acc, err := options.GetUnlockedAccount(wall, from, pass) if err != nil { return cli.NewExitError(err, 1) } @@ -618,7 +618,7 @@ func transferNEP(ctx *cli.Context, standard string) error { if err != nil { return cli.NewExitError(err, 1) } - acc, err := getDecryptedAccount(wall, from, pass) + acc, err := options.GetUnlockedAccount(wall, from, pass) if err != nil { return cli.NewExitError(err, 1) } diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index 1194fa371..5eaa2b38e 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -5,7 +5,6 @@ import ( "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/flags" - "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/txctx" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -111,7 +110,7 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w return cli.NewExitError("address was not provided", 1) } addr := addrFlag.Uint160() - acc, err := getDecryptedAccount(wall, addr, pass) + acc, err := options.GetUnlockedAccount(wall, addr, pass) if err != nil { return cli.NewExitError(err, 1) } @@ -153,32 +152,3 @@ func handleVote(ctx *cli.Context) error { return contract.VoteUnsigned(addr, pub) }) } - -// getDecryptedAccount tries to get and unlock the specified account if it has a -// key inside (otherwise it's returned as is, without an ability to sign). If -// password is nil, it will be requested via terminal. -func getDecryptedAccount(wall *wallet.Wallet, addr util.Uint160, password *string) (*wallet.Account, error) { - acc := wall.GetAccount(addr) - if acc == nil { - return nil, fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addr)) - } - - // No private key available, nothing to decrypt, but it's still a useful account for many purposes. - if acc.EncryptedWIF == "" { - return acc, nil - } - - if password == nil { - pass, err := input.ReadPassword(EnterPasswordPrompt) - if err != nil { - fmt.Println("Error reading password", err) - return nil, err - } - password = &pass - } - err := acc.Decrypt(*password, wall.Scrypt) - if err != nil { - return nil, err - } - return acc, nil -} diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index f5051efb8..4a10fc96a 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -15,7 +15,6 @@ import ( "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/txctx" - "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -26,7 +25,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/urfave/cli" - "gopkg.in/yaml.v3" ) const ( @@ -822,7 +820,7 @@ func createWallet(ctx *cli.Context) error { } var pass *string if len(configPath) != 0 { - cfg, err := ReadWalletConfig(configPath) + cfg, err := options.ReadWalletConfig(configPath) if err != nil { return cli.NewExitError(err, 1) } @@ -946,7 +944,7 @@ func getWalletPathAndPass(ctx *cli.Context, canUseWalletConfig bool) (string, *s } var pass *string if len(configPath) != 0 { - cfg, err := ReadWalletConfig(configPath) + cfg, err := options.ReadWalletConfig(configPath) if err != nil { return "", nil, err } @@ -956,27 +954,6 @@ func getWalletPathAndPass(ctx *cli.Context, canUseWalletConfig bool) (string, *s return path, pass, nil } -func ReadWalletConfig(configPath string) (*config.Wallet, error) { - file, err := os.Open(configPath) - if err != nil { - return nil, err - } - defer file.Close() - - configData, err := os.ReadFile(configPath) - if err != nil { - return nil, fmt.Errorf("unable to read wallet config: %w", err) - } - - cfg := &config.Wallet{} - - err = yaml.Unmarshal(configData, &cfg) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal wallet config YAML: %w", err) - } - return cfg, nil -} - func newAccountFromWIF(w io.Writer, wif string, scrypt keys.ScryptParams, label *string, pass *string) (*wallet.Account, error) { var ( phrase, name string