From dcef4128b8a3c157fe42a86a55c31380cee4668a Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 24 Jan 2014 14:10:02 -0600 Subject: [PATCH] Add support for getaddednodeinfo RPC command. This commit adds full support for the getaddednodeinfo RPC command including DNS lookups which abide by proxy/onion/tor rules when the DNS flag is specified. Note that it returns an array of strings when the DNS flag is not set which is different than the current version of bitcoind which is bugged and scheduled to be fixed per issue 3581 on the bitcoind issue tracker. --- rpcserver.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++- server.go | 28 +++++++++++++++-- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index dcacd28f..bd060858 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -54,6 +54,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "debuglevel": handleDebugLevel, "decoderawtransaction": handleDecodeRawTransaction, "decodescript": handleDecodeScript, + "getaddednodeinfo": handleGetAddedNodeInfo, "getbestblockhash": handleGetBestBlockHash, "getblock": handleGetBlock, "getblockcount": handleGetBlockCount, @@ -127,7 +128,6 @@ var rpcAskWallet = map[string]bool{ // Commands that are temporarily unimplemented. var rpcUnimplemented = map[string]bool{ - "getaddednodeinfo": true, "getblocktemplate": true, "getinfo": true, "getmininginfo": true, @@ -704,6 +704,89 @@ func handleDecodeScript(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { return reply, nil } +// handleGetAddedNodeInfo handles getaddednodeinfo commands. +func handleGetAddedNodeInfo(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { + c := cmd.(*btcjson.GetAddedNodeInfoCmd) + + // Retrieve a list of persistent (added) peers from the bitcoin server + // and filter the list of peer per the specified address (if any). + peers := s.server.AddedNodeInfo() + if c.Node != "" { + found := false + for i, peer := range peers { + if peer.addr == c.Node { + peers = peers[i : i+1] + found = true + } + } + if !found { + return nil, btcjson.Error{ + Code: -24, + Message: "Node has not been added.", + } + } + } + + // Without the dns flag, the result is just a slice of the adddresses + // as strings. + if !c.Dns { + results := make([]string, 0, len(peers)) + for _, peer := range peers { + results = append(results, peer.addr) + } + return results, nil + } + + // With the dns flag, the result is an array of JSON objects which + // include the result of DNS lookups for each peer. + results := make([]*btcjson.GetAddedNodeInfoResult, 0, len(peers)) + for _, peer := range peers { + // Set the "address" of the peer which could be an ip address + // or a domain name. + var result btcjson.GetAddedNodeInfoResult + result.AddedNode = peer.addr + isConnected := peer.Connected() + result.Connected = &isConnected + + // Split the address into host and port portions so we can do + // a DNS lookup against the host. When no port is specified in + // the address, just use the address as the host. + host, _, err := net.SplitHostPort(peer.addr) + if err != nil { + host = peer.addr + } + + // Do a DNS lookup for the address. If the lookup fails, just + // use the host. + var ipList []string + ips, err := btcdLookup(host) + if err == nil { + ipList = make([]string, 0, len(ips)) + for _, ip := range ips { + ipList = append(ipList, ip.String()) + } + } else { + ipList = make([]string, 1) + ipList[0] = host + } + + // Add the addresses and connection info to the result. + addrs := make([]btcjson.GetAddedNodeInfoResultAddr, 0, len(ipList)) + for _, ip := range ipList { + var addr btcjson.GetAddedNodeInfoResultAddr + addr.Address = ip + addr.Connected = "false" + if ip == host && peer.Connected() { + addr.Connected = directionString(peer.inbound) + } + addrs = append(addrs, addr) + } + result.Addresses = &addrs + results = append(results, &result) + } + return results, nil +} + // handleGetBestBlockHash implements the getbestblockhash command. func handleGetBestBlockHash(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { sha, _, err := s.server.db.NewestSha() diff --git a/server.go b/server.go index 599b2320..3b84d153 100644 --- a/server.go +++ b/server.go @@ -215,7 +215,7 @@ func forAllOutboundPeers(state *peerState, closure func(p *peer)) { } // forAllPeers is a helper function that runs closure on all peers known to -// peerSTate. +// peerState. func forAllPeers(state *peerState, closure func(p *peer)) { for e := state.peers.Front(); e != nil; e = e.Next() { closure(e.Value.(*peer)) @@ -296,20 +296,23 @@ type delNodeMsg struct { reply chan error } +type getAddedNodesMsg struct { + reply chan []*peer +} + // handleQuery is the central handler for all queries and commands from other // goroutines related to peer state. func (s *server) handleQuery(querymsg interface{}, state *peerState) { switch msg := querymsg.(type) { case getConnCountMsg: nconnected := 0 - forAllPeers(state, func(p *peer) { if p.Connected() { nconnected++ } }) - msg.reply <- nconnected + case getPeerInfoMsg: infos := make([]*PeerInfo, 0, state.peers.Len()) forAllPeers(state, func(p *peer) { @@ -346,6 +349,7 @@ func (s *server) handleQuery(querymsg interface{}, state *peerState) { infos = append(infos, info) }) msg.reply <- infos + case addNodeMsg: // XXX(oga) duplicate oneshots? if msg.permanent { @@ -387,6 +391,16 @@ func (s *server) handleQuery(querymsg interface{}, state *peerState) { } else { msg.reply <- errors.New("peer not found") } + + // Request a list of the persistent (added) peers. + case getAddedNodesMsg: + // Respond with a slice of the relavent peers. + peers := make([]*peer, 0, state.persistentPeers.Len()) + for e := state.persistentPeers.Front(); e != nil; e = e.Next() { + peer := e.Value.(*peer) + peers = append(peers, peer) + } + msg.reply <- peers } } @@ -639,6 +653,14 @@ func (s *server) ConnectedCount() int { return <-replyChan } +// AddedNodeInfo returns an array of btcjson.GetAddedNodeInfoResult structures +// describing the persistent (added) nodes. +func (s *server) AddedNodeInfo() []*peer { + replyChan := make(chan []*peer) + s.query <- getAddedNodesMsg{reply: replyChan} + return <-replyChan +} + // PeerInfo returns an array of PeerInfo structures describing all connected // peers. func (s *server) PeerInfo() []*PeerInfo {