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 <ekt@morphbits.io>
This commit is contained in:
Ekaterina Pavlova 2023-12-04 17:02:44 +03:00
parent f93e2fbba4
commit d7cab3b82c
9 changed files with 119 additions and 150 deletions

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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