From fff96610aa6a3d82280363ca682ea2745d7d842b Mon Sep 17 00:00:00 2001 From: Mikael Lindlof Date: Sun, 31 May 2020 13:30:50 +0100 Subject: [PATCH] rpc: Add getnodeaddresses JSON-RPC support Add NodeAddresses function to rpcserverConnManager interface for fetching known node addresses. --- btcjson/chainsvrcmds.go | 17 ++++++++++++++++ btcjson/chainsvrcmds_test.go | 26 ++++++++++++++++++++++++ btcjson/chainsvrresults.go | 10 +++++++++ rpcadapters.go | 9 +++++++++ rpcclient/net.go | 37 ++++++++++++++++++++++++++++++++++ rpcserver.go | 39 ++++++++++++++++++++++++++++++++++++ rpcserverhelp.go | 12 +++++++++++ 7 files changed, 150 insertions(+) diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index d751d9bc..793bb503 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -557,6 +557,22 @@ func NewGetNetworkHashPSCmd(numBlocks, height *int) *GetNetworkHashPSCmd { } } +// GetNodeAddressesCmd defines the getnodeaddresses JSON-RPC command. +type GetNodeAddressesCmd struct { + Count *int32 `jsonrpcdefault:"1"` +} + +// NewGetNodeAddressesCmd returns a new instance which can be used to issue a +// getnodeaddresses JSON-RPC command. +// +// The parameters which are pointers indicate they are optional. Passing nil +// for optional parameters will use the default value. +func NewGetNodeAddressesCmd(count *int32) *GetNodeAddressesCmd { + return &GetNodeAddressesCmd{ + Count: count, + } +} + // GetPeerInfoCmd defines the getpeerinfo JSON-RPC command. type GetPeerInfoCmd struct{} @@ -974,6 +990,7 @@ func init() { MustRegisterCmd("getnetworkinfo", (*GetNetworkInfoCmd)(nil), flags) MustRegisterCmd("getnettotals", (*GetNetTotalsCmd)(nil), flags) MustRegisterCmd("getnetworkhashps", (*GetNetworkHashPSCmd)(nil), flags) + MustRegisterCmd("getnodeaddresses", (*GetNodeAddressesCmd)(nil), flags) MustRegisterCmd("getpeerinfo", (*GetPeerInfoCmd)(nil), flags) MustRegisterCmd("getrawmempool", (*GetRawMempoolCmd)(nil), flags) MustRegisterCmd("getrawtransaction", (*GetRawTransactionCmd)(nil), flags) diff --git a/btcjson/chainsvrcmds_test.go b/btcjson/chainsvrcmds_test.go index b510b5ec..c7752d2b 100644 --- a/btcjson/chainsvrcmds_test.go +++ b/btcjson/chainsvrcmds_test.go @@ -753,6 +753,32 @@ func TestChainSvrCmds(t *testing.T) { Height: btcjson.Int(123), }, }, + { + name: "getnodeaddresses", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getnodeaddresses") + }, + staticCmd: func() interface{} { + return btcjson.NewGetNodeAddressesCmd(nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getnodeaddresses","params":[],"id":1}`, + unmarshalled: &btcjson.GetNodeAddressesCmd{ + Count: btcjson.Int32(1), + }, + }, + { + name: "getnodeaddresses optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getnodeaddresses", 10) + }, + staticCmd: func() interface{} { + return btcjson.NewGetNodeAddressesCmd(btcjson.Int32(10)) + }, + marshalled: `{"jsonrpc":"1.0","method":"getnodeaddresses","params":[10],"id":1}`, + unmarshalled: &btcjson.GetNodeAddressesCmd{ + Count: btcjson.Int32(10), + }, + }, { name: "getpeerinfo", newCmd: func() (interface{}, error) { diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 5699c53c..eb9a0060 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -365,6 +365,16 @@ type GetNetworkInfoResult struct { Warnings string `json:"warnings"` } +// GetNodeAddressesResult models the data returned from the getnodeaddresses +// command. +type GetNodeAddressesResult struct { + // Timestamp in seconds since epoch (Jan 1 1970 GMT) keeping track of when the node was last seen + Time int64 `json:"time"` + Services uint64 `json:"services"` // The services offered + Address string `json:"address"` // The address of the node + Port uint16 `json:"port"` // The port of the node +} + // GetPeerInfoResult models the data returned from the getpeerinfo command. type GetPeerInfoResult struct { ID int32 `json:"id"` diff --git a/rpcadapters.go b/rpcadapters.go index 7d2c3a14..ddcdf79b 100644 --- a/rpcadapters.go +++ b/rpcadapters.go @@ -223,6 +223,15 @@ func (cm *rpcConnManager) RelayTransactions(txns []*mempool.TxDesc) { cm.server.relayTransactions(txns) } +// NodeAddresses returns an array consisting node addresses which can +// potentially be used to find new nodes in the network. +// +// This function is safe for concurrent access and is part of the +// rpcserverConnManager interface implementation. +func (cm *rpcConnManager) NodeAddresses() []*wire.NetAddress { + return cm.server.addrManager.AddressCache() +} + // rpcSyncMgr provides a block manager for use with the RPC server and // implements the rpcserverSyncManager interface. type rpcSyncMgr struct { diff --git a/rpcclient/net.go b/rpcclient/net.go index 6eb73625..8f781e4e 100644 --- a/rpcclient/net.go +++ b/rpcclient/net.go @@ -281,6 +281,43 @@ func (c *Client) GetNetworkInfo() (*btcjson.GetNetworkInfoResult, error) { return c.GetNetworkInfoAsync().Receive() } +// FutureGetNodeAddressesResult is a future promise to deliver the result of a +// GetNodeAddressesAsync RPC invocation (or an applicable error). +type FutureGetNodeAddressesResult chan *response + +// Receive waits for the response promised by the future and returns data about +// known node addresses. +func (r FutureGetNodeAddressesResult) Receive() ([]btcjson.GetNodeAddressesResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + // Unmarshal result as an array of getnodeaddresses result objects. + var nodeAddresses []btcjson.GetNodeAddressesResult + err = json.Unmarshal(res, &nodeAddresses) + if err != nil { + return nil, err + } + + return nodeAddresses, nil +} + +// GetNodeAddressesAsync returns an instance of a type that can be used to get the +// result of the RPC at some future time by invoking the Receive function on the +// returned instance. +// +// See GetNodeAddresses for the blocking version and more details. +func (c *Client) GetNodeAddressesAsync(count *int32) FutureGetNodeAddressesResult { + cmd := btcjson.NewGetNodeAddressesCmd(count) + return c.sendCmd(cmd) +} + +// GetNodeAddresses returns data about known node addresses. +func (c *Client) GetNodeAddresses(count *int32) ([]btcjson.GetNodeAddressesResult, error) { + return c.GetNodeAddressesAsync(count).Receive() +} + // FutureGetPeerInfoResult is a future promise to deliver the result of a // GetPeerInfoAsync RPC invocation (or an applicable error). type FutureGetPeerInfoResult chan *response diff --git a/rpcserver.go b/rpcserver.go index 7e000041..d7b68bf8 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -156,6 +156,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "getmininginfo": handleGetMiningInfo, "getnettotals": handleGetNetTotals, "getnetworkhashps": handleGetNetworkHashPS, + "getnodeaddresses": handleGetNodeAddresses, "getpeerinfo": handleGetPeerInfo, "getrawmempool": handleGetRawMempool, "getrawtransaction": handleGetRawTransaction, @@ -2477,6 +2478,40 @@ func handleGetNetworkHashPS(s *rpcServer, cmd interface{}, closeChan <-chan stru return hashesPerSec.Int64(), nil } +// handleGetNodeAddresses implements the getnodeaddresses command. +func handleGetNodeAddresses(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.GetNodeAddressesCmd) + + count := int32(1) + if c.Count != nil { + count = *c.Count + if count <= 0 { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidParameter, + Message: "Address count out of range", + } + } + } + + nodes := s.cfg.ConnMgr.NodeAddresses() + if n := int32(len(nodes)); n < count { + count = n + } + + addresses := make([]*btcjson.GetNodeAddressesResult, 0, count) + for _, node := range nodes[:count] { + address := &btcjson.GetNodeAddressesResult{ + Time: node.Timestamp.Unix(), + Services: uint64(node.Services), + Address: node.IP.String(), + Port: node.Port, + } + addresses = append(addresses, address) + } + + return addresses, nil +} + // handleGetPeerInfo implements the getpeerinfo command. func handleGetPeerInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { peers := s.cfg.ConnMgr.ConnectedPeers() @@ -4298,6 +4333,10 @@ type rpcserverConnManager interface { // RelayTransactions generates and relays inventory vectors for all of // the passed transactions to all connected peers. RelayTransactions(txns []*mempool.TxDesc) + + // NodeAddresses returns an array consisting node addresses which can + // potentially be used to find new nodes in the network. + NodeAddresses() []*wire.NetAddress } // rpcserverSyncManager represents a sync manager for use with the RPC server. diff --git a/rpcserverhelp.go b/rpcserverhelp.go index bd0f8fd7..ca6d2e79 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -452,6 +452,17 @@ var helpDescsEnUS = map[string]string{ "getnettotalsresult-totalbytessent": "Total bytes sent", "getnettotalsresult-timemillis": "Number of milliseconds since 1 Jan 1970 GMT", + // GetNodeAddressesResult help. + "getnodeaddressesresult-time": "Timestamp in seconds since epoch (Jan 1 1970 GMT) keeping track of when the node was last seen", + "getnodeaddressesresult-services": "The services offered", + "getnodeaddressesresult-address": "The address of the node", + "getnodeaddressesresult-port": "The port of the node", + + // GetNodeAddressesCmd help. + "getnodeaddresses--synopsis": "Return known addresses which can potentially be used to find new nodes in the network", + "getnodeaddresses-count": "How many addresses to return. Limited to the smaller of 2500 or 23% of all known addresses", + "getnodeaddresses--result0": "List of node addresses", + // GetPeerInfoResult help. "getpeerinforesult-id": "A unique node ID", "getpeerinforesult-addr": "The ip address and port of the peer", @@ -726,6 +737,7 @@ var rpcResultTypes = map[string][]interface{}{ "getmininginfo": {(*btcjson.GetMiningInfoResult)(nil)}, "getnettotals": {(*btcjson.GetNetTotalsResult)(nil)}, "getnetworkhashps": {(*int64)(nil)}, + "getnodeaddresses": {(*[]btcjson.GetNodeAddressesResult)(nil)}, "getpeerinfo": {(*[]btcjson.GetPeerInfoResult)(nil)}, "getrawmempool": {(*[]string)(nil), (*btcjson.GetRawMempoolVerboseResult)(nil)}, "getrawtransaction": {(*string)(nil), (*btcjson.TxRawResult)(nil)},