diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..645a145 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +clustervis diff --git a/cluster.go b/cluster.go index 90124d9..92ff5a2 100644 --- a/cluster.go +++ b/cluster.go @@ -1,92 +1,77 @@ package main import ( - "encoding/json" - "errors" - "fmt" - "io" "log" - "net/http" + "sort" ) // ClusterSource represents knowledge source of // cluster configuration. type ClusterSource interface { - Nodes(dc, tag string) ([]*Node, error) + IPs(dc, tag string) ([]string, error) } -// ConsulSource implements Consul clients that fetches -// actual information about hosts in cluster. -type ConsulSource struct { - hostport string +// Fetched implements data fetching from multiple sources +// to get the needed peers data. +type Fetcher struct { + cluster ClusterSource + rpc RPCClient } -// NewConsulSource creates new Consul source. It doesn't attempt -// to connect or verify if address is correct. -func NewConsulSource(hostport string) ClusterSource { - return &ConsulSource{ - hostport: hostport, +// NewFetcher creates new Fetcher. +func NewFetcher(cluster ClusterSource, rpc RPCClient) *Fetcher { + return &Fetcher{ + cluster: cluster, + rpc: rpc, } } -// Node returns the list of nodes for the given datacentre 'dc' and tag. -// Satisfies ClusterSource interface. -func (c *ConsulSource) Nodes(dc, tag string) ([]*Node, error) { - url := fmt.Sprintf("http://%s/v1/catalog/service/statusd-rpc?tag=%s", c.hostport, tag) - resp, err := http.Get(url) +// Nodes returns the list of nodes for the given datacentre 'dc' and tag. +func (f *Fetcher) Nodes(dc, tag string) ([]*ClusterNode, error) { + ips, err := f.cluster.IPs(dc, tag) if err != nil { - return nil, fmt.Errorf("http call failed: %s", err) + return nil, err } - defer resp.Body.Close() - ips, err := ParseConsulResponse(resp.Body) - if err != nil { - return nil, fmt.Errorf("get nodes list: %s", err) + var ret []*ClusterNode + for _, ip := range ips { + nodeInfo, err := f.rpc.NodeInfo(ip) + if err != nil { + return nil, err + } + node := NewClusterNode(ip, nodeInfo) + ret = append(ret, node) + } + + return ret, nil +} + +// NodePeers runs `admin_peers` command for each node. +func (f *Fetcher) NodePeers(nodes []*ClusterNode) ([]*Node, []*Link, error) { + m := make(map[string]*Node) + var links []*Link + for _, node := range nodes { + // TODO: run concurrently + peers, err := f.rpc.AdminPeers(node.IP) + if err != nil { + log.Printf("[ERROR] Failed to get peers from %s\n", node.IP) + continue + } + + for _, peer := range peers { + m[peer.ID()] = peer + + link := NewLink(node.ID, peer.ID()) + links = append(links, link) + } } var ret []*Node - for _, ip := range ips { - // TODO: run concurrently - rpc := NewHTTPRPCClient(ip) - nodes, err := rpc.AdminPeers() - if err != nil { - log.Println("[ERROR] Failed to get peers from %s", ip) - continue - } - ret = append(ret, nodes...) + for _, node := range m { + ret = append(ret, node) } - - return ret, nil - - return nil, errors.New("TBD") -} - -// ConsulResponse describes response structure from Consul. -type ConsulResponse []*ConsulNodeInfo - -// ConsulNodeInfo describes single node as reported by Consul. -type ConsulNodeInfo struct { - ServiceAddress string - ServicePort string -} - -// ToIP converts ConsulNodeInfo fields into hostport representation of IP. -func (c *ConsulNodeInfo) ToIP() string { - return fmt.Sprintf("%s:%s", c.ServiceAddress, c.ServicePort) -} - -// ParseConsulResponse parses JSON output from Consul response with -// the list of service and extracts IP addresses. -func ParseConsulResponse(r io.Reader) ([]string, error) { - var resp ConsulResponse - err := json.NewDecoder(r).Decode(&resp) - if err != nil { - return nil, fmt.Errorf("unmarshal Consul JSON response: %s", err) - } - - ret := make([]string, len(resp)) - for i := range resp { - ret[i] = resp[i].ToIP() - } - return ret, nil + sort.Slice(ret, func(i, j int) bool { + return ret[i].ID() < ret[j].ID() + }) + return ret, links, nil } diff --git a/cluster_mock.go b/cluster_mock.go index f387be7..9efa50d 100644 --- a/cluster_mock.go +++ b/cluster_mock.go @@ -2,8 +2,6 @@ package main import ( "bytes" - "fmt" - "log" ) // MockConsulSource implements ClusterSource for local @@ -18,26 +16,9 @@ func NewMockConsulSource() ClusterSource { // Node returns the list of mock nodes for the given datacentre 'dc' and tag. // Satisfies ClusterSource interface. -func (c *MockConsulSource) Nodes(dc, tag string) ([]*Node, error) { +func (c *MockConsulSource) IPs(dc, tag string) ([]string, error) { r := bytes.NewBufferString(mockClusterIPsJSON) - ips, err := ParseConsulResponse(r) - if err != nil { - return nil, fmt.Errorf("get nodes list: %s", err) - } - - var ret []*Node - for _, ip := range ips { - // TODO: run concurrently - rpc := NewMockRPCClient(ip) - nodes, err := rpc.AdminPeers() - if err != nil { - log.Println("[ERROR] Failed to get peers from %s", ip) - continue - } - ret = append(ret, nodes...) - } - - return ret, nil + return ParseConsulResponse(r) } const mockClusterIPsJSON = `[{"ID":"edaddefa-f894-703a-69db-6158dd56aa5a","Node":"mail-01.do-ams3.eth.beta","Address":"206.189.243.162","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.162","wan":"206.189.243.162"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-mail-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","mail","rpc"],"ServiceAddress":"10.1.0.13","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":719808,"ModifyIndex":719808},{"ID":"d62fa419-49f7-32e5-52f9-64478b4e104b","Node":"mail-02.do-ams3.eth.beta","Address":"206.189.243.169","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.169","wan":"206.189.243.169"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-mail-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","mail","rpc"],"ServiceAddress":"10.1.0.14","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":718197,"ModifyIndex":718197},{"ID":"ad5cbd92-b28e-d5fa-3e49-17674eb91de9","Node":"mail-03.do-ams3.eth.beta","Address":"206.189.243.168","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.168","wan":"206.189.243.168"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-mail-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","mail","rpc"],"ServiceAddress":"10.1.0.12","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":725592,"ModifyIndex":725592},{"ID":"3b6ae627-d8c2-c39a-0628-0792ad1c46e4","Node":"node-01.do-ams3.eth.beta","Address":"206.189.243.176","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.176","wan":"206.189.243.176"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.1.99","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":719848,"ModifyIndex":719848},{"ID":"3b6ae627-d8c2-c39a-0628-0792ad1c46e4","Node":"node-01.do-ams3.eth.beta","Address":"206.189.243.176","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.176","wan":"206.189.243.176"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-2","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.1.99","ServiceMeta":{},"ServicePort":8547,"ServiceEnableTagOverride":true,"CreateIndex":719849,"ModifyIndex":719849},{"ID":"79eacab7-006c-1a19-0bfd-046f874ec1ec","Node":"node-02.do-ams3.eth.beta","Address":"206.189.243.178","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.178","wan":"206.189.243.178"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.9","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":718374,"ModifyIndex":718374},{"ID":"79eacab7-006c-1a19-0bfd-046f874ec1ec","Node":"node-02.do-ams3.eth.beta","Address":"206.189.243.178","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.178","wan":"206.189.243.178"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-2","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.9","ServiceMeta":{},"ServicePort":8547,"ServiceEnableTagOverride":true,"CreateIndex":718375,"ModifyIndex":718375},{"ID":"dd349135-a34a-1452-b1ea-b00987d588f2","Node":"node-03.do-ams3.eth.beta","Address":"206.189.243.179","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.179","wan":"206.189.243.179"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.6","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":719868,"ModifyIndex":719868},{"ID":"dd349135-a34a-1452-b1ea-b00987d588f2","Node":"node-03.do-ams3.eth.beta","Address":"206.189.243.179","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.179","wan":"206.189.243.179"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-2","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.6","ServiceMeta":{},"ServicePort":8547,"ServiceEnableTagOverride":true,"CreateIndex":719869,"ModifyIndex":719869},{"ID":"3e7a4660-ea9f-1d5f-f3e8-94f002f3a4cc","Node":"node-04.do-ams3.eth.beta","Address":"206.189.243.171","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.171","wan":"206.189.243.171"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.7","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":719858,"ModifyIndex":719858},{"ID":"3e7a4660-ea9f-1d5f-f3e8-94f002f3a4cc","Node":"node-04.do-ams3.eth.beta","Address":"206.189.243.171","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.171","wan":"206.189.243.171"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-2","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.7","ServiceMeta":{},"ServicePort":8547,"ServiceEnableTagOverride":true,"CreateIndex":719859,"ModifyIndex":719859},{"ID":"3d75a106-6bd6-027f-b7bd-371a01739d32","Node":"node-05.do-ams3.eth.beta","Address":"206.189.243.172","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.172","wan":"206.189.243.172"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.4","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":719817,"ModifyIndex":719817},{"ID":"3d75a106-6bd6-027f-b7bd-371a01739d32","Node":"node-05.do-ams3.eth.beta","Address":"206.189.243.172","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.172","wan":"206.189.243.172"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-2","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.4","ServiceMeta":{},"ServicePort":8547,"ServiceEnableTagOverride":true,"CreateIndex":719818,"ModifyIndex":719818},{"ID":"7c395574-9e32-41d8-05a9-8898c07f65a6","Node":"node-06.do-ams3.eth.beta","Address":"206.189.243.177","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.177","wan":"206.189.243.177"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-1","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.5","ServiceMeta":{},"ServicePort":8546,"ServiceEnableTagOverride":true,"CreateIndex":719842,"ModifyIndex":719842},{"ID":"7c395574-9e32-41d8-05a9-8898c07f65a6","Node":"node-06.do-ams3.eth.beta","Address":"206.189.243.177","Datacenter":"do-ams3","TaggedAddresses":{"lan":"206.189.243.177","wan":"206.189.243.177"},"NodeMeta":{"consul-network-segment":"","env":"eth","stage":"beta"},"ServiceID":"statusd-whisper-rpc-2","ServiceName":"statusd-rpc","ServiceTags":["eth.beta","statusd","whisper","rpc"],"ServiceAddress":"10.1.0.5","ServiceMeta":{},"ServicePort":8547,"ServiceEnableTagOverride":true,"CreateIndex":719843,"ModifyIndex":719843}]` diff --git a/consul.go b/consul.go new file mode 100644 index 0000000..11206b6 --- /dev/null +++ b/consul.go @@ -0,0 +1,67 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +// Consul implements ClusterSource for Consul. +type Consul struct { + hostport string +} + +// NewConsul creates new Consul source. It doesn't attempt +// to connect or verify if address is correct. +func NewConsul(hostport string) *Consul { + return &Consul{ + hostport: hostport, + } +} + +// IPs returns the list of IPs for the given datacenter and tag from Consul. +func (c *Consul) IPs(dc, tag string) ([]string, error) { + url := fmt.Sprintf("http://%s/v1/catalog/service/statusd-rpc?tag=%s", c.hostport, tag) + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("http call failed: %s", err) + } + defer resp.Body.Close() + + ips, err := ParseConsulResponse(resp.Body) + if err != nil { + return nil, fmt.Errorf("get nodes list: %s", err) + } + return ips, err +} + +// ConsulResponse describes response structure from Consul. +type ConsulResponse []*ConsulNodeInfo + +// ConsulNodeInfo describes single node as reported by Consul. +type ConsulNodeInfo struct { + ServiceAddress string + ServicePort int +} + +// ToIP converts ConsulNodeInfo fields into hostport representation of IP. +func (c *ConsulNodeInfo) ToIP() string { + return fmt.Sprintf("%s:%d", c.ServiceAddress, c.ServicePort) +} + +// ParseConsulResponse parses JSON output from Consul response with +// the list of service and extracts IP addresses. +func ParseConsulResponse(r io.Reader) ([]string, error) { + var resp ConsulResponse + err := json.NewDecoder(r).Decode(&resp) + if err != nil { + return nil, fmt.Errorf("unmarshal Consul JSON response: %s", err) + } + + ret := make([]string, len(resp)) + for i := range resp { + ret[i] = resp[i].ToIP() + } + return ret, nil +} diff --git a/jsonrpc.go b/jsonrpc.go index 3e91dbf..8800a85 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -7,17 +7,36 @@ import ( "github.com/ethereum/go-ethereum/p2p" ) -// JSONRPCResponse represents JSON-RPC response for `admin_peers` command from +// PeersResponse represents JSON-RPC response for `admin_peers` command from // geth instance. -type JSONRPCResponse struct { +type PeersResponse struct { Version string `json:"jsonrpc"` Id interface{} `json:"id,omitempty"` Result []*p2p.PeerInfo `json:"result"` } -// ParseResponse parses JSON-RPC 'admin_peers' response from reader r. -func ParseResponse(r io.Reader) ([]*p2p.PeerInfo, error) { - var resp JSONRPCResponse +// ParsePeersResponse parses JSON-RPC 'admin_peers' response from reader r. +func ParsePeersResponse(r io.Reader) ([]*p2p.PeerInfo, error) { + var resp PeersResponse + err := json.NewDecoder(r).Decode(&resp) + if err != nil { + return nil, err + } + + return resp.Result, nil +} + +// NodeInfoResponse represents JSON-RPC response for `admin_nodeInfo` command from +// geth instance. +type NodeInfoResponse struct { + Version string `json:"jsonrpc"` + Id interface{} `json:"id,omitempty"` + Result *p2p.NodeInfo `json:"result"` +} + +// ParseNodeInfoResponse parses JSON-RPC 'admin_nodeInfo' response from reader r. +func ParseNodeInfoResponse(r io.Reader) (*p2p.NodeInfo, error) { + var resp NodeInfoResponse err := json.NewDecoder(r).Decode(&resp) if err != nil { return nil, err diff --git a/main.go b/main.go index 3892f57..ea6c56f 100644 --- a/main.go +++ b/main.go @@ -6,23 +6,72 @@ import ( "log" "github.com/divan/graph-experiments/graph" + "github.com/ethereum/go-ethereum/p2p" ) func main() { - var consulAddr = flag.String("consul", "localhost:8500", "Host:port for consul address to query") + var ( + consulAddr = flag.String("consul", "localhost:8500", "Host:port for consul address to query") + testMode = flag.Bool("test", false, "Test mode (use local test data)") + ) flag.Parse() - cluster := NewConsulSource(*consulAddr) - nodes, err := cluster.Nodes("", "eth.beta") + var rpc RPCClient + rpc = NewHTTPRPCClient() + if *testMode { + rpc = NewMockRPCClient() + } + var cluster ClusterSource + cluster = NewConsul(*consulAddr) + if *testMode { + cluster = NewMockConsulSource() + } + + fetcher := NewFetcher(cluster, rpc) + nodes, err := fetcher.Nodes("", "eth.beta") if err != nil { - log.Fatalf("Getting list of nodes: %s", err) + log.Fatalf("Getting list of ips: %s", err) + } + + peers, links, err := fetcher.NodePeers(nodes) + if err != nil { + log.Fatalf("Getting list of ips: %s", err) } g := graph.NewGraph() - for _, node := range nodes { - _ = node - //g.AddNode(node) + for _, peer := range peers { + _ = peer + //AddPeer(g, "", node.PI) + } + for _, link := range links { + _ = link + //AddPeer(g, "", node.PI) } fmt.Printf("Graph has %d nodes and %d links\n", len(g.Nodes()), len(g.Links())) } + +func AddPeer(g *graph.Graph, fromID string, to *p2p.PeerInfo) { + toID := to.ID + addNode(g, fromID, false) + //addNode(g, toID, isClient(to.Name)) + addNode(g, toID, false) + + if g.LinkExistsByID(fromID, toID) { + return + } + if to.Network.Inbound == false { + g.AddLinkByIDs(fromID, toID) + } else { + g.AddLinkByIDs(toID, fromID) + } +} + +func addNode(g *graph.Graph, id string, client bool) { + if _, err := g.NodeByID(id); err == nil { + // already exists + return + } + //node := NewNode(id) + //g.AddNode(node) +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..264ebf3 --- /dev/null +++ b/main_test.go @@ -0,0 +1,38 @@ +package main + +import "testing" + +func TestGraphCreate(t *testing.T) { + cluster := NewMockConsulSource() + rpc := NewMockRPCClient() + + f := NewFetcher(cluster, rpc) + + nodes, err := f.Nodes("", "eth.beta") + if err != nil { + t.Fatal(err) + } + + got := len(nodes) + expected := 15 + if got != expected { + t.Fatalf("Expected %d nodes, got %d", expected, got) + } + + peers, links, err := f.NodePeers(nodes) + if err != nil { + t.Fatal(err) + } + + got = len(peers) + expected = 49 + if got != expected { + t.Fatalf("Expected %d nodes, got %d", expected, got) + } + + got = len(links) + expected = 200 + if got != expected { + t.Fatalf("Expected %d links, got %d", expected, got) + } +} diff --git a/node.go b/node.go index 29bbc09..e552b62 100644 --- a/node.go +++ b/node.go @@ -2,15 +2,39 @@ package main import "github.com/ethereum/go-ethereum/p2p" -// Node represents single node information. +// Node represents single node information to be used in Graph. type Node struct { - *p2p.PeerInfo + ID_ string `json:"ID"` + Group_ int `json:"Group"` } // NewNode creates new Node object for the given peerinfo. func NewNode(peer *p2p.PeerInfo) *Node { return &Node{ - PeerInfo: peer, + ID_: peer.ID, + Group_: clientGroup(peer.Name), + } +} + +// clientGroup returns group id based in server type. +func clientGroup(name string) int { + // TODO: implement + return 1 +} + +// ClusterNode represents single cluster node information. +type ClusterNode struct { + IP string + ID string + Type string // name field in JSON (statusd or statusIM) +} + +// NewClusterNode creates new Node object for the given peerinfo. +func NewClusterNode(ip string, peer *p2p.NodeInfo) *ClusterNode { + return &ClusterNode{ + IP: ip, + ID: peer.ID, + Type: peer.Name, } } @@ -22,3 +46,23 @@ func PeersToNodes(peers []*p2p.PeerInfo) ([]*Node, error) { } return ret, nil } + +// ID returns ID of the node. Satisfies graph.Node interface. +func (n *Node) ID() string { + return n.ID_ +} + +// Group returns group of the node. Satisfies graph.Node interface. +func (n *Node) Group() int { + return n.Group_ +} + +// Link represents link between two nodes. +type Link struct { + FromID, ToID string +} + +// NewLinks creates link for the given IDs. +func NewLink(fromID, toID string) *Link { + return &Link{fromID, toID} +} diff --git a/rpc.go b/rpc.go index 6ac3f32..f7cbe17 100644 --- a/rpc.go +++ b/rpc.go @@ -4,41 +4,59 @@ import ( "bytes" "fmt" "net/http" + + "github.com/ethereum/go-ethereum/p2p" ) // RPCClient defines subset of client that // can call needed methods to geth's RPC server. type RPCClient interface { - AdminPeers() ([]*Node, error) + AdminPeers(ip string) ([]*Node, error) + NodeInfo(ip string) (*p2p.NodeInfo, error) } // HTTPRPCClient implements RPCClient for // HTTP transport. type HTTPRPCClient struct { - IP string } // NewHTTPRPCClient creates new HTTP RPC client for eth JSON-RPC server. -func NewHTTPRPCClient(ip string) *HTTPRPCClient { - return &HTTPRPCClient{ - IP: ip, - } +func NewHTTPRPCClient() *HTTPRPCClient { + return &HTTPRPCClient{} } // AdminPeers executes `admin_peers` RPC call and parses the response. // Satisfies RPCClient interface. -func (h *HTTPRPCClient) AdminPeers() ([]*Node, error) { +func (h *HTTPRPCClient) AdminPeers(ip string) ([]*Node, error) { data := bytes.NewBufferString(`{"jsonrpc":"2.0","method":"admin_peers","params":[],"id":1}`) - resp, err := http.Post("https://"+h.IP, "application/json", data) + resp, err := http.Post("https://"+ip, "application/json", data) if err != nil { return nil, fmt.Errorf("POST RPC request: %s", err) } defer resp.Body.Close() - nodes, err := ParseResponse(resp.Body) + nodes, err := ParsePeersResponse(resp.Body) if err != nil { return nil, fmt.Errorf("get admin peers: %s", err) } return PeersToNodes(nodes) } + +// NodeInfo executes `admin_nodeInfo` RPC call and parses the response. +// Satisfies RPCClient interface. +func (h *HTTPRPCClient) NodeInfo(ip string) (*p2p.NodeInfo, error) { + data := bytes.NewBufferString(`{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":1}`) + resp, err := http.Post("https://"+ip, "application/json", data) + if err != nil { + return nil, fmt.Errorf("POST RPC request: %s", err) + } + defer resp.Body.Close() + + nodeInfo, err := ParseNodeInfoResponse(resp.Body) + if err != nil { + return nil, fmt.Errorf("get node info: %s", err) + } + + return nodeInfo, err +} diff --git a/rpc_mock.go b/rpc_mock.go index 243e77a..0a7b2b1 100644 --- a/rpc_mock.go +++ b/rpc_mock.go @@ -3,25 +3,23 @@ package main import ( "bytes" "fmt" + + "github.com/ethereum/go-ethereum/p2p" ) // MockRPCClient implements mock for RPCClient. -type MockRPCClient struct { - IP string -} +type MockRPCClient struct{} // NewMockRPCClient creates new mocked RPC client for eth JSON-RPC server. -func NewMockRPCClient(ip string) *MockRPCClient { - return &MockRPCClient{ - IP: ip, - } +func NewMockRPCClient() *MockRPCClient { + return &MockRPCClient{} } // AdminPeers simulates call to `admin_peers` RPC and parses the response. // Satisfies RPCClient interface. -func (h *MockRPCClient) AdminPeers() ([]*Node, error) { - r := bytes.NewBufferString(mockPeers[h.IP]) - nodes, err := ParseResponse(r) +func (h *MockRPCClient) AdminPeers(ip string) ([]*Node, error) { + r := bytes.NewBufferString(mockPeers[ip]) + nodes, err := ParsePeersResponse(r) if err != nil { return nil, fmt.Errorf("get admin peers: %s", err) } @@ -29,6 +27,18 @@ func (h *MockRPCClient) AdminPeers() ([]*Node, error) { return PeersToNodes(nodes) } +// AdminPeers simulates call to `admin_peers` RPC and parses the response. +// Satisfies RPCClient interface. +func (h *MockRPCClient) NodeInfo(ip string) (*p2p.NodeInfo, error) { + r := bytes.NewBufferString(mockInfo[ip]) + nodeInfo, err := ParseNodeInfoResponse(r) + if err != nil { + return nil, fmt.Errorf("get node info: %s", err) + } + + return nodeInfo, nil +} + var mockPeers = map[string]string{ "10.1.0.13:8546": `{"jsonrpc":"2.0","id":1,"result":[{"id":"1d193635e015918fb85bbaf774863d12f65d70c6977506187ef04420d74ec06c9e8f0dcb57ea042f85df87433dab17a1260ed8dde1bdf9d6d5d2de4b7bf8e993","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.78:32774","remoteAddress":"188.166.2.203:30305","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"20a3d41514c3bcebe4efed84ac3d111dae3f7c850709b216f864ec0f70c3dfe61e783bab0a993a6d8653cbc5b60f8b8e76ba17b1c611054975a8b88f33e64b6e","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.78:58420","remoteAddress":"188.166.2.203:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"59ff3b5c99bd25d1270457be496fb25044befff89951f0d1bfeb77773586c63c82d029e1d5de0a6b466f4d830f640b7053ecb20f502e36360f59f30915481a48","name":"StatusIM/v0.9.9-a339d7e/darwin-amd64/go1.10.1","caps":["shh/6"],"network":{"localAddress":"206.189.108.78:30504","remoteAddress":"51.179.97.64:24565","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"67f731dfdbd038ceae0213257aa83ec89cdb5ed8b3dc032a733044b8a0b44668263a5d06d73bebe4f8d139a6a1cd024956ff5fe6ccf24de4067e39da1c33df09","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.78:44140","remoteAddress":"206.189.108.74:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"7500e64a9a3b1dd1321da0c7fe725af038d82b20a028b5aeeddd4e52d4f682705f2838cc427937b1f20190cd4a1ea1f0850c6bd9317c08de8b368eb869f575a5","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.78:44832","remoteAddress":"206.189.108.50:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.78:35022","remoteAddress":"206.189.108.89:30305","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}}]}`, "10.1.0.14:8546": `{"jsonrpc":"2.0","id":1,"result":[{"id":"03f21684e777341df7f1b93888c9ba13a4d15130bb3c2d4fb13e817035b6cd6f3ed3dad1cd6c48dc995b17a2f4005188bae6d74d19d361a789c0f7085675a7f2","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:37246","remoteAddress":"206.189.108.89:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"1d193635e015918fb85bbaf774863d12f65d70c6977506187ef04420d74ec06c9e8f0dcb57ea042f85df87433dab17a1260ed8dde1bdf9d6d5d2de4b7bf8e993","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:54030","remoteAddress":"188.166.2.203:30305","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"3ba7e28541dba99f2209dba2dfb438f37cb4dcb908e28560f78398e175450cba24a8389814c114ece177e916be0912d639eb33c52d7ac959e38d8d4457df3898","name":"StatusIM/v0.9.9-a339d7e/android-arm/go1.10.1","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:30504","remoteAddress":"41.186.83.140:60748","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"423c4839ccfba3cca2483d51d782ddb15308238aee09b85b7eaecc47ce8f556cc8629c16d1c789d1d9bb6710deed8871cfc666d4ac6d987fbc5006a6d75f629b","name":"StatusIM/v0.9.9-a339d7e/android-arm/go1.10.1","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:30504","remoteAddress":"37.201.210.55:51872","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"59ff3b5c99bd25d1270457be496fb25044befff89951f0d1bfeb77773586c63c82d029e1d5de0a6b466f4d830f640b7053ecb20f502e36360f59f30915481a48","name":"StatusIM/v0.9.9-a339d7e/darwin-amd64/go1.10.1","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:30504","remoteAddress":"51.179.97.64:24650","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"67f731dfdbd038ceae0213257aa83ec89cdb5ed8b3dc032a733044b8a0b44668263a5d06d73bebe4f8d139a6a1cd024956ff5fe6ccf24de4067e39da1c33df09","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:55180","remoteAddress":"206.189.108.74:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"7500e64a9a3b1dd1321da0c7fe725af038d82b20a028b5aeeddd4e52d4f682705f2838cc427937b1f20190cd4a1ea1f0850c6bd9317c08de8b368eb869f575a5","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:43498","remoteAddress":"206.189.108.50:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"8080a119ca4e0930248915b4b504c63475e1b13c7cad71ab289ddbad37ac51b1f915063b68389a5b70dc673ebd94f7019796fc53bacbbd539f779034f59d70d2","name":"StatusIM/v0.9.9-a339d7e/android-arm/go1.10.1","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:30504","remoteAddress":"2.234.173.179:39912","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"81fdcbe375dcb88e0732cbb8158c5c2ec7779fa1c3e43abcb137277634a5b9789de2a9d0728e004d7dd139fc782b47dd446970a52cbd12dbc35ba2b91842eb44","name":"StatusIM/v0.9.9-a339d7e/android-arm/go1.10.1","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:30504","remoteAddress":"94.19.149.209:49088","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"93d67ae104ab9f28afa253942c7f7efeac300fb2b2cce6e59e661b0781b94365c0d987498022d0a338e7497786cfe5d3fe891173109319b4f92bc7c6df504f73","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.63:42776","remoteAddress":"206.189.108.76:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}}]}`, @@ -46,3 +56,21 @@ var mockPeers = map[string]string{ "10.1.0.5:8546": `{"jsonrpc":"2.0","id":1,"result":[{"id":"1d193635e015918fb85bbaf774863d12f65d70c6977506187ef04420d74ec06c9e8f0dcb57ea042f85df87433dab17a1260ed8dde1bdf9d6d5d2de4b7bf8e993","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:33794","remoteAddress":"188.166.2.203:30305","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"207e53d9bf66be7441e3daba36f53bfbda0b6099dba9a865afc6260a2d253fb8a56a72a48598a4f7ba271792c2e4a8e1a43aaef7f34857f520c8c820f63b44c8","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"35.224.15.65:35128","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"4e0a8db9b73403c9339a2077e911851750fc955db1fc1e09f81a4a56725946884dd5e4d11258eac961f9078a393c45bcab78dd0e3bc74e37ce773b3471d2e29c","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"206.189.108.50:52156","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"5679e2835694448e7060fb8f275681b2cff6bdbc89b0261969553db1d97c47784cbe34ac1e3aba955c277f268c171d5b20bd84595d2eb418b5e8db804351f8fe","name":"StatusIM/v0.9.9-a339d7e/android-386/go1.10.1","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"95.161.239.81:53865","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"67f731dfdbd038ceae0213257aa83ec89cdb5ed8b3dc032a733044b8a0b44668263a5d06d73bebe4f8d139a6a1cd024956ff5fe6ccf24de4067e39da1c33df09","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:43292","remoteAddress":"206.189.108.74:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"707e57453acd3e488c44b9d0e17975371e2f8fb67525eae5baca9b9c8e06c86cde7c794a6c2e36203bf9f56cae8b0e50f3b33c4c2b694a7baeea1754464ce4e3","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"35.192.229.172:60690","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"7500e64a9a3b1dd1321da0c7fe725af038d82b20a028b5aeeddd4e52d4f682705f2838cc427937b1f20190cd4a1ea1f0850c6bd9317c08de8b368eb869f575a5","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:56214","remoteAddress":"206.189.108.50:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"206.189.108.63:37246","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"81fdcbe375dcb88e0732cbb8158c5c2ec7779fa1c3e43abcb137277634a5b9789de2a9d0728e004d7dd139fc782b47dd446970a52cbd12dbc35ba2b91842eb44","name":"StatusIM/v0.9.9-a339d7e/android-arm/go1.10.1","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"94.19.149.209:42053","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"206.189.108.89:36464","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"206.189.7.30:34002","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"93d67ae104ab9f28afa253942c7f7efeac300fb2b2cce6e59e661b0781b94365c0d987498022d0a338e7497786cfe5d3fe891173109319b4f92bc7c6df504f73","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"206.189.108.76:50510","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"a6a2a9b3a7cbb0a15da74301537ebba549c990e3325ae78e1272a19a3ace150d03c184b8ac86cc33f1f2f63691e467d49308f02d613277754c4dccd6773b95e8","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"206.189.108.68:53232","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"206.189.108.74:56858","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"b33dc678589931713a085d29f9dc0efee1783dacce1d13696eb5d3a546293198470d97822c40b187336062b39fd3464e9807858109752767d486ea699a6ab3de","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"35.193.151.184:41706","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"ce559a37a9c344d7109bd4907802dd690008381d51f658c43056ec36ac043338bd92f1ac6043e645b64953b06f27202d679756a9c7cf62fdefa01b2e6ac5098e","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"206.189.108.76:50810","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"f9ea3c2bd207b88b387deca50ee25394f69d5785c7f28320c24b954a7ea3d2bc72c09880d17f921ef9f2afb33af412ac25c6ffdd4ecce5b5cd244cb2bd26200c","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30304","remoteAddress":"146.148.66.209:59436","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}}]}`, "10.1.0.5:8547": `{"jsonrpc":"2.0","id":1,"result":[{"id":"015e22f6cd2b44c8a51bd7a23555e271e0759c7d7f52432719665a74966f2da456d28e154e836bee6092b4d686fe67e331655586c57b718be3997c1629d24167","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30305","remoteAddress":"35.226.21.19:36410","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"03f21684e777341df7f1b93888c9ba13a4d15130bb3c2d4fb13e817035b6cd6f3ed3dad1cd6c48dc995b17a2f4005188bae6d74d19d361a789c0f7085675a7f2","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:36464","remoteAddress":"206.189.108.89:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"20a3d41514c3bcebe4efed84ac3d111dae3f7c850709b216f864ec0f70c3dfe61e783bab0a993a6d8653cbc5b60f8b8e76ba17b1c611054975a8b88f33e64b6e","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:55836","remoteAddress":"188.166.2.203:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"2af8f4f7a0b5aabaf49eb72b9b59474b1b4a576f99a869e00f8455928fa242725864c86bdff95638a8b17657040b21771a7588d18b0f351377875f5b46426594","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30305","remoteAddress":"35.232.187.4:45534","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"4e0a8db9b73403c9339a2077e911851750fc955db1fc1e09f81a4a56725946884dd5e4d11258eac961f9078a393c45bcab78dd0e3bc74e37ce773b3471d2e29c","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:58142","remoteAddress":"206.189.108.50:30305","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"4ea35352702027984a13274f241a56a47854a7fd4b3ba674a596cff917d3c825506431cf149f9f2312a293bb7c2b1cca55db742027090916d01529fe0729643b","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30305","remoteAddress":"206.189.108.62:57036","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"552942cc4858073102a6bcd0df9fe4de6d9fc52ddf7363e8e0746eba21b0f98fb37e8270bc629f72cfe29e0b3522afaf51e309a05998736e2c0dad5288991148","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30305","remoteAddress":"130.211.215.133:41716","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"a1a8e2416266020e168a2257851cdb59cd951e822655730dc1bbd50adb892a6444987d3baece727ae83600e1db8db49a707012b7ebe6fd4eb3e350166fe55579","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:48542","remoteAddress":"206.189.108.62:30304","inbound":false,"trusted":false,"static":true},"protocols":{"shh":"unknown"}},{"id":"a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30305","remoteAddress":"206.189.108.74:51580","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30305","remoteAddress":"206.189.108.78:35022","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"ce559a37a9c344d7109bd4907802dd690008381d51f658c43056ec36ac043338bd92f1ac6043e645b64953b06f27202d679756a9c7cf62fdefa01b2e6ac5098e","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30305","remoteAddress":"206.189.108.76:39946","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}},{"id":"eb4cc33c1948b1f4b9cb8157757645d78acd731cc8f9468ad91cef8a7023e9c9c62b91ddab107043aabc483742ac15cb4372107b23962d3bfa617b05583f2260","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","caps":["shh/6"],"network":{"localAddress":"206.189.108.89:30305","remoteAddress":"146.148.66.209:47694","inbound":true,"trusted":false,"static":false},"protocols":{"shh":"unknown"}}]}`, } + +var mockInfo = map[string]string{ + "10.1.0.13:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@[::]:30504?discport=0","ip":"::","ports":{"discovery":0,"listener":30504},"listenAddr":"[::]:30504","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.14:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@[::]:30504?discport=0","ip":"::","ports":{"discovery":0,"listener":30504},"listenAddr":"[::]:30504","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.12:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@[::]:30504?discport=0","ip":"::","ports":{"discovery":0,"listener":30504},"listenAddr":"[::]:30504","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.1.99:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"a6a2a9b3a7cbb0a15da74301537ebba549c990e3325ae78e1272a19a3ace150d03c184b8ac86cc33f1f2f63691e467d49308f02d613277754c4dccd6773b95e8","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://a6a2a9b3a7cbb0a15da74301537ebba549c990e3325ae78e1272a19a3ace150d03c184b8ac86cc33f1f2f63691e467d49308f02d613277754c4dccd6773b95e8@[::]:30304?discport=0","ip":"::","ports":{"discovery":0,"listener":30304},"listenAddr":"[::]:30304","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.1.99:8547": `{"jsonrpc":"2.0","id":1,"result":{"id":"66ba15600cda86009689354c3a77bdf1a97f4f4fb3ab50ffe34dbc904fac561040496828397be18d9744c75881ffc6ac53729ddbd2cdbdadc5f45c400e2622f7","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://66ba15600cda86009689354c3a77bdf1a97f4f4fb3ab50ffe34dbc904fac561040496828397be18d9744c75881ffc6ac53729ddbd2cdbdadc5f45c400e2622f7@[::]:30305?discport=0","ip":"::","ports":{"discovery":0,"listener":30305},"listenAddr":"[::]:30305","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.9:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"a1a8e2416266020e168a2257851cdb59cd951e822655730dc1bbd50adb892a6444987d3baece727ae83600e1db8db49a707012b7ebe6fd4eb3e350166fe55579","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://a1a8e2416266020e168a2257851cdb59cd951e822655730dc1bbd50adb892a6444987d3baece727ae83600e1db8db49a707012b7ebe6fd4eb3e350166fe55579@[::]:30304?discport=0","ip":"::","ports":{"discovery":0,"listener":30304},"listenAddr":"[::]:30304","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.9:8547": `{"jsonrpc":"2.0","id":1,"result":{"id":"4ea35352702027984a13274f241a56a47854a7fd4b3ba674a596cff917d3c825506431cf149f9f2312a293bb7c2b1cca55db742027090916d01529fe0729643b","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://4ea35352702027984a13274f241a56a47854a7fd4b3ba674a596cff917d3c825506431cf149f9f2312a293bb7c2b1cca55db742027090916d01529fe0729643b@[::]:30305?discport=0","ip":"::","ports":{"discovery":0,"listener":30305},"listenAddr":"[::]:30305","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.6:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"93d67ae104ab9f28afa253942c7f7efeac300fb2b2cce6e59e661b0781b94365c0d987498022d0a338e7497786cfe5d3fe891173109319b4f92bc7c6df504f73","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://93d67ae104ab9f28afa253942c7f7efeac300fb2b2cce6e59e661b0781b94365c0d987498022d0a338e7497786cfe5d3fe891173109319b4f92bc7c6df504f73@[::]:30304?discport=0","ip":"::","ports":{"discovery":0,"listener":30304},"listenAddr":"[::]:30304","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.6:8547": `{"jsonrpc":"2.0","id":1,"result":{"id":"ce559a37a9c344d7109bd4907802dd690008381d51f658c43056ec36ac043338bd92f1ac6043e645b64953b06f27202d679756a9c7cf62fdefa01b2e6ac5098e","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://ce559a37a9c344d7109bd4907802dd690008381d51f658c43056ec36ac043338bd92f1ac6043e645b64953b06f27202d679756a9c7cf62fdefa01b2e6ac5098e@[::]:30305?discport=0","ip":"::","ports":{"discovery":0,"listener":30305},"listenAddr":"[::]:30305","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.7:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"7500e64a9a3b1dd1321da0c7fe725af038d82b20a028b5aeeddd4e52d4f682705f2838cc427937b1f20190cd4a1ea1f0850c6bd9317c08de8b368eb869f575a5","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://7500e64a9a3b1dd1321da0c7fe725af038d82b20a028b5aeeddd4e52d4f682705f2838cc427937b1f20190cd4a1ea1f0850c6bd9317c08de8b368eb869f575a5@[::]:30304?discport=0","ip":"::","ports":{"discovery":0,"listener":30304},"listenAddr":"[::]:30304","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.7:8547": `{"jsonrpc":"2.0","id":1,"result":{"id":"4e0a8db9b73403c9339a2077e911851750fc955db1fc1e09f81a4a56725946884dd5e4d11258eac961f9078a393c45bcab78dd0e3bc74e37ce773b3471d2e29c","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://4e0a8db9b73403c9339a2077e911851750fc955db1fc1e09f81a4a56725946884dd5e4d11258eac961f9078a393c45bcab78dd0e3bc74e37ce773b3471d2e29c@[::]:30305?discport=0","ip":"::","ports":{"discovery":0,"listener":30305},"listenAddr":"[::]:30305","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.4:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"67f731dfdbd038ceae0213257aa83ec89cdb5ed8b3dc032a733044b8a0b44668263a5d06d73bebe4f8d139a6a1cd024956ff5fe6ccf24de4067e39da1c33df09","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://67f731dfdbd038ceae0213257aa83ec89cdb5ed8b3dc032a733044b8a0b44668263a5d06d73bebe4f8d139a6a1cd024956ff5fe6ccf24de4067e39da1c33df09@[::]:30304?discport=0","ip":"::","ports":{"discovery":0,"listener":30304},"listenAddr":"[::]:30304","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.4:8547": `{"jsonrpc":"2.0","id":1,"result":{"id":"a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c@[::]:30305?discport=0","ip":"::","ports":{"discovery":0,"listener":30305},"listenAddr":"[::]:30305","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.5:8546": `{"jsonrpc":"2.0","id":1,"result":{"id":"03f21684e777341df7f1b93888c9ba13a4d15130bb3c2d4fb13e817035b6cd6f3ed3dad1cd6c48dc995b17a2f4005188bae6d74d19d361a789c0f7085675a7f2","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://03f21684e777341df7f1b93888c9ba13a4d15130bb3c2d4fb13e817035b6cd6f3ed3dad1cd6c48dc995b17a2f4005188bae6d74d19d361a789c0f7085675a7f2@[::]:30304?discport=0","ip":"::","ports":{"discovery":0,"listener":30304},"listenAddr":"[::]:30304","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, + "10.1.0.5:8547": `{"jsonrpc":"2.0","id":1,"result":{"id":"887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875","name":"Statusd/v0.9.9-8141657a/linux-amd64/go1.10.2","enode":"enode://887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875@[::]:30305?discport=0","ip":"::","ports":{"discovery":0,"listener":30305},"listenAddr":"[::]:30305","protocols":{"shh":{"maxMessageSize":1048576,"minimumPoW":0.001,"version":"6.0"}}}}`, +}