Skip to content

Commit

Permalink
Merge pull request #2090 from nspcc-dev/new-query-commands
Browse files Browse the repository at this point in the history
New query commands
  • Loading branch information
roman-khimov authored Jul 23, 2021
2 parents 6b852fc + 8b0dfe1 commit 6e2eddb
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 85 deletions.
37 changes: 29 additions & 8 deletions cli/candidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
func TestRegisterCandidate(t *testing.T) {
e := newExecutor(t, true)

validatorHex := hex.EncodeToString(validatorPriv.PublicKey().Bytes())

e.In.WriteString("one\r")
e.Run(t, "neo-go", "wallet", "nep17", "multitransfer",
"--rpc-endpoint", "http://"+e.RPC.Addr,
Expand All @@ -24,6 +26,15 @@ func TestRegisterCandidate(t *testing.T) {
"GAS:"+validatorPriv.Address()+":10000")
e.checkTxPersisted(t)

e.Run(t, "neo-go", "query", "committee",
"--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, "^\\s*"+validatorHex)

e.Run(t, "neo-go", "query", "candidates",
"--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, "^\\s*Key.+$") // Header.
e.checkEOF(t)

// missing address
e.RunWithError(t, "neo-go", "wallet", "candidate", "register",
"--rpc-endpoint", "http://"+e.RPC.Addr,
Expand All @@ -48,7 +59,7 @@ func TestRegisterCandidate(t *testing.T) {
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", validatorWallet,
"--address", validatorPriv.Address(),
"--candidate", hex.EncodeToString(validatorPriv.PublicKey().Bytes()))
"--candidate", validatorHex)
_, index := e.checkTxPersisted(t)

vs, err = e.Chain.GetEnrollments()
Expand All @@ -57,11 +68,21 @@ func TestRegisterCandidate(t *testing.T) {
b, _ := e.Chain.GetGoverningTokenBalance(validatorPriv.GetScriptHash())
require.Equal(t, b, vs[0].Votes)

e.Run(t, "neo-go", "query", "committee",
"--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, "^\\s*"+validatorHex)

e.Run(t, "neo-go", "query", "candidates",
"--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, "^\\s*Key.+$") // Header.
e.checkNextLine(t, "^\\s*"+validatorHex+"\\s*"+b.String()+"\\s*true\\s*true$")
e.checkEOF(t)

// check state
e.Run(t, "neo-go", "wallet", "candidate", "getstate",
e.Run(t, "neo-go", "query", "voter",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--address", validatorPriv.Address())
e.checkNextLine(t, "^\\s*Voted:\\s+"+validatorPriv.Address())
validatorPriv.Address())
e.checkNextLine(t, "^\\s*Voted:\\s+"+validatorHex+"\\s+\\("+validatorPriv.Address()+"\\)$")
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$")
e.checkNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
e.checkEOF(t)
Expand All @@ -80,9 +101,9 @@ func TestRegisterCandidate(t *testing.T) {
require.Equal(t, big.NewInt(0), vs[0].Votes)

// check state
e.Run(t, "neo-go", "wallet", "candidate", "getstate",
e.Run(t, "neo-go", "query", "voter",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--address", validatorPriv.Address())
validatorPriv.Address())
e.checkNextLine(t, "^\\s*Voted:\\s+"+"null") // no vote.
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$")
e.checkNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
Expand All @@ -104,6 +125,6 @@ func TestRegisterCandidate(t *testing.T) {
vs, err = e.Chain.GetEnrollments()
require.Equal(t, 0, len(vs))

// getstate: missing address
e.RunWithError(t, "neo-go", "wallet", "candidate", "getstate")
// query voter: missing address
e.RunWithError(t, "neo-go", "query", "voter")
}
178 changes: 178 additions & 0 deletions cli/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ package query
import (
"bytes"
"encoding/base64"
"encoding/hex"
"fmt"
"sort"
"strconv"
"strings"
"text/tabwriter"

"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/urfave/cli"
)

Expand All @@ -29,12 +36,36 @@ func NewCommands() []cli.Command {
Name: "query",
Usage: "Query data from RPC node",
Subcommands: []cli.Command{
{
Name: "candidates",
Usage: "Get candidates and votes",
Action: queryCandidates,
Flags: options.RPC,
},
{
Name: "committee",
Usage: "Get committee list",
Action: queryCommittee,
Flags: options.RPC,
},
{
Name: "height",
Usage: "Get node height",
Action: queryHeight,
Flags: options.RPC,
},
{
Name: "tx",
Usage: "Query transaction status",
Action: queryTx,
Flags: queryTxFlags,
},
{
Name: "voter",
Usage: "Print NEO holder account state",
Action: queryVoter,
Flags: options.RPC,
},
},
}}
}
Expand Down Expand Up @@ -113,3 +144,150 @@ func dumpApplicationLog(ctx *cli.Context, res *result.ApplicationLog, tx *result
_ = tw.Flush()
fmt.Fprint(ctx.App.Writer, buf.String())
}

func queryCandidates(ctx *cli.Context) error {
var err error

gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()

c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
}

vals, err := c.GetNextBlockValidators()
if err != nil {
return cli.NewExitError(err, 1)
}
comm, err := c.GetCommittee()
if err != nil {
return cli.NewExitError(err, 1)
}

sort.Slice(vals, func(i, j int) bool {
if vals[i].Active != vals[j].Active {
return vals[i].Active
}
if vals[i].Votes != vals[j].Votes {
return vals[i].Votes > vals[j].Votes
}
return vals[i].PublicKey.Cmp(&vals[j].PublicKey) == -1
})
buf := bytes.NewBuffer(nil)
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
_, _ = tw.Write([]byte("Key\tVotes\tCommittee\tConsensus\n"))
for _, val := range vals {
_, _ = tw.Write([]byte(fmt.Sprintf("%s\t%d\t%t\t%t\n", hex.EncodeToString(val.PublicKey.Bytes()), val.Votes, comm.Contains(&val.PublicKey), val.Active)))
}
_ = tw.Flush()
fmt.Fprint(ctx.App.Writer, buf.String())
return nil
}

func queryCommittee(ctx *cli.Context) error {
var err error

gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()

c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
}

comm, err := c.GetCommittee()
if err != nil {
return cli.NewExitError(err, 1)
}

for _, k := range comm {
fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(k.Bytes()))
}
return nil
}

func queryHeight(ctx *cli.Context) error {
var err error

gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()

c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
}

blockCount, err := c.GetBlockCount()
if err != nil {
return cli.NewExitError(err, 1)
}
blockHeight := blockCount - 1 // GetBlockCount returns block count (including 0), not the highest block index.

fmt.Fprintf(ctx.App.Writer, "Latest block: %d\n", blockHeight)

stateHeight, err := c.GetStateHeight()
if err == nil { // We can be talking to a node without getstateheight request support.
fmt.Fprintf(ctx.App.Writer, "Validated state: %d\n", stateHeight.Validated)
}

return nil
}

func queryVoter(ctx *cli.Context) error {
args := ctx.Args()
if len(args) == 0 {
return cli.NewExitError("No address specified", 1)
}

addr, err := flags.ParseAddress(args[0])
if err != nil {
return cli.NewExitError(fmt.Sprintf("wrong address: %s", args[0]), 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: addr,
},
}, 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)
}
st := new(state.NEOBalance)
if _, ok := res.Stack[0].(stackitem.Null); !ok {
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 = fmt.Sprintf("%s (%s)", hex.EncodeToString(st.VoteTo.Bytes()), 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
}
9 changes: 9 additions & 0 deletions cli/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,12 @@ func (e *executor) compareQueryTxVerbose(t *testing.T, tx *transaction.Transacti
}
e.checkEOF(t)
}

func TestQueryHeight(t *testing.T) {
e := newExecutor(t, true)

e.Run(t, "neo-go", "query", "height", "--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, `^Latest block: [0-9]+$`)
e.checkNextLine(t, `^Validated state: [0-9]+$`)
e.checkEOF(t)
}
66 changes: 0 additions & 66 deletions cli/wallet/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ import (
"github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
Expand Down Expand Up @@ -74,18 +71,6 @@ func newValidatorCommands() []cli.Command {
},
}, options.RPC...),
},
{
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...),
},
}
}

Expand Down Expand Up @@ -227,54 +212,3 @@ func getDecryptedAccount(ctx *cli.Context, wall *wallet.Wallet, addr util.Uint16
}
return acc, nil
}

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)
}
st := new(state.NEOBalance)
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
}
Loading

0 comments on commit 6e2eddb

Please sign in to comment.