2020-08-07 09:18:38 +00:00
|
|
|
package wallet
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
2020-08-31 09:22:09 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/cli/input"
|
2020-08-07 09:18:38 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/cli/options"
|
2020-12-13 18:25:04 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
2021-05-28 15:05:19 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
2020-09-01 13:55:00 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
2020-08-07 09:18:38 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2020-08-07 09:34:54 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
2021-05-28 15:05:19 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
2020-08-07 09:18:38 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
2021-03-02 12:43:09 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
2021-05-28 15:05:19 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
2020-12-29 10:44:07 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
2020-08-07 09:34:54 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2020-08-07 09:18:38 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
2020-08-07 09:34:54 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
2020-08-07 09:18:38 +00:00
|
|
|
"github.com/urfave/cli"
|
|
|
|
)
|
|
|
|
|
|
|
|
func newValidatorCommands() []cli.Command {
|
|
|
|
return []cli.Command{
|
2020-08-07 09:24:54 +00:00
|
|
|
{
|
|
|
|
Name: "register",
|
|
|
|
Usage: "register as a new candidate",
|
|
|
|
UsageText: "register -w <path> -r <rpc> -a <addr>",
|
|
|
|
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 <path> -r <rpc> -a <addr>",
|
|
|
|
Action: handleUnregister,
|
|
|
|
Flags: append([]cli.Flag{
|
|
|
|
walletPathFlag,
|
|
|
|
gasFlag,
|
|
|
|
flags.AddressFlag{
|
|
|
|
Name: "address, a",
|
|
|
|
Usage: "Address to unregister",
|
|
|
|
},
|
|
|
|
}, options.RPC...),
|
|
|
|
},
|
2020-08-07 09:18:38 +00:00
|
|
|
{
|
|
|
|
Name: "vote",
|
|
|
|
Usage: "vote for a validator",
|
2021-05-28 12:15:37 +00:00
|
|
|
UsageText: "vote -w <path> -r <rpc> [-s <timeout>] [-g gas] -a <addr> [-c <public key>]",
|
|
|
|
Description: `Votes for a validator by calling "vote" method of a NEO native
|
|
|
|
contract. Do not provide candidate argument to perform unvoting.
|
|
|
|
`,
|
|
|
|
Action: handleVote,
|
2020-08-07 09:18:38 +00:00
|
|
|
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...),
|
|
|
|
},
|
2021-05-28 15:05:19 +00:00
|
|
|
{
|
|
|
|
Name: "getstate",
|
|
|
|
Usage: "print NEO holder account state",
|
|
|
|
UsageText: "getstate -a <addr>",
|
|
|
|
Action: getAccountState,
|
|
|
|
Flags: append([]cli.Flag{
|
|
|
|
flags.AddressFlag{
|
|
|
|
Name: "address, a",
|
|
|
|
Usage: "Address to get state of",
|
|
|
|
},
|
|
|
|
}, options.RPC...),
|
|
|
|
},
|
2020-08-07 09:18:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 09:24:54 +00:00
|
|
|
func handleRegister(ctx *cli.Context) error {
|
2021-07-21 09:25:42 +00:00
|
|
|
return handleCandidate(ctx, "registerCandidate", 100000000) // 1 additional GAS.
|
2020-08-07 09:24:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func handleUnregister(ctx *cli.Context) error {
|
2021-01-21 20:02:59 +00:00
|
|
|
return handleCandidate(ctx, "unregisterCandidate", -1)
|
2020-08-07 09:24:54 +00:00
|
|
|
}
|
|
|
|
|
2021-01-21 20:02:59 +00:00
|
|
|
func handleCandidate(ctx *cli.Context, method string, sysGas int64) error {
|
2020-08-07 09:24:54 +00:00
|
|
|
wall, err := openWallet(ctx.String("wallet"))
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
2020-09-01 13:55:00 +00:00
|
|
|
defer wall.Close()
|
2020-08-07 09:24:54 +00:00
|
|
|
|
|
|
|
addrFlag := ctx.Generic("address").(*flags.Address)
|
2021-04-16 09:03:03 +00:00
|
|
|
if !addrFlag.IsSet {
|
|
|
|
return cli.NewExitError("address was not provided", 1)
|
|
|
|
}
|
2020-08-07 09:24:54 +00:00
|
|
|
addr := addrFlag.Uint160()
|
2020-08-28 09:11:19 +00:00
|
|
|
acc, err := getDecryptedAccount(ctx, wall, addr)
|
2020-08-07 09:34:54 +00:00
|
|
|
if err != nil {
|
2020-08-07 09:24:54 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
c, err := options.GetRPCClient(gctx, ctx)
|
|
|
|
if err != nil {
|
2020-11-03 11:08:59 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2020-08-07 09:24:54 +00:00
|
|
|
}
|
|
|
|
|
2021-07-21 09:25:42 +00:00
|
|
|
if sysGas >= 0 {
|
|
|
|
regPrice, err := c.GetCandidateRegisterPrice()
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
|
|
|
sysGas += regPrice
|
|
|
|
}
|
|
|
|
|
2020-08-07 09:24:54 +00:00
|
|
|
gas := flags.Fixed8FromContext(ctx, "gas")
|
2020-12-13 18:25:04 +00:00
|
|
|
neoContractHash, err := c.GetNativeContractHash(nativenames.Neo)
|
2020-09-25 16:32:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-07 09:24:54 +00:00
|
|
|
w := io.NewBufBinWriter()
|
2021-02-25 15:04:46 +00:00
|
|
|
emit.AppCall(w.BinWriter, neoContractHash, method, callflag.States, acc.PrivateKey().PublicKey().Bytes())
|
2020-10-02 08:30:15 +00:00
|
|
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
2021-04-21 14:21:54 +00:00
|
|
|
res, err := c.SignAndPushInvocationTx(w.Bytes(), acc, sysGas, gas, []client.SignerAccount{{
|
2021-03-02 12:43:09 +00:00
|
|
|
Signer: transaction.Signer{
|
|
|
|
Account: acc.Contract.ScriptHash(),
|
|
|
|
Scopes: transaction.CalledByEntry,
|
|
|
|
},
|
|
|
|
Account: acc,
|
2021-04-21 14:21:54 +00:00
|
|
|
}})
|
2020-08-07 09:24:54 +00:00
|
|
|
if err != nil {
|
2021-04-21 14:21:54 +00:00
|
|
|
return cli.NewExitError(fmt.Errorf("failed to push transaction: %w", err), 1)
|
2020-08-07 09:24:54 +00:00
|
|
|
}
|
2020-08-28 09:11:19 +00:00
|
|
|
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
2020-08-07 09:24:54 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-07 09:18:38 +00:00
|
|
|
func handleVote(ctx *cli.Context) error {
|
|
|
|
wall, err := openWallet(ctx.String("wallet"))
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
2020-09-01 13:55:00 +00:00
|
|
|
defer wall.Close()
|
2020-08-07 09:18:38 +00:00
|
|
|
|
|
|
|
addrFlag := ctx.Generic("address").(*flags.Address)
|
2021-04-16 09:03:03 +00:00
|
|
|
if !addrFlag.IsSet {
|
|
|
|
return cli.NewExitError("address was not provided", 1)
|
|
|
|
}
|
2020-08-07 09:18:38 +00:00
|
|
|
addr := addrFlag.Uint160()
|
2020-08-28 09:11:19 +00:00
|
|
|
acc, err := getDecryptedAccount(ctx, wall, addr)
|
2020-08-07 09:34:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
2020-08-07 09:18:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2020-11-03 11:08:59 +00:00
|
|
|
return cli.NewExitError(err, 1)
|
2020-08-07 09:18:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var pubArg interface{}
|
|
|
|
if pub != nil {
|
|
|
|
pubArg = pub.Bytes()
|
|
|
|
}
|
|
|
|
|
|
|
|
gas := flags.Fixed8FromContext(ctx, "gas")
|
2020-12-13 18:25:04 +00:00
|
|
|
neoContractHash, err := c.GetNativeContractHash(nativenames.Neo)
|
2020-09-25 16:32:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
2020-08-07 09:18:38 +00:00
|
|
|
w := io.NewBufBinWriter()
|
2021-02-25 15:04:46 +00:00
|
|
|
emit.AppCall(w.BinWriter, neoContractHash, "vote", callflag.States, addr.BytesBE(), pubArg)
|
2020-10-02 08:30:15 +00:00
|
|
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
2020-08-07 09:18:38 +00:00
|
|
|
|
2021-04-21 14:25:25 +00:00
|
|
|
res, err := c.SignAndPushInvocationTx(w.Bytes(), acc, -1, gas, []client.SignerAccount{{
|
2021-03-02 12:43:09 +00:00
|
|
|
Signer: transaction.Signer{
|
|
|
|
Account: acc.Contract.ScriptHash(),
|
|
|
|
Scopes: transaction.CalledByEntry,
|
|
|
|
},
|
2021-04-21 14:25:25 +00:00
|
|
|
Account: acc}})
|
2020-08-07 09:18:38 +00:00
|
|
|
if err != nil {
|
2021-04-21 14:25:25 +00:00
|
|
|
return cli.NewExitError(fmt.Errorf("failed to push invocation transaction: %w", err), 1)
|
2020-08-07 09:18:38 +00:00
|
|
|
}
|
2020-08-28 09:11:19 +00:00
|
|
|
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
2020-08-07 09:18:38 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-08-07 09:34:54 +00:00
|
|
|
|
2020-08-28 09:11:19 +00:00
|
|
|
func getDecryptedAccount(ctx *cli.Context, wall *wallet.Wallet, addr util.Uint160) (*wallet.Account, error) {
|
2020-08-07 09:34:54 +00:00
|
|
|
acc := wall.GetAccount(addr)
|
|
|
|
if acc == nil {
|
|
|
|
return nil, fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addr))
|
|
|
|
}
|
|
|
|
|
2021-02-10 08:53:01 +00:00
|
|
|
if pass, err := input.ReadPassword("Password > "); err != nil {
|
2020-08-31 09:22:09 +00:00
|
|
|
fmt.Println("ERROR", pass, err)
|
2020-08-07 09:34:54 +00:00
|
|
|
return nil, err
|
2021-06-04 11:27:22 +00:00
|
|
|
} else if err := acc.Decrypt(pass, wall.Scrypt); err != nil {
|
2020-08-07 09:34:54 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return acc, nil
|
|
|
|
}
|
2021-05-28 15:05:19 +00:00
|
|
|
|
|
|
|
func getAccountState(ctx *cli.Context) error {
|
|
|
|
addrFlag := ctx.Generic("address").(*flags.Address)
|
|
|
|
if !addrFlag.IsSet {
|
|
|
|
return cli.NewExitError("address was not provided", 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
|
|
|
defer cancel()
|
|
|
|
c, exitErr := options.GetRPCClient(gctx, ctx)
|
|
|
|
if exitErr != nil {
|
|
|
|
return exitErr
|
|
|
|
}
|
|
|
|
|
|
|
|
neoHash, err := c.GetNativeContractHash(nativenames.Neo)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(fmt.Errorf("failed to get NEO contract hash: %w", err), 1)
|
|
|
|
}
|
|
|
|
res, err := c.InvokeFunction(neoHash, "getAccountState", []smartcontract.Parameter{
|
|
|
|
{
|
|
|
|
Type: smartcontract.Hash160Type,
|
|
|
|
Value: addrFlag.Uint160(),
|
|
|
|
},
|
|
|
|
}, nil)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(err, 1)
|
|
|
|
}
|
|
|
|
if res.State != "HALT" {
|
|
|
|
return cli.NewExitError(fmt.Errorf("invocation failed: %s", res.FaultException), 1)
|
|
|
|
}
|
|
|
|
if len(res.Stack) == 0 {
|
|
|
|
return cli.NewExitError("result stack is empty", 1)
|
|
|
|
}
|
2021-07-18 09:39:31 +00:00
|
|
|
st := new(state.NEOBalance)
|
2021-05-28 15:05:19 +00:00
|
|
|
err = st.FromStackItem(res.Stack[0])
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(fmt.Errorf("failed to convert account state from stackitem: %w", err), 1)
|
|
|
|
}
|
|
|
|
dec, err := c.NEP17Decimals(neoHash)
|
|
|
|
if err != nil {
|
|
|
|
return cli.NewExitError(fmt.Errorf("failed to get decimals: %w", err), 1)
|
|
|
|
}
|
|
|
|
voted := "null"
|
|
|
|
if st.VoteTo != nil {
|
|
|
|
voted = address.Uint160ToString(st.VoteTo.GetScriptHash())
|
|
|
|
}
|
|
|
|
fmt.Fprintf(ctx.App.Writer, "\tVoted: %s\n", voted)
|
|
|
|
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", fixedn.ToString(&st.Balance, int(dec)))
|
|
|
|
fmt.Fprintf(ctx.App.Writer, "\tBlock: %d\n", st.BalanceHeight)
|
|
|
|
return nil
|
|
|
|
}
|