/ 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 }