diff --git a/cli/wallet/multisig.go b/cli/wallet/multisig.go index 0fde124aa..3bcd47828 100644 --- a/cli/wallet/multisig.go +++ b/cli/wallet/multisig.go @@ -50,9 +50,9 @@ func signMultisig(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("invalid address: %w", err), 1) } - acc := wall.GetAccount(sh) - if acc == nil { - return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", addr), 1) + acc, err := getDecryptedAccount(wall, sh) + if err != nil { + return cli.NewExitError(err, 1) } tx, ok := c.Verifiable.(*transaction.Transaction) @@ -60,13 +60,6 @@ func signMultisig(ctx *cli.Context) error { return cli.NewExitError("verifiable item is not a transaction", 1) } printTxInfo(tx) - fmt.Println("Enter password to unlock wallet and sign the transaction") - pass, err := readPassword("Password > ") - if err != nil { - return cli.NewExitError(err, 1) - } else if err := acc.Decrypt(pass); err != nil { - return cli.NewExitError(fmt.Errorf("can't unlock an account: %w", err), 1) - } priv := acc.PrivateKey() sign := priv.Sign(tx.GetSignedPart()) diff --git a/cli/wallet/nep5.go b/cli/wallet/nep5.go index f41a4ad4a..4a352e3e3 100644 --- a/cli/wallet/nep5.go +++ b/cli/wallet/nep5.go @@ -343,9 +343,9 @@ func multiTransferNEP5(ctx *cli.Context) error { fromFlag := ctx.Generic("from").(*flags.Address) from := fromFlag.Uint160() - acc := wall.GetAccount(from) - if acc == nil { - return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", fromFlag), 1) + acc, err := getDecryptedAccount(wall, from) + if err != nil { + return cli.NewExitError(err, 1) } gctx, cancel := options.GetTimeoutContext(ctx) @@ -406,9 +406,9 @@ func transferNEP5(ctx *cli.Context) error { fromFlag := ctx.Generic("from").(*flags.Address) from := fromFlag.Uint160() - acc := wall.GetAccount(from) - if acc == nil { - return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", fromFlag), 1) + acc, err := getDecryptedAccount(wall, from) + if err != nil { + return cli.NewExitError(err, 1) } gctx, cancel := options.GetTimeoutContext(ctx) @@ -445,12 +445,6 @@ func transferNEP5(ctx *cli.Context) error { func signAndSendTransfer(ctx *cli.Context, c *client.Client, acc *wallet.Account, recepients []client.TransferTarget) error { gas := flags.Fixed8FromContext(ctx, "gas") - if pass, err := readPassword("Password > "); err != nil { - return cli.NewExitError(err, 1) - } else if err := acc.Decrypt(pass); err != nil { - return cli.NewExitError(err, 1) - } - tx, err := c.CreateNEP5MultiTransferTx(acc, int64(gas), recepients...) if err != nil { return cli.NewExitError(err, 1) diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go new file mode 100644 index 000000000..2e9a4c8be --- /dev/null +++ b/cli/wallet/validator.go @@ -0,0 +1,187 @@ +package wallet + +import ( + "fmt" + + "github.com/nspcc-dev/neo-go/cli/flags" + "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/io" + "github.com/nspcc-dev/neo-go/pkg/rpc/client" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/urfave/cli" +) + +func newValidatorCommands() []cli.Command { + return []cli.Command{ + { + Name: "register", + Usage: "register as a new candidate", + UsageText: "register -w -r -a ", + Action: handleRegister, + Flags: append([]cli.Flag{ + walletPathFlag, + gasFlag, + flags.AddressFlag{ + Name: "address, a", + Usage: "Address to register", + }, + }, options.RPC...), + }, + { + Name: "unregister", + Usage: "unregister self as a candidate", + UsageText: "unregister -w -r -a ", + Action: handleUnregister, + Flags: append([]cli.Flag{ + walletPathFlag, + gasFlag, + flags.AddressFlag{ + Name: "address, a", + Usage: "Address to unregister", + }, + }, options.RPC...), + }, + { + Name: "vote", + Usage: "vote for a validator", + UsageText: "vote -w -r [-s ] [-g gas] -a -c ", + Action: handleVote, + Flags: append([]cli.Flag{ + walletPathFlag, + gasFlag, + flags.AddressFlag{ + Name: "address, a", + Usage: "Address to vote from", + }, + cli.StringFlag{ + Name: "candidate, c", + Usage: "Public key of candidate to vote for", + }, + }, options.RPC...), + }, + } +} + +func handleRegister(ctx *cli.Context) error { + return handleCandidate(ctx, "registerCandidate") +} + +func handleUnregister(ctx *cli.Context) error { + return handleCandidate(ctx, "unregisterCandidate") +} + +func handleCandidate(ctx *cli.Context, method string) error { + wall, err := openWallet(ctx.String("wallet")) + if err != nil { + return cli.NewExitError(err, 1) + } + + addrFlag := ctx.Generic("address").(*flags.Address) + addr := addrFlag.Uint160() + acc, err := getDecryptedAccount(wall, addr) + if err != nil { + return cli.NewExitError(err, 1) + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + c, err := options.GetRPCClient(gctx, ctx) + if err != nil { + return err + } + + gas := flags.Fixed8FromContext(ctx, "gas") + w := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, method, acc.PrivateKey().PublicKey().Bytes()) + emit.Opcode(w.BinWriter, opcode.ASSERT) + tx, err := c.CreateTxFromScript(w.Bytes(), acc, int64(gas)) + if err != nil { + return cli.NewExitError(err, 1) + } else if err = acc.SignTx(tx); err != nil { + return cli.NewExitError(fmt.Errorf("can't sign tx: %v", err), 1) + } + + res, err := c.SendRawTransaction(tx) + if err != nil { + return cli.NewExitError(err, 1) + } + fmt.Println(res.StringLE()) + return nil +} + +func handleVote(ctx *cli.Context) error { + wall, err := openWallet(ctx.String("wallet")) + if err != nil { + return cli.NewExitError(err, 1) + } + + addrFlag := ctx.Generic("address").(*flags.Address) + addr := addrFlag.Uint160() + acc, err := getDecryptedAccount(wall, addr) + if err != nil { + return cli.NewExitError(err, 1) + } + + var pub *keys.PublicKey + pubStr := ctx.String("candidate") + if pubStr != "" { + pub, err = keys.NewPublicKeyFromString(pubStr) + if err != nil { + return cli.NewExitError(fmt.Errorf("invalid public key: '%s'", pubStr), 1) + } + } + + gctx, cancel := options.GetTimeoutContext(ctx) + defer cancel() + + c, err := options.GetRPCClient(gctx, ctx) + if err != nil { + return err + } + + var pubArg interface{} + if pub != nil { + pubArg = pub.Bytes() + } + + gas := flags.Fixed8FromContext(ctx, "gas") + w := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, "vote", addr.BytesBE(), pubArg) + emit.Opcode(w.BinWriter, opcode.ASSERT) + + tx, err := c.CreateTxFromScript(w.Bytes(), acc, int64(gas)) + if err != nil { + return cli.NewExitError(err, 1) + } + + if err = acc.SignTx(tx); err != nil { + return cli.NewExitError(fmt.Errorf("can't sign tx: %v", err), 1) + } + + res, err := c.SendRawTransaction(tx) + if err != nil { + return cli.NewExitError(err, 1) + } + fmt.Println(res.StringLE()) + return nil +} + +func getDecryptedAccount(wall *wallet.Wallet, addr util.Uint160) (*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)) + } + + if pass, err := readPassword("Password > "); err != nil { + return nil, err + } else if err := acc.Decrypt(pass); err != nil { + return nil, err + } + return acc, nil +} diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index c71a9f7b7..fd92310a6 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -13,11 +13,7 @@ import ( "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/io" - "github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" - "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/urfave/cli" "golang.org/x/crypto/ssh/terminal" @@ -191,22 +187,9 @@ func NewCommands() []cli.Command { Subcommands: newNEP5Commands(), }, { - Name: "vote", - Usage: "vote for a validator", - UsageText: "vote -w -r [-t ] [-g gas] -a -k ", - Action: handleVote, - Flags: append([]cli.Flag{ - walletPathFlag, - gasFlag, - flags.AddressFlag{ - Name: "addr, a", - Usage: "Address to vote from", - }, - cli.StringFlag{ - Name: "key, k", - Usage: "Public key of candidate to vote for", - }, - }, options.RPC...), + Name: "candidate", + Usage: "work with candidates", + Subcommands: newValidatorCommands(), }, }, }} @@ -224,16 +207,9 @@ func claimGas(ctx *cli.Context) error { return cli.NewExitError("address was not provided", 1) } scriptHash := addrFlag.Uint160() - acc := wall.GetAccount(scriptHash) - if acc == nil { - return cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", addrFlag), 1) - } - - pass, err := readPassword("Enter password > ") + acc, err := getDecryptedAccount(wall, scriptHash) if err != nil { return cli.NewExitError(err, 1) - } else if err := acc.Decrypt(pass); err != nil { - return cli.NewExitError(err, 1) } gctx, cancel := options.GetTimeoutContext(ctx) @@ -535,69 +511,6 @@ func createWallet(ctx *cli.Context) error { return nil } -func handleVote(ctx *cli.Context) error { - wall, err := openWallet(ctx.String("wallet")) - if err != nil { - return cli.NewExitError(err, 1) - } - - addrFlag := ctx.Generic("addr").(*flags.Address) - addr := addrFlag.Uint160() - acc := wall.GetAccount(addr) - if acc == nil { - return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", addrFlag), 1) - } - - var pub *keys.PublicKey - pubStr := ctx.String("key") - if pubStr != "" { - pub, err = keys.NewPublicKeyFromString(pubStr) - if err != nil { - return cli.NewExitError(fmt.Errorf("invalid public key: '%s'", pubStr), 1) - } - } - - gctx, cancel := options.GetTimeoutContext(ctx) - defer cancel() - - c, err := options.GetRPCClient(gctx, ctx) - if err != nil { - return err - } - - var pubArg interface{} - if pub != nil { - pubArg = pub.Bytes() - } - - gas := flags.Fixed8FromContext(ctx, "gas") - w := io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, "vote", addr.BytesBE(), pubArg) - emit.Opcode(w.BinWriter, opcode.ASSERT) - - tx, err := c.CreateTxFromScript(w.Bytes(), acc, int64(gas)) - if err != nil { - return cli.NewExitError(err, 1) - } - - if pass, err := readPassword("Password > "); err != nil { - return cli.NewExitError(err, 1) - } else if err := acc.Decrypt(pass); err != nil { - return cli.NewExitError(err, 1) - } - - if err = acc.SignTx(tx); err != nil { - return cli.NewExitError(fmt.Errorf("can't sign tx: %v", err), 1) - } - - res, err := c.SendRawTransaction(tx) - if err != nil { - return cli.NewExitError(err, 1) - } - fmt.Println(res.StringLE()) - return nil -} - func readAccountInfo() (string, string, error) { buf := bufio.NewReader(os.Stdin) fmt.Print("Enter the name of the account > ")