mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-22 19:19:09 +00:00
a50723ff72
It's slightly less efficient (all comparisons are always made), but for strings/ints it's negligible performance difference, while the code looks a tiny bit better. Signed-off-by: Roman Khimov <roman@nspcc.ru>
319 lines
8 KiB
Go
319 lines
8 KiB
Go
package query
|
|
|
|
import (
|
|
"bytes"
|
|
"cmp"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
|
"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/transaction"
|
|
"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/neorpc/result"
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
// NewCommands returns 'query' command.
|
|
func NewCommands() []*cli.Command {
|
|
queryTxFlags := append([]cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "verbose",
|
|
Aliases: []string{"v"},
|
|
Usage: "Output full tx info and execution logs",
|
|
},
|
|
}, options.RPC...)
|
|
return []*cli.Command{{
|
|
Name: "query",
|
|
Usage: "Query data from RPC node",
|
|
Subcommands: []*cli.Command{
|
|
{
|
|
Name: "candidates",
|
|
Usage: "Get candidates and votes",
|
|
UsageText: "neo-go query candidates -r endpoint [-s timeout]",
|
|
Action: queryCandidates,
|
|
Flags: options.RPC,
|
|
},
|
|
{
|
|
Name: "committee",
|
|
Usage: "Get committee list",
|
|
UsageText: "neo-go query committee -r endpoint [-s timeout]",
|
|
Action: queryCommittee,
|
|
Flags: options.RPC,
|
|
},
|
|
{
|
|
Name: "height",
|
|
Usage: "Get node height",
|
|
UsageText: "neo-go query height -r endpoint [-s timeout]",
|
|
Action: queryHeight,
|
|
Flags: options.RPC,
|
|
},
|
|
{
|
|
Name: "tx",
|
|
Usage: "Query transaction status",
|
|
UsageText: "neo-go query tx -r endpoint [-s timeout] [-v] <hash>",
|
|
Action: queryTx,
|
|
Flags: queryTxFlags,
|
|
},
|
|
{
|
|
Name: "voter",
|
|
Usage: "Print NEO holder account state",
|
|
UsageText: "neo-go query voter -r endpoint [-s timeout] <address>",
|
|
Action: queryVoter,
|
|
Flags: options.RPC,
|
|
},
|
|
},
|
|
}}
|
|
}
|
|
|
|
func queryTx(ctx *cli.Context) error {
|
|
args := ctx.Args().Slice()
|
|
if len(args) == 0 {
|
|
return cli.Exit("transaction hash is missing", 1)
|
|
} else if len(args) > 1 {
|
|
return cli.Exit("only one transaction hash is accepted", 1)
|
|
}
|
|
|
|
txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x"))
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("invalid tx hash: %s", args[0]), 1)
|
|
}
|
|
|
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
|
defer cancel()
|
|
|
|
c, err := options.GetRPCClient(gctx, ctx)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
txOut, err := c.GetRawTransactionVerbose(txHash)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
var res *result.ApplicationLog
|
|
if !txOut.Blockhash.Equals(util.Uint256{}) {
|
|
res, err = c.GetApplicationLog(txHash, nil)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
}
|
|
|
|
err = DumpApplicationLog(ctx, res, &txOut.Transaction, &txOut.TransactionMetadata, ctx.Bool("verbose"))
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DumpApplicationLog(
|
|
ctx *cli.Context,
|
|
res *result.ApplicationLog,
|
|
tx *transaction.Transaction,
|
|
txMeta *result.TransactionMetadata,
|
|
verbose bool) error {
|
|
var buf []byte
|
|
|
|
buf = fmt.Appendf(buf, "Hash:\t%s\n", tx.Hash().StringLE())
|
|
buf = fmt.Appendf(buf, "OnChain:\t%t\n", res != nil)
|
|
if res == nil {
|
|
buf = fmt.Appendf(buf, "ValidUntil:\t%s\n", strconv.FormatUint(uint64(tx.ValidUntilBlock), 10))
|
|
} else {
|
|
if txMeta != nil {
|
|
buf = fmt.Appendf(buf, "BlockHash:\t%s\n", txMeta.Blockhash.StringLE())
|
|
}
|
|
if len(res.Executions) != 1 {
|
|
buf = fmt.Appendf(buf, "Success:\tunknown (no execution data)\n")
|
|
} else {
|
|
buf = fmt.Appendf(buf, "Success:\t%t\n", res.Executions[0].VMState == vmstate.Halt)
|
|
}
|
|
}
|
|
if verbose {
|
|
for _, sig := range tx.Signers {
|
|
buf = fmt.Appendf(buf, "Signer:\t%s (%s)\n", address.Uint160ToString(sig.Account), sig.Scopes)
|
|
}
|
|
buf = fmt.Appendf(buf, "SystemFee:\t%s GAS\n", fixedn.Fixed8(tx.SystemFee).String())
|
|
buf = fmt.Appendf(buf, "NetworkFee:\t%s GAS\n", fixedn.Fixed8(tx.NetworkFee).String())
|
|
buf = fmt.Appendf(buf, "Script:\t%s\n", base64.StdEncoding.EncodeToString(tx.Script))
|
|
v := vm.New()
|
|
v.Load(tx.Script)
|
|
opts := bytes.NewBuffer(nil)
|
|
v.PrintOps(opts)
|
|
buf = append(buf, opts.Bytes()...)
|
|
if res != nil {
|
|
for _, e := range res.Executions {
|
|
if e.VMState != vmstate.Halt {
|
|
buf = fmt.Appendf(buf, "Exception:\t%s\n", e.FaultException)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
tw := tabwriter.NewWriter(ctx.App.Writer, 0, 4, 4, '\t', 0)
|
|
_, err := tw.Write(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return tw.Flush()
|
|
}
|
|
|
|
func queryCandidates(ctx *cli.Context) error {
|
|
var err error
|
|
|
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
|
defer cancel()
|
|
|
|
c, err := options.GetRPCClient(gctx, ctx)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
vals, err := c.GetCandidates()
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
comm, err := c.GetCommittee()
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
slices.SortFunc(vals, func(a, b result.Candidate) int {
|
|
if a.Active && !b.Active {
|
|
return 1
|
|
}
|
|
if !a.Active && b.Active {
|
|
return -1
|
|
}
|
|
return cmp.Or(
|
|
cmp.Compare(a.Votes, b.Votes),
|
|
a.PublicKey.Cmp(&b.PublicKey),
|
|
)
|
|
})
|
|
var res []byte
|
|
res = fmt.Appendf(res, "Key\tVotes\tCommittee\tConsensus\n")
|
|
for _, val := range vals {
|
|
res = fmt.Appendf(res, "%s\t%d\t%t\t%t\n", val.PublicKey.StringCompressed(), val.Votes, comm.Contains(&val.PublicKey), val.Active)
|
|
}
|
|
tw := tabwriter.NewWriter(ctx.App.Writer, 0, 2, 2, ' ', 0)
|
|
_, err = tw.Write(res)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return tw.Flush()
|
|
}
|
|
|
|
func queryCommittee(ctx *cli.Context) error {
|
|
var err error
|
|
|
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
|
defer cancel()
|
|
|
|
c, err := options.GetRPCClient(gctx, ctx)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
comm, err := c.GetCommittee()
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
for _, k := range comm {
|
|
fmt.Fprintln(ctx.App.Writer, k.StringCompressed())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func queryHeight(ctx *cli.Context) error {
|
|
var err error
|
|
|
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
|
defer cancel()
|
|
|
|
c, err := options.GetRPCClient(gctx, ctx)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
|
|
blockCount, err := c.GetBlockCount()
|
|
if err != nil {
|
|
return cli.Exit(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().Slice()
|
|
if len(args) == 0 {
|
|
return cli.Exit("no address specified", 1)
|
|
} else if len(args) > 1 {
|
|
return cli.Exit("this command only accepts one address", 1)
|
|
}
|
|
|
|
addr, err := flags.ParseAddress(args[0])
|
|
if err != nil {
|
|
return cli.Exit(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
|
|
}
|
|
|
|
neoToken := neo.NewReader(invoker.New(c, nil))
|
|
|
|
st, err := neoToken.GetAccountState(addr)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
if st == nil {
|
|
st = new(state.NEOBalance)
|
|
}
|
|
dec, err := neoToken.Decimals()
|
|
if err != nil {
|
|
return cli.Exit(fmt.Errorf("failed to get decimals: %w", err), 1)
|
|
}
|
|
voted := "null"
|
|
if st.VoteTo != nil {
|
|
voted = fmt.Sprintf("%s (%s)", st.VoteTo.StringCompressed(), 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
|
|
}
|