Merge pull request #3235 from nspcc-dev/actor_refactor

cli: move actor handling
This commit is contained in:
Anna Shaleva 2023-12-11 12:19:33 +05:00 committed by GitHub
commit 6f3a219d67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 191 additions and 226 deletions

View file

@ -11,18 +11,25 @@ import (
"os" "os"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"time" "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"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "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/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/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"gopkg.in/yaml.v3"
) )
// DefaultTimeout is the default timeout used for RPC requests. // DefaultTimeout is the default timeout used for RPC requests.
@ -97,6 +104,8 @@ var Debug = cli.BoolFlag{
var errNoEndpoint = errors.New("no RPC endpoint specified, use option '--" + RPCEndpointFlag + "' or '-r'") 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 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 // GetNetwork examines Context's flags and returns the appropriate network. It
// defaults to PrivNet if no flags are given. // defaults to PrivNet if no flags are given.
@ -279,3 +288,108 @@ func HandleLoggingParams(debug bool, cfg config.ApplicationConfiguration) (*zap.
log, err := cc.Build() log, err := cc.Build()
return log, &cc.Level, _winfileSinkCloser, err return log, &cc.Level, _winfileSinkCloser, err
} }
// GetRPCWithActor returns an RPC client instance and Actor instance for the given context.
func GetRPCWithActor(gctx context.Context, ctx *cli.Context, signers []actor.SignerAccount) (*rpcclient.Client, *actor.Actor, cli.ExitCoder) {
c, err := GetRPCClient(gctx, ctx)
if err != nil {
return nil, nil, err
}
a, actorErr := actor.New(c, signers)
if actorErr != nil {
c.Close()
return nil, nil, cli.NewExitError(fmt.Errorf("failed to create Actor: %w", actorErr), 1)
}
return c, a, 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 := 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/cmdargs"
"github.com/nspcc-dev/neo-go/cli/flags" "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/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "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) h := state.CreateContractHash(sender, nf.Checksum, m.Name)
gAcc, w, err := GetAccFromContext(ctx) gAcc, w, err := options.GetAccFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't get account to sign group with: %w", err), 1) 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/cmdargs"
"github.com/nspcc-dev/neo-go/cli/flags" "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/options"
"github.com/nspcc-dev/neo-go/cli/txctx" "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/compiler"
"github.com/nspcc-dev/neo-go/pkg/core/state" "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/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/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
@ -39,16 +36,14 @@ import (
const addressFlagName = "address, a" const addressFlagName = "address, a"
var ( var (
errNoInput = errors.New("no input file was found, specify an input file with the '--in or -i' flag") 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") 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") 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") 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") errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument")
errConflictingWalletFlags = errors.New("--wallet flag conflicts with --wallet-config flag, please, provide one of them to specify wallet location") errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag")
errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument") errFileExist = errors.New("A file with given smart-contract name already exists")
errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag") addressFlag = flags.AddressFlag{
errFileExist = errors.New("A file with given smart-contract name already exists")
addressFlag = flags.AddressFlag{
Name: addressFlagName, Name: addressFlagName,
Usage: "address to use as transaction signee (and gas source)", 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 w *wallet.Wallet
) )
if signAndPush { if signAndPush {
acc, w, err = GetAccFromContext(ctx) acc, w, err = options.GetAccFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -597,19 +592,14 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return err
}
if signAndPush { if signAndPush {
act, err = actor.New(c, signersAccounts) _, act, err = options.GetRPCWithActor(gctx, ctx, signersAccounts)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1) return err
} }
inv = &act.Invoker inv = &act.Invoker
} else { } else {
inv, err = options.GetInvoker(c, ctx, cosigners) _, inv, err = options.GetRPCWithInvoker(gctx, ctx, cosigners)
if err != nil { if err != nil {
return err return err
} }
@ -746,71 +736,6 @@ func inspect(ctx *cli.Context) error {
return nil 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. // contractDeploy deploys contract.
func contractDeploy(ctx *cli.Context) error { func contractDeploy(ctx *cli.Context) error {
nefFile, f, err := readNEFFile(ctx.String("in")) nefFile, f, err := readNEFFile(ctx.String("in"))
@ -836,7 +761,7 @@ func contractDeploy(ctx *cli.Context) error {
appCallParams = append(appCallParams, data[0]) appCallParams = append(appCallParams, data[0])
} }
acc, w, err := GetAccFromContext(ctx) acc, w, err := options.GetAccFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't get sender address: %w", err), 1) return cli.NewExitError(fmt.Errorf("can't get sender address: %w", err), 1)
} }

View file

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/nspcc-dev/neo-go/cli/cmdargs"
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/options" "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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result" "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/actor"
@ -31,30 +31,30 @@ func cancelTx(ctx *cli.Context) error {
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
c, err := options.GetRPCClient(gctx, ctx) acc, w, err := options.GetAccFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create RPC client: %w", err), 1) return cli.NewExitError(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1)
}
defer w.Close()
signers, err := cmdargs.GetSignersAccounts(acc, w, nil, transaction.CalledByEntry)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
}
c, a, exitErr := options.GetRPCWithActor(gctx, ctx, signers)
if exitErr != nil {
return exitErr
} }
mainTx, _ := c.GetRawTransactionVerbose(txHash) mainTx, _ := c.GetRawTransactionVerbose(txHash)
if mainTx != nil && !mainTx.Blockhash.Equals(util.Uint256{}) { 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) return cli.NewExitError(fmt.Errorf("transaction %s is already accepted at block %s", txHash, mainTx.Blockhash.StringLE()), 1)
} }
acc, w, err := smartcontract.GetAccFromContext(ctx)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1)
}
defer w.Close()
if mainTx != nil && !mainTx.HasSigner(acc.ScriptHash()) { if mainTx != nil && !mainTx.HasSigner(acc.ScriptHash()) {
return cli.NewExitError(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1) return cli.NewExitError(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1)
} }
a, err := actor.NewSimple(c, acc)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create Actor: %w", err), 1)
}
resHash, _, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error { resHash, _, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error {
err := actor.DefaultCheckerModifier(r, t) err := actor.DefaultCheckerModifier(r, t)
if err != nil { if err != nil {

View file

@ -8,6 +8,7 @@ import (
"errors" "errors"
"os" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -46,7 +47,7 @@ func newWalletV2FromFile(path string, configPath string) (*walletV2, *string, er
} }
var pass *string var pass *string
if len(configPath) != 0 { if len(configPath) != 0 {
cfg, err := ReadWalletConfig(configPath) cfg, err := options.ReadWalletConfig(configPath)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View file

@ -21,11 +21,6 @@ func signStoredTransaction(ctx *cli.Context) error {
if err := cmdargs.EnsureNone(ctx); err != nil { if err := cmdargs.EnsureNone(ctx); err != nil {
return err 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")) pc, err := paramcontext.Read(ctx.String("in"))
if err != nil { if err != nil {
@ -35,9 +30,7 @@ func signStoredTransaction(ctx *cli.Context) error {
if !addrFlag.IsSet { if !addrFlag.IsSet {
return cli.NewExitError("address was not provided", 1) return cli.NewExitError("address was not provided", 1)
} }
acc, _, err := options.GetAccFromContext(ctx)
var ch = addrFlag.Uint160()
acc, err := getDecryptedAccount(wall, ch, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -47,20 +40,13 @@ func signStoredTransaction(ctx *cli.Context) error {
return cli.NewExitError("verifiable item is not a transaction", 1) return cli.NewExitError("verifiable item is not a transaction", 1)
} }
signerFound := false if !tx.HasSigner(acc.ScriptHash()) {
for i := range tx.Signers {
if tx.Signers[i].Account == ch {
signerFound = true
break
}
}
if !signerFound {
return cli.NewExitError("tx signers don't contain provided account", 1) return cli.NewExitError("tx signers don't contain provided account", 1)
} }
if acc.CanSign() { if acc.CanSign() {
sign := acc.SignHashable(pc.Network, pc.Verifiable) sign := acc.SignHashable(pc.Network, pc.Verifiable)
if err := pc.AddSignature(ch, acc.Contract, acc.PublicKey(), sign); err != nil { if err := pc.AddSignature(acc.ScriptHash(), acc.Contract, acc.PublicKey(), sign); err != nil {
return cli.NewExitError(fmt.Errorf("can't add signature: %w", err), 1) return cli.NewExitError(fmt.Errorf("can't add signature: %w", err), 1)
} }
} else if rpcNode == "" { } else if rpcNode == "" {

View file

@ -522,7 +522,7 @@ func multiTransferNEP17(ctx *cli.Context) error {
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
acc, err := getDecryptedAccount(wall, from, pass) acc, err := options.GetUnlockedAccount(wall, from, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -530,25 +530,36 @@ func multiTransferNEP17(ctx *cli.Context) error {
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
}
if ctx.NArg() == 0 { if ctx.NArg() == 0 {
return cli.NewExitError("empty recipients list", 1) return cli.NewExitError("empty recipients list", 1)
} }
var ( var (
recipients []transferTarget recipients []transferTarget
cosignersOffset = ctx.NArg() cosignersSepPos = ctx.NArg() // `--` position.
) )
cache := make(map[string]*wallet.Token)
for i := 0; i < ctx.NArg(); i++ { for i := 0; i < ctx.NArg(); i++ {
arg := ctx.Args().Get(i) arg := ctx.Args().Get(i)
if arg == cmdargs.CosignersSeparator { if arg == cmdargs.CosignersSeparator {
cosignersOffset = i + 1 cosignersSepPos = i
break break
} }
}
cosigners, extErr := cmdargs.GetSignersFromContext(ctx, cosignersSepPos+1)
if extErr != nil {
return extErr
}
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
}
c, act, exitErr := options.GetRPCWithActor(gctx, ctx, signersAccounts)
if exitErr != nil {
return exitErr
}
cache := make(map[string]*wallet.Token)
for i := 0; i < cosignersSepPos; i++ {
arg := ctx.Args().Get(i)
ss := strings.SplitN(arg, ":", 3) ss := strings.SplitN(arg, ":", 3)
if len(ss) != 3 { if len(ss) != 3 {
return cli.NewExitError("send format must be '<token>:<addr>:<amount>", 1) return cli.NewExitError("send format must be '<token>:<addr>:<amount>", 1)
@ -580,19 +591,6 @@ func multiTransferNEP17(ctx *cli.Context) error {
}) })
} }
cosigners, extErr := cmdargs.GetSignersFromContext(ctx, cosignersOffset)
if extErr != nil {
return extErr
}
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
}
act, err := actor.New(c, signersAccounts)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1)
}
tx, err := makeMultiTransferNEP17(act, recipients) tx, err := makeMultiTransferNEP17(act, recipients)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1) return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1)
@ -618,7 +616,7 @@ func transferNEP(ctx *cli.Context, standard string) error {
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
acc, err := getDecryptedAccount(wall, from, pass) acc, err := options.GetUnlockedAccount(wall, from, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -626,9 +624,22 @@ func transferNEP(ctx *cli.Context, standard string) error {
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
c, err := options.GetRPCClient(gctx, ctx) cosignersOffset, data, extErr := cmdargs.GetDataFromContext(ctx)
if extErr != nil {
return extErr
}
cosigners, extErr := cmdargs.GetSignersFromContext(ctx, cosignersOffset)
if extErr != nil {
return extErr
}
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
}
c, act, exitErr := options.GetRPCWithActor(gctx, ctx, signersAccounts)
if exitErr != nil {
return exitErr
} }
toFlag := ctx.Generic("to").(*flags.Address) toFlag := ctx.Generic("to").(*flags.Address)
@ -644,24 +655,6 @@ func transferNEP(ctx *cli.Context, standard string) error {
} }
} }
cosignersOffset, data, extErr := cmdargs.GetDataFromContext(ctx)
if extErr != nil {
return extErr
}
cosigners, extErr := cmdargs.GetSignersFromContext(ctx, cosignersOffset)
if extErr != nil {
return extErr
}
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
}
act, err := actor.New(c, signersAccounts)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1)
}
amountArg := ctx.String("amount") amountArg := ctx.String("amount")
amount, err := fixedn.FromString(amountArg, int(token.Decimals)) amount, err := fixedn.FromString(amountArg, int(token.Decimals))
// It's OK for NEP-11 transfer to not have amount set. // It's OK for NEP-11 transfer to not have amount set.

View file

@ -5,13 +5,10 @@ import (
"github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/cmdargs"
"github.com/nspcc-dev/neo-go/cli/flags" "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/options"
"github.com/nspcc-dev/neo-go/cli/txctx" "github.com/nspcc-dev/neo-go/cli/txctx"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
@ -111,7 +108,7 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
return cli.NewExitError("address was not provided", 1) return cli.NewExitError("address was not provided", 1)
} }
addr := addrFlag.Uint160() addr := addrFlag.Uint160()
acc, err := getDecryptedAccount(wall, addr, pass) acc, err := options.GetUnlockedAccount(wall, addr, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -119,13 +116,13 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
c, err := options.GetRPCClient(gctx, ctx) signers, err := cmdargs.GetSignersAccounts(acc, wall, nil, transaction.CalledByEntry)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
} }
act, err := actor.NewSimple(c, acc) _, act, exitErr := options.GetRPCWithActor(gctx, ctx, signers)
if err != nil { if exitErr != nil {
return cli.NewExitError(fmt.Errorf("RPC actor issue: %w", err), 1) return exitErr
} }
contract := neo.New(act) contract := neo.New(act)
@ -153,32 +150,3 @@ func handleVote(ctx *cli.Context) error {
return contract.VoteUnsigned(addr, pub) 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/input"
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/cli/txctx" "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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/encoding/address"
@ -26,7 +25,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli"
"gopkg.in/yaml.v3"
) )
const ( const (
@ -822,7 +820,7 @@ func createWallet(ctx *cli.Context) error {
} }
var pass *string var pass *string
if len(configPath) != 0 { if len(configPath) != 0 {
cfg, err := ReadWalletConfig(configPath) cfg, err := options.ReadWalletConfig(configPath)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -946,7 +944,7 @@ func getWalletPathAndPass(ctx *cli.Context, canUseWalletConfig bool) (string, *s
} }
var pass *string var pass *string
if len(configPath) != 0 { if len(configPath) != 0 {
cfg, err := ReadWalletConfig(configPath) cfg, err := options.ReadWalletConfig(configPath)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -956,27 +954,6 @@ func getWalletPathAndPass(ctx *cli.Context, canUseWalletConfig bool) (string, *s
return path, pass, nil 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) { func newAccountFromWIF(w io.Writer, wif string, scrypt keys.ScryptParams, label *string, pass *string) (*wallet.Account, error) {
var ( var (
phrase, name string phrase, name string