/ rpc.go
rpc.go
 1  package main
 2  
 3  import (
 4  	"bytes"
 5  	"context"
 6  	"fmt"
 7  	"io"
 8  	"net/http"
 9  	"time"
10  
11  	"github.com/ethereum/go-ethereum/p2p"
12  )
13  
14  // RPCClient defines subset of client that
15  // can call needed methods to geth's RPC server.
16  type RPCClient interface {
17  	AdminPeers(ctx context.Context, ip string) ([]*Node, error)
18  	NodeInfo(ctx context.Context, ip string) (*p2p.NodeInfo, error)
19  }
20  
21  // HTTPRPCClient implements RPCClient for
22  // HTTP transport.
23  type HTTPRPCClient struct {
24  	client *http.Client
25  }
26  
27  const HTTPRPCTimeout = 3 * time.Second
28  
29  // NewHTTPRPCClient creates new HTTP RPC client for eth JSON-RPC server.
30  func NewHTTPRPCClient() *HTTPRPCClient {
31  	return &HTTPRPCClient{
32  		client: &http.Client{
33  			Timeout: ConsulTimeout,
34  		},
35  	}
36  }
37  
38  // AdminPeers executes `admin_peers` RPC call and parses the response.
39  // Satisfies RPCClient interface.
40  func (h *HTTPRPCClient) AdminPeers(ctx context.Context, ip string) ([]*Node, error) {
41  	r, err := h.postMethod(ctx, ip, "admin_peers")
42  	if err != nil {
43  		return nil, fmt.Errorf("rpc admin_peers: %s", err)
44  	}
45  	defer r.Close()
46  
47  	nodes, err := ParsePeersResponse(r)
48  	if err != nil {
49  		return nil, fmt.Errorf("get admin peers: %s", err)
50  	}
51  
52  	return PeersToNodes(nodes)
53  }
54  
55  // NodeInfo executes `admin_nodeInfo` RPC call and parses the response.
56  // Satisfies RPCClient interface.
57  func (h *HTTPRPCClient) NodeInfo(ctx context.Context, ip string) (*p2p.NodeInfo, error) {
58  	r, err := h.postMethod(ctx, ip, "admin_nodeInfo")
59  	if err != nil {
60  		return nil, fmt.Errorf("rpc admin_nodeInfo: %s", err)
61  	}
62  	defer r.Close()
63  
64  	nodeInfo, err := ParseNodeInfoResponse(r)
65  	if err != nil {
66  		return nil, fmt.Errorf("get node info: %s", err)
67  	}
68  
69  	return nodeInfo, err
70  }
71  
72  // postMethod performs POST RPC request for single method RPC calls without params.
73  // it reads body and return the whole answer.
74  func (h *HTTPRPCClient) postMethod(ctx context.Context, ip, method string) (io.ReadCloser, error) {
75  	payload := fmt.Sprintf("{\"jsonrpc\":\"2.0\",\"method\":\"%s\",\"params\":[],\"id\":1}", method)
76  	data := bytes.NewBufferString(payload)
77  	url := "http://" + ip
78  	req, err := http.NewRequest("POST", url, data)
79  	if err != nil {
80  		return nil, fmt.Errorf("request: %s", err)
81  	}
82  
83  	req = req.WithContext(ctx)
84  	req.Header.Set("Content-Type", "application/json")
85  
86  	resp, err := h.client.Do(req)
87  	if err != nil {
88  		return nil, fmt.Errorf("POST RPC request: %s", err)
89  	}
90  
91  	return resp.Body, nil
92  }