From 753e61bcfff902418d914e8cfcd59ad4324a62d1 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 22 Jul 2021 14:40:18 +0300 Subject: [PATCH 1/7] cli/query: add 'candidates' and 'committee', fix #2067 --- cli/candidate_test.go | 23 ++++++++++++- cli/query/query.go | 76 +++++++++++++++++++++++++++++++++++++++++++ docs/cli.md | 59 ++++++++++++++++++++++++++++++++- docs/consensus.md | 8 ++--- 4 files changed, 160 insertions(+), 6 deletions(-) diff --git a/cli/candidate_test.go b/cli/candidate_test.go index 91ba7cb50a..d9093c51e8 100644 --- a/cli/candidate_test.go +++ b/cli/candidate_test.go @@ -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, @@ -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, @@ -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() @@ -57,6 +68,16 @@ 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", "--rpc-endpoint", "http://"+e.RPC.Addr, diff --git a/cli/query/query.go b/cli/query/query.go index 9ee5b560d3..48ffacc3d2 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -3,7 +3,9 @@ package query import ( "bytes" "encoding/base64" + "encoding/hex" "fmt" + "sort" "strconv" "strings" "text/tabwriter" @@ -29,6 +31,18 @@ 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: "tx", Usage: "Query transaction status", @@ -113,3 +127,65 @@ 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 +} diff --git a/docs/cli.md b/docs/cli.md index 5f0939e8bb..0b3b6b8b52 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -464,7 +464,9 @@ You can also vote for candidates if you own NEO: ./bin/neo-go wallet candidate vote -a NMe64G6j6nkPZby26JAgpaCNrn1Ee4wW6E -w wallet.json -r http://localhost:20332 -c 03cecd63d7d8120c3b194c3b2880dd4aafe1475c57e40c852872d7305615258140 ``` -### Querying transaction status +### Getting data from chain + +#### Transaction status `query tx` provides convenient wrapper over RPC calls to query transaction status. ``` ./bin/neo-go query tx --rpc-endpoint http://localhost:20332 aaf87628851e0c03ee086ff88596bc24de87082e9e5c73d75bb1c740d1d68088 @@ -476,6 +478,61 @@ Success: true `OnChain` is true if transaction was included in block and `Success` is true if it was executed successfully. +#### Committee members +`query commitee` returns a list of current committee members: +``` +$ ./bin/neo-go query committee -r http://localhost:20332 +03009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a2 +030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba +0207da870cedb777fceff948641021714ec815110ca111ccc7a54c168e065bda70 +02147c1b1d5728e1954958daff2f88ee2fa50a06890a8a9db3fa9e972b66ae559f +0214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff01 +03184b018d6b2bc093e535519732b3fd3f7551c8cffaf4621dd5a0b89482ca66c9 +0231edee3978d46c335e851c76059166eb8878516f459e085c0dd092f0f1d51c21 +023e9b32ea89b94d066e649b124fd50e396ee91369e8e2a6ae1b11c170d022256d +03408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a259477806 +035056669864feea401d8c31e447fb82dd29f342a9476cfd449584ce2a6165e4d7 +025831cee3708e87d78211bec0d1bfee9f4c85ae784762f042e7f31c0d40c329b8 +026328aae34f149853430f526ecaa9cf9c8d78a4ea82d08bdf63dd03c4d0693be6 +0370c75c54445565df62cfe2e76fbec4ba00d1298867972213530cae6d418da636 +03840415b0a0fcf066bcc3dc92d8349ebd33a6ab1402ef649bae00e5d9f5840828 +03957af9e77282ae3263544b7b2458903624adc3f5dee303957cb6570524a5f254 +02a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b +02ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd +03c609bea5a4825908027e4ab217e7efc06e311f19ecad9d417089f14927a173d5 +02c69a8d084ee7319cfecf5161ff257aa2d1f53e79bf6c6f164cff5d94675c38b3 +02cf9dc6e85d581480d91e88e8cbeaa0c153a046e89ded08b4cefd851e1d7325b5 +03d84d22b8753cf225d263a3a782a4e16ca72ef323cfde04977c74f14873ab1e4c +``` + +#### Candidate/voting data +`query candidates` returns all current candidates, number of votes for them +and their committee/consensus status: +``` +$ ./bin/neo-go query candidates -r http://localhost:20332 +Key Votes Committee Consensus +03009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a2 2000000 true true +030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba 2000000 true true +0214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff01 2000000 true true +023e9b32ea89b94d066e649b124fd50e396ee91369e8e2a6ae1b11c170d022256d 2000000 true true +03408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a259477806 2000000 true true +02a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b 2000000 true true +02ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd 2000000 true true +025664cef0abcba7787ad5fb12f3af31c5cdc7a479068aa2ad8ee78804768bffe9 1000000 false false +03650a684461a64bf46bee561d9981a4c57adc6ccbd3a9512b83701480b30218ab 1000000 false false +026a10aa2b4d7639c5deafa4ff081467db10b5d00432749a2a5ee1d2bfed23e1c0 1000000 false false +02d5786a9214a8a3f1757d7596fd10f5241205e2c0d68362f4766579bac6189249 1000000 false false +033d8e35f8cd9a33852280b6d93093c7292ed5ce90d90f149fa2da50ba6168dfce 100000 false false +0349c7ef0b4aaf181f0a3e1350c527b136cc5b42498cb83ab8880c05ed95167e1c 100000 false false +035b4f9be2b853e06eb5a09c167e038b96b4804235961510423252f2ee3dbba583 100000 false false +027e459b264b6f7e325ab4b0bb0fa641081fb68517fd613ebd7a94cb79d3081e4f 100000 false false +0288cad442a877960c76b4f688f4be30f768256d9a3da2492b0180b91243918b4f 100000 false false +02a40c552798f79636095817ec88924fc6cb7094e5a3cb059a9b3bc91ea3bf0d3d 100000 false false +02db79e69c518ae9254e314b6f5f4b63e914cdd4b2574dc2f9236c01c1fc1d8973 100000 false false +02ec143f00b88524caf36a0121c2de09eef0519ddbe1c710a00f0e2663201ee4c0 100000 false false +03d8d58d2257ca6cb14522b76513d4783f7d481801695893794c2186515c6de76f 0 false false +``` + ### NEP-17 token functions `wallet nep17` contains a set of commands to use for NEP-17 tokens. diff --git a/docs/consensus.md b/docs/consensus.md index 1ddd12e0b0..29cbc92cac 100644 --- a/docs/consensus.md +++ b/docs/consensus.md @@ -92,16 +92,16 @@ use. This command will create and send appropriate transaction to the network and you should then wait for it to settle in a block. If all goes well it'll end with "HALT" state and your registration will be completed. You can use -`query tx` command to see transaction status or -`getnextblockvalidators` to see if your candidate was added. +`query tx` command to see transaction status or `query candidates` to see if +your candidate was added. ### Voting After registration completion if you own some NEO you can also vote for your candidate to help it become CN and receive additional voter GAS. To do that you need to know the public key of your candidate, which can either be seen in -`getnextblockvalidators` RPC call output or extracted from wallet `wallet -dump-keys` command: +`query candidates` command output or extracted from wallet `wallet dump-keys` +command: ``` $ neo-go wallet dump-keys -w wallet.json From 24ee8fab5c1166045df2131937a6342cde588065 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 22 Jul 2021 14:57:29 +0300 Subject: [PATCH 2/7] cli: move `wallet candidate getstate` into `query voter` This command has nothing to do with wallets. --- cli/candidate_test.go | 12 ++++---- cli/query/query.go | 66 +++++++++++++++++++++++++++++++++++++++++ cli/wallet/validator.go | 66 ----------------------------------------- docs/cli.md | 11 +++++++ 4 files changed, 83 insertions(+), 72 deletions(-) diff --git a/cli/candidate_test.go b/cli/candidate_test.go index d9093c51e8..ffb0fd10b6 100644 --- a/cli/candidate_test.go +++ b/cli/candidate_test.go @@ -79,9 +79,9 @@ func TestRegisterCandidate(t *testing.T) { 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()) + validatorPriv.Address()) e.checkNextLine(t, "^\\s*Voted:\\s+"+validatorPriv.Address()) e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$") e.checkNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) @@ -101,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)) @@ -125,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") } diff --git a/cli/query/query.go b/cli/query/query.go index 48ffacc3d2..18ac8f0ba1 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -10,10 +10,14 @@ import ( "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/urfave/cli" @@ -49,6 +53,12 @@ func NewCommands() []cli.Command { Action: queryTx, Flags: queryTxFlags, }, + { + Name: "voter", + Usage: "Print NEO holder account state", + Action: queryVoter, + Flags: options.RPC, + }, }, }} } @@ -189,3 +199,59 @@ func queryCommittee(ctx *cli.Context) error { } 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) + 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 +} diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index 0289ae47c4..16f754ae46 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -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" @@ -74,18 +71,6 @@ func newValidatorCommands() []cli.Command { }, }, options.RPC...), }, - { - Name: "getstate", - Usage: "print NEO holder account state", - UsageText: "getstate -a ", - Action: getAccountState, - Flags: append([]cli.Flag{ - flags.AddressFlag{ - Name: "address, a", - Usage: "Address to get state of", - }, - }, options.RPC...), - }, } } @@ -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 -} diff --git a/docs/cli.md b/docs/cli.md index 0b3b6b8b52..dbc2fd3592 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -533,6 +533,17 @@ Key Votes Com 03d8d58d2257ca6cb14522b76513d4783f7d481801695893794c2186515c6de76f 0 false false ``` +#### Voter data +`query voter` returns additional data about NEO holder: amount of NEO he has, +candidate he voted for (if any) and block number of the last transactions +involving NEO on this account: +``` +$ ./bin/neo-go query voter -r http://localhost:20332 Nj91C8TxQSxW1jCE1ytFre6mg5qxTypg1Y + Voted: 0214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff01 (Nj91C8TxQSxW1jCE1ytFre6mg5qxTypg1Y) + Amount : 2000000 + Block: 3970 +``` + ### NEP-17 token functions `wallet nep17` contains a set of commands to use for NEP-17 tokens. From fde3cce93d523afff04519b7f8a3e477d86c518d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 22 Jul 2021 15:05:09 +0300 Subject: [PATCH 3/7] cli/query: output key in voter data People vote for keys, not addresses. --- cli/candidate_test.go | 2 +- cli/query/query.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/candidate_test.go b/cli/candidate_test.go index ffb0fd10b6..ac175458b6 100644 --- a/cli/candidate_test.go +++ b/cli/candidate_test.go @@ -82,7 +82,7 @@ func TestRegisterCandidate(t *testing.T) { e.Run(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addr, validatorPriv.Address()) - e.checkNextLine(t, "^\\s*Voted:\\s+"+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) diff --git a/cli/query/query.go b/cli/query/query.go index 18ac8f0ba1..17585d0a77 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -248,7 +248,7 @@ func queryVoter(ctx *cli.Context) error { } voted := "null" if st.VoteTo != nil { - voted = address.Uint160ToString(st.VoteTo.GetScriptHash()) + 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))) From a8a6c8c13d349d672065304935cf85972c9a73f1 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 22 Jul 2021 19:52:17 +0300 Subject: [PATCH 4/7] query: display zeroes for unknown addresses This: Voted: null Amount : 0 Block: 0 is better than this: failed to convert account state from stackitem: invalid stackitem length --- cli/query/query.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/query/query.go b/cli/query/query.go index 17585d0a77..7327198978 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -20,6 +20,7 @@ import ( "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" ) @@ -238,9 +239,11 @@ func queryVoter(ctx *cli.Context) error { 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) + 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 { From a188d20fd195c66289af22e7fdcc62806c65c27d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 22 Jul 2021 19:55:41 +0300 Subject: [PATCH 5/7] rpc: fix getstateheight result compatibility C#: "result" : { "localrootindex" : 11623, "validatedrootindex" : 11623 } Go: "result" : { "blockHeight" : 11627, "stateHeight" : 11627 } --- pkg/rpc/response/result/mpt.go | 4 ++-- pkg/rpc/server/server.go | 4 ++-- pkg/rpc/server/server_test.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/rpc/response/result/mpt.go b/pkg/rpc/response/result/mpt.go index 768c382fdc..8ba54defb4 100644 --- a/pkg/rpc/response/result/mpt.go +++ b/pkg/rpc/response/result/mpt.go @@ -10,8 +10,8 @@ import ( // StateHeight is a result of getstateheight RPC. type StateHeight struct { - BlockHeight uint32 `json:"blockHeight"` - StateHeight uint32 `json:"stateHeight"` + Local uint32 `json:"localrootindex"` + Validated uint32 `json:"validatedrootindex"` } // ProofWithKey represens key-proof pair. diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index aef2e09460..2dd03bc8bb 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -969,8 +969,8 @@ func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error) stateHeight = height - 1 } return &result.StateHeight{ - BlockHeight: height, - StateHeight: stateHeight, + Local: height, + Validated: stateHeight, }, nil } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 30dda685cc..c7f11b9441 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -324,8 +324,8 @@ var rpcTestCases = map[string][]rpcTestCase{ sh, ok := res.(*result.StateHeight) require.True(t, ok) - require.Equal(t, e.chain.BlockHeight(), sh.BlockHeight) - require.Equal(t, uint32(0), sh.StateHeight) + require.Equal(t, e.chain.BlockHeight(), sh.Local) + require.Equal(t, uint32(0), sh.Validated) }, }, }, From 7366d45985b2dda445da3ec4b892eef90f88840b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 22 Jul 2021 20:02:59 +0300 Subject: [PATCH 6/7] rpc: add GetStateHeight to client --- pkg/rpc/client/rpc.go | 12 ++++++++++++ pkg/rpc/client/rpc_test.go | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index bb0655f9d8..1792d6ce5c 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -374,6 +374,18 @@ func (c *Client) GetRawTransactionVerbose(hash util.Uint256) (*result.Transactio return resp, nil } +// GetStateHeight returns current validated and local node state height. +func (c *Client) GetStateHeight() (*result.StateHeight, error) { + var ( + params = request.NewRawParams() + resp = new(result.StateHeight) + ) + if err := c.performRequest("getstateheight", params, resp); err != nil { + return nil, err + } + return resp, nil +} + // GetStorageByID returns the stored value, according to the contract ID and the stored key. func (c *Client) GetStorageByID(id int32, key []byte) ([]byte, error) { return c.getStorage(request.NewRawParams(id, base64.StdEncoding.EncodeToString(key))) diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 55fff2543f..097543292d 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -680,6 +680,21 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, }, }, + "getstateheight": { + { + name: "positive", + invoke: func(c *Client) (interface{}, error) { + return c.GetStateHeight() + }, + serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"localrootindex":11646,"validatedrootindex":11645}}`, + result: func(c *Client) interface{} { + return &result.StateHeight{ + Local: 11646, + Validated: 11645, + } + }, + }, + }, "getstorage": { { name: "by hash, positive", From 8b0dfe135fd4097ad7f5ad963aed9cfd11aadf7e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 22 Jul 2021 21:10:32 +0300 Subject: [PATCH 7/7] cli/query: add height command --- cli/query/query.go | 33 +++++++++++++++++++++++++++++++++ cli/query_test.go | 9 +++++++++ docs/cli.md | 8 ++++++++ 3 files changed, 50 insertions(+) diff --git a/cli/query/query.go b/cli/query/query.go index 7327198978..8d15e121e9 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -48,6 +48,12 @@ func NewCommands() []cli.Command { Action: queryCommittee, Flags: options.RPC, }, + { + Name: "height", + Usage: "Get node height", + Action: queryHeight, + Flags: options.RPC, + }, { Name: "tx", Usage: "Query transaction status", @@ -201,6 +207,33 @@ func queryCommittee(ctx *cli.Context) error { 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 { diff --git a/cli/query_test.go b/cli/query_test.go index 0e6f8e9d26..5e4c9a516e 100644 --- a/cli/query_test.go +++ b/cli/query_test.go @@ -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) +} diff --git a/docs/cli.md b/docs/cli.md index dbc2fd3592..7dc4a9e027 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -466,6 +466,14 @@ You can also vote for candidates if you own NEO: ### Getting data from chain +#### Node height/validated height +`query height` returns the latest block and validated state height: +``` +$ ./bin/neo-go query height -r http://localhost:20332 +Latest block: 11926 +Validated state: 11926 +``` + #### Transaction status `query tx` provides convenient wrapper over RPC calls to query transaction status. ```