diff --git a/cli/query/query.go b/cli/query/query.go index fec1529038..2bd29dcc9b 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -167,7 +167,7 @@ func queryCandidates(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - vals, err := c.GetNextBlockValidators() + vals, err := c.GetCandidates() if err != nil { return cli.NewExitError(err, 1) } @@ -177,21 +177,19 @@ func queryCandidates(ctx *cli.Context) error { } 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 - } - */ + 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), true))) + _, _ = 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()) diff --git a/docs/rpc.md b/docs/rpc.md index acf02bdf05..e4c3a08768 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -44,6 +44,7 @@ which would yield the response: | `getblockhash` | | `getblockheader` | | `getblockheadercount` | +| `getcandidates` | | `getcommittee` | | `getconnectioncount` | | `getcontractstate` | diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 0fa33b0d92..d08c7d03bb 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -557,6 +557,19 @@ func (c *Client) GetUnclaimedGas(address string) (result.UnclaimedGas, error) { return resp, nil } +// GetCandidates returns the current list of NEO candidate node with voting data and +// validator status. +func (c *Client) GetCandidates() ([]result.Candidate, error) { + var ( + params = request.NewRawParams() + resp = new([]result.Candidate) + ) + if err := c.performRequest("getcandidates", params, resp); err != nil { + return nil, err + } + return *resp, nil +} + // GetNextBlockValidators returns the current NEO consensus nodes information and voting data. func (c *Client) GetNextBlockValidators() ([]result.Validator, error) { var ( diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 8b2d92854d..b0876a4795 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -940,6 +940,21 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, }, }, + "getcandidates": { + { + name: "positive", + invoke: func(c *Client) (interface{}, error) { + return c.GetCandidates() + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":[{"publickey":"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2","votes":"0","active":true},{"publickey":"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e","votes":"0","active":true},{"publickey":"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699","votes":"0","active":true},{"publickey":"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62","votes":"0","active":true}]}`, + result: func(c *Client) interface{} { return []result.Candidate{} }, + check: func(t *testing.T, c *Client, uns interface{}) { + res, ok := uns.([]result.Candidate) + require.True(t, ok) + assert.Equal(t, 4, len(res)) + }, + }, + }, "getvalidators": { { name: "positive", @@ -1657,6 +1672,12 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ return c.GetUnclaimedGas("") }, }, + { + name: "getcandidates_unmarshalling_error", + invoke: func(c *Client) (interface{}, error) { + return c.GetCandidates() + }, + }, { name: "getvalidators_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { diff --git a/pkg/rpc/response/result/validator.go b/pkg/rpc/response/result/validator.go index c3d933560a..f5aafd5b59 100644 --- a/pkg/rpc/response/result/validator.go +++ b/pkg/rpc/response/result/validator.go @@ -13,6 +13,14 @@ type Validator struct { Votes int64 `json:"votes"` } +// Candidate represents a node participating in the governance elections, it's +// active when it's a validator (consensus node). +type Candidate struct { + PublicKey keys.PublicKey `json:"publickey"` + Votes int64 `json:"votes,string"` + Active bool `json:"active"` +} + type newValidator struct { PublicKey keys.PublicKey `json:"publickey"` Votes int64 `json:"votes"` diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 39b48df84b..8436369d8a 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -118,6 +118,7 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon "getblockheader": (*Server).getBlockHeader, "getblockheadercount": (*Server).getBlockHeaderCount, "getblocksysfee": (*Server).getBlockSysFee, + "getcandidates": (*Server).getCandidates, "getcommittee": (*Server).getCommittee, "getconnectioncount": (*Server).getConnectionCount, "getcontractstate": (*Server).getContractState, @@ -1538,6 +1539,29 @@ func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Erro }, nil } +// getCandidates returns the current list of candidates with their active/inactive voting status. +func (s *Server) getCandidates(_ request.Params) (interface{}, *response.Error) { + var validators keys.PublicKeys + + validators, err := s.chain.GetNextBlockValidators() + if err != nil { + return nil, response.NewRPCError("Can't get next block validators", err.Error()) + } + enrollments, err := s.chain.GetEnrollments() + if err != nil { + return nil, response.NewRPCError("Can't get enrollments", err.Error()) + } + var res = make([]result.Candidate, 0) + for _, v := range enrollments { + res = append(res, result.Candidate{ + PublicKey: *v.Key, + Votes: v.Votes.Int64(), + Active: validators.Contains(v.Key), + }) + } + return res, nil +} + // getNextBlockValidators returns validators for the next block with voting status. func (s *Server) getNextBlockValidators(_ request.Params) (interface{}, *response.Error) { var validators keys.PublicKeys diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 4ea259de8c..27bf5985bf 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -818,6 +818,14 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, }, + "getcandidates": { + { + params: "[]", + result: func(*executor) interface{} { + return &[]result.Candidate{} + }, + }, + }, "getnextblockvalidators": { { params: "[]",