cli: unify RPC endpoint flags under options package

This makes rpc flags consistent across all commands, previously some commands
used 'endpoint, e' and some 'rpc, r', some had ability to change timeout and
some hadn't. Now 'rpc-endpoint, r' is used everywhere along with 'timeout, t'.
This commit is contained in:
Roman Khimov 2020-06-18 00:15:13 +03:00
parent f2c4b9b1d9
commit 16ce63e653
6 changed files with 188 additions and 161 deletions

View file

@ -4,10 +4,22 @@ Package options contains a set of common CLI options and helper functions to use
package options
import (
"context"
"errors"
"time"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/urfave/cli"
)
// DefaultTimeout is the default timeout used for RPC requests.
const DefaultTimeout = 10 * time.Second
// RPCEndpointFlag is a long flag name for RPC endpoint. It can be used to
// check for flag presence in the context.
const RPCEndpointFlag = "rpc-endpoint"
// Network is a set of flags for choosing the network to operate on
// (privnet/mainnet/testnet).
var Network = []cli.Flag{
@ -16,6 +28,20 @@ var Network = []cli.Flag{
cli.BoolFlag{Name: "testnet, t"},
}
// RPC is a set of flags used for RPC connections (endpoint and timeout).
var RPC = []cli.Flag{
cli.StringFlag{
Name: RPCEndpointFlag + ", r",
Usage: "RPC node address",
},
cli.DurationFlag{
Name: "timeout, t",
Usage: "Timeout for the operation (10 seconds by default)",
},
}
var errNoEndpoint = errors.New("no RPC endpoint specified, use option '--" + RPCEndpointFlag + "' or '-r'")
// GetNetwork examines Context's flags and returns the appropriate network. It
// defaults to PrivNet if no flags are given.
func GetNetwork(ctx *cli.Context) netmode.Magic {
@ -28,3 +54,25 @@ func GetNetwork(ctx *cli.Context) netmode.Magic {
}
return net
}
// GetTimeoutContext returns a context.Context with default of user-set timeout.
func GetTimeoutContext(ctx *cli.Context) (context.Context, func()) {
dur := ctx.Duration("timeout")
if dur == 0 {
dur = DefaultTimeout
}
return context.WithTimeout(context.Background(), dur)
}
// GetRPCClient returns an RPC client instance for the given Context.
func GetRPCClient(gctx context.Context, ctx *cli.Context) (*client.Client, cli.ExitCoder) {
endpoint := ctx.String(RPCEndpointFlag)
if len(endpoint) == 0 {
return nil, cli.NewExitError(errNoEndpoint, 1)
}
c, err := client.New(gctx, endpoint, client.Options{})
if err != nil {
return nil, cli.NewExitError(err, 1)
}
return c, nil
}

View file

@ -2,7 +2,6 @@ package smartcontract
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
@ -14,11 +13,11 @@ import (
"github.com/go-yaml/yaml"
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -32,7 +31,6 @@ import (
)
var (
errNoEndpoint = errors.New("no RPC endpoint specified, use option '--endpoint' or '-e'")
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")
errNoMethod = errors.New("no method specified for function invocation command")
@ -41,10 +39,6 @@ var (
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")
endpointFlag = cli.StringFlag{
Name: "endpoint, e",
Usage: "trusted RPC endpoint address (like 'http://localhost:20331')",
}
walletFlag = cli.StringFlag{
Name: "wallet, w",
Usage: "wallet to use to get the key for transaction signing",
@ -76,6 +70,33 @@ func Main(op string, args []interface{}) {
// NewCommands returns 'contract' command.
func NewCommands() []cli.Command {
testInvokeScriptFlags := []cli.Flag{
cli.StringFlag{
Name: "in, i",
Usage: "Input location of the avm file that needs to be invoked",
},
}
testInvokeScriptFlags = append(testInvokeScriptFlags, options.RPC...)
deployFlags := []cli.Flag{
cli.StringFlag{
Name: "in, i",
Usage: "Input file for the smart contract (*.avm)",
},
cli.StringFlag{
Name: "config, c",
Usage: "configuration input file (*.yml)",
},
walletFlag,
addressFlag,
gasFlag,
}
deployFlags = append(deployFlags, options.RPC...)
invokeFunctionFlags := []cli.Flag{
walletFlag,
addressFlag,
gasFlag,
}
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
return []cli.Command{{
Name: "contract",
Usage: "compile - debug - deploy smart contracts",
@ -121,42 +142,24 @@ func NewCommands() []cli.Command {
to it).
`,
Action: contractDeploy,
Flags: []cli.Flag{
cli.StringFlag{
Name: "in, i",
Usage: "Input file for the smart contract (*.avm)",
},
cli.StringFlag{
Name: "config, c",
Usage: "configuration input file (*.yml)",
},
endpointFlag,
walletFlag,
addressFlag,
gasFlag,
},
Flags: deployFlags,
},
{
Name: "invokefunction",
Usage: "invoke deployed contract on the blockchain",
UsageText: "neo-go contract invokefunction -e endpoint -w wallet [-a address] [-g gas] scripthash [method] [arguments...] [--] [cosigners...]",
UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] scripthash [method] [arguments...] [--] [cosigners...]",
Description: `Executes given (as a script hash) deployed script with the given method,
arguments and cosigners. See testinvokefunction documentation for the details
about parameters. It differs from testinvokefunction in that this command
sends an invocation transaction to the network.
`,
Action: invokeFunction,
Flags: []cli.Flag{
endpointFlag,
walletFlag,
addressFlag,
gasFlag,
},
Flags: invokeFunctionFlags,
},
{
Name: "testinvokefunction",
Usage: "invoke deployed contract on the blockchain (test mode)",
UsageText: "neo-go contract testinvokefunction -e endpoint scripthash [method] [arguments...] [--] [cosigners...]",
UsageText: "neo-go contract testinvokefunction -r endpoint scripthash [method] [arguments...] [--] [cosigners...]",
Description: `Executes given (as a script hash) deployed script with the given method,
arguments and cosigners. If no method is given "" is passed to the script, if
no arguments are given, an empty array is passed, if no cosigners are given,
@ -247,25 +250,17 @@ func NewCommands() []cli.Command {
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,CustomGroups'
`,
Action: testInvokeFunction,
Flags: []cli.Flag{
endpointFlag,
},
Flags: options.RPC,
},
{
Name: "testinvokescript",
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
UsageText: "neo-go contract testinvokescript -e endpoint -i input.avm [cosigners...]",
UsageText: "neo-go contract testinvokescript -r endpoint -i input.avm [cosigners...]",
Description: `Executes given compiled AVM instructions with the given set of
cosigners. See testinvokefunction documentation for the details about parameters.
`,
Action: testInvokeScript,
Flags: []cli.Flag{
endpointFlag,
cli.StringFlag{
Name: "in, i",
Usage: "Input location of the avm file that needs to be invoked",
},
},
Flags: testInvokeScriptFlags,
},
{
Name: "init",
@ -407,11 +402,6 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
acc *wallet.Account
)
endpoint := ctx.String("endpoint")
if len(endpoint) == 0 {
return cli.NewExitError(errNoEndpoint, 1)
}
args := ctx.Args()
if !args.Present() {
return cli.NewExitError(errNoScriptHash, 1)
@ -455,9 +445,12 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
return err
}
}
c, err := client.New(context.TODO(), endpoint, client.Options{})
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
return err
}
resp, err = c.InvokeFunction(script, operation, params, cosigners)
@ -494,10 +487,6 @@ func testInvokeScript(ctx *cli.Context) error {
if len(src) == 0 {
return cli.NewExitError(errNoInput, 1)
}
endpoint := ctx.String("endpoint")
if len(endpoint) == 0 {
return cli.NewExitError(errNoEndpoint, 1)
}
b, err := ioutil.ReadFile(src)
if err != nil {
@ -516,9 +505,12 @@ func testInvokeScript(ctx *cli.Context) error {
}
}
c, err := client.New(context.TODO(), endpoint, client.Options{})
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
return err
}
scriptHex := hex.EncodeToString(b)
@ -640,10 +632,6 @@ func contractDeploy(ctx *cli.Context) error {
if len(confFile) == 0 {
return cli.NewExitError(errNoConfFile, 1)
}
endpoint := ctx.String("endpoint")
if len(endpoint) == 0 {
return cli.NewExitError(errNoEndpoint, 1)
}
gas := flags.Fixed8FromContext(ctx, "gas")
acc, err := getAccFromContext(ctx)
@ -659,9 +647,12 @@ func contractDeploy(ctx *cli.Context) error {
return err
}
c, err := client.New(context.TODO(), endpoint, client.Options{})
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
return err
}
m := conf.ToManifest(avm)

View file

@ -5,31 +5,31 @@ import (
"fmt"
"io/ioutil"
"github.com/nspcc-dev/neo-go/cli/options"
"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/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"github.com/urfave/cli"
)
func newMultisigCommands() []cli.Command {
signFlags := []cli.Flag{
walletPathFlag,
outFlag,
inFlag,
cli.StringFlag{
Name: "addr",
Usage: "Address to use",
},
}
signFlags = append(signFlags, options.RPC...)
return []cli.Command{
{
Name: "sign",
Usage: "sign a transaction",
UsageText: "multisig sign --path <path> --addr <addr> --in <file.in> --out <file.out>",
Action: signMultisig,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
timeoutFlag,
outFlag,
inFlag,
cli.StringFlag{
Name: "addr",
Usage: "Address to use",
},
},
Flags: signFlags,
},
}
}
@ -75,20 +75,21 @@ func signMultisig(ctx *cli.Context) error {
} else if err := writeParameterContext(c, ctx.String("out")); err != nil {
return cli.NewExitError(err, 1)
}
if endpoint := ctx.String("rpc"); endpoint != "" {
if len(ctx.String(options.RPCEndpointFlag)) != 0 {
w, err := c.GetWitness(acc.Contract)
if err != nil {
return cli.NewExitError(err, 1)
}
tx.Scripts = append(tx.Scripts, *w)
gctx, cancel := getGoContext(ctx)
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
} else if err := c.SendRawTransaction(tx); err != nil {
return err
}
if err := c.SendRawTransaction(tx); err != nil {
return cli.NewExitError(err, 1)
}
}

View file

@ -7,6 +7,7 @@ import (
"io/ioutil"
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
@ -16,39 +17,59 @@ import (
)
func newNEP5Commands() []cli.Command {
balanceFlags := []cli.Flag{
walletPathFlag,
cli.StringFlag{
Name: "addr",
Usage: "Address to use",
},
cli.StringFlag{
Name: "token",
Usage: "Token to use",
},
}
balanceFlags = append(balanceFlags, options.RPC...)
importFlags := []cli.Flag{
walletPathFlag,
cli.StringFlag{
Name: "token",
Usage: "Token contract hash in LE",
},
}
importFlags = append(importFlags, options.RPC...)
transferFlags := []cli.Flag{
walletPathFlag,
outFlag,
fromAddrFlag,
toAddrFlag,
cli.StringFlag{
Name: "token",
Usage: "Token to use",
},
cli.StringFlag{
Name: "amount",
Usage: "Amount of asset to send",
},
flags.Fixed8Flag{
Name: "gas",
Usage: "Amount of GAS to attach to a tx",
},
}
transferFlags = append(transferFlags, options.RPC...)
return []cli.Command{
{
Name: "balance",
Usage: "get address balance",
UsageText: "balance --path <path> --rpc <node> --addr <addr> [--token <hash-or-name>]",
UsageText: "balance --path <path> --rpc-endpoint <node> --timeout <time> --addr <addr> [--token <hash-or-name>]",
Action: getNEP5Balance,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
timeoutFlag,
cli.StringFlag{
Name: "addr",
Usage: "Address to use",
},
cli.StringFlag{
Name: "token",
Usage: "Token to use",
},
},
Flags: balanceFlags,
},
{
Name: "import",
Usage: "import NEP5 token to a wallet",
UsageText: "import --path <path> --rpc <node> --token <hash>",
UsageText: "import --path <path> --rpc-endpoint <node> --timeout <time> --token <hash>",
Action: importNEP5Token,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
cli.StringFlag{
Name: "token",
Usage: "Token contract hash in LE",
},
},
Flags: importFlags,
},
{
Name: "info",
@ -80,28 +101,9 @@ func newNEP5Commands() []cli.Command {
{
Name: "transfer",
Usage: "transfer NEP5 tokens",
UsageText: "transfer --path <path> --rpc <node> --from <addr> --to <addr> --token <hash> --amount string",
UsageText: "transfer --path <path> --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash> --amount string",
Action: transferNEP5,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
outFlag,
timeoutFlag,
fromAddrFlag,
toAddrFlag,
cli.StringFlag{
Name: "token",
Usage: "Token to use",
},
cli.StringFlag{
Name: "amount",
Usage: "Amount of asset to send",
},
flags.Fixed8Flag{
Name: "gas",
Usage: "Amount of GAS to attach to a tx",
},
},
Flags: transferFlags,
},
}
}
@ -123,12 +125,12 @@ func getNEP5Balance(ctx *cli.Context) error {
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", addr), 1)
}
gctx, cancel := getGoContext(ctx)
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
return err
}
var token *wallet.Token
@ -218,12 +220,12 @@ func importNEP5Token(ctx *cli.Context) error {
}
}
gctx, cancel := getGoContext(ctx)
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
return err
}
tok, err := c.NEP5TokenInfo(tokenHash)
@ -314,11 +316,12 @@ func transferNEP5(ctx *cli.Context) error {
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", fromFlag), 1)
}
gctx, cancel := getGoContext(ctx)
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
return err
}
toFlag := ctx.Generic("to").(*flags.Address)

View file

@ -2,7 +2,6 @@ package wallet
import (
"bufio"
"context"
"encoding/hex"
"errors"
"fmt"
@ -11,9 +10,9 @@ import (
"syscall"
"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/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli"
@ -38,14 +37,6 @@ var (
Name: "decrypt, d",
Usage: "Decrypt encrypted keys.",
}
rpcFlag = cli.StringFlag{
Name: "rpc, r",
Usage: "RPC node address",
}
timeoutFlag = cli.DurationFlag{
Name: "timeout, t",
Usage: "Timeout for the operation",
}
outFlag = cli.StringFlag{
Name: "out",
Usage: "file to put JSON transaction to",
@ -70,6 +61,14 @@ var (
// NewCommands returns 'wallet' command.
func NewCommands() []cli.Command {
claimFlags := []cli.Flag{
walletPathFlag,
flags.AddressFlag{
Name: "address, a",
Usage: "Address to claim GAS for",
},
}
claimFlags = append(claimFlags, options.RPC...)
return []cli.Command{{
Name: "wallet",
Usage: "create, open and manage a NEO wallet",
@ -78,15 +77,7 @@ func NewCommands() []cli.Command {
Name: "claim",
Usage: "claim GAS",
Action: claimGas,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
timeoutFlag,
flags.AddressFlag{
Name: "address, a",
Usage: "Address to claim GAS for",
},
},
Flags: claimFlags,
},
{
Name: "init",
@ -223,12 +214,12 @@ func claimGas(ctx *cli.Context) error {
return cli.NewExitError(err, 1)
}
gctx, cancel := getGoContext(ctx)
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
return err
}
// Temporary.
neoHash, err := util.Uint160DecodeStringLE("3b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c")
@ -476,13 +467,6 @@ func askForConsent() bool {
return false
}
func getGoContext(ctx *cli.Context) (context.Context, func()) {
if dur := ctx.Duration("timeout"); dur != 0 {
return context.WithTimeout(context.Background(), dur)
}
return context.Background(), func() {}
}
func dumpWallet(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("path"))
if err != nil {

View file

@ -144,10 +144,10 @@ project:
It's passed to the `deploy` command via `-c` option:
```
$ ./bin/neo-go contract deploy -i contract.avm -c contract.yml -e http://localhost:20331 -w wallet.json -g 0.001
$ ./bin/neo-go contract deploy -i contract.avm -c contract.yml -r http://localhost:20331 -w wallet.json -g 0.001
```
Deployment works via an RPC server, an address of which is passed via `-e`
Deployment works via an RPC server, an address of which is passed via `-r`
option and should be signed using a wallet from `-w` option. More details can
be found in `deploy` command help.
@ -184,7 +184,7 @@ Example call (contract `f84d6a337fbc3d3a201d41da99e86b479e7a2554` with method
given RPC server and wallet and paying 0.00001 GAS for this transaction):
```
$ ./bin/neo-go contract invokefunction -e http://localhost:20331 -w my_wallet.json -g 0.00001 f84d6a337fbc3d3a201d41da99e86b479e7a2554 balanceOf AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json -g 0.00001 f84d6a337fbc3d3a201d41da99e86b479e7a2554 balanceOf AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
```
## Smart contract examples