|
|
- package crawler
-
- import (
- "fmt"
- rpctypes "github.com/tendermint/tendermint/rpc/core/types"
- rpcclient "github.com/tendermint/tendermint/rpc/core_client"
- types "github.com/tendermint/tendermint/types"
- "sync"
- "time"
- )
-
- const (
- CheckQueueBufferSize = 100
- NodeQueueBufferSize = 100
- )
-
- //---------------------------------------------------------------------------------------
- // crawler.Node
-
- // A node is a peer on the network.
- type Node struct {
- Host string
- P2PPort uint16
- RPCPort uint16
-
- failed int
- connected bool
-
- client *NodeClient
-
- LastSeen time.Time
- GenesisHash []byte
- BlockHeight uint
- NetInfo *rpctypes.ResponseNetInfo
-
- Validator bool
-
- // other peers we heard about this peer from
- heardFrom map[string]struct{}
- }
-
- func (n *Node) Address() string {
- return fmt.Sprintf("%s:%d", n.Host, n.RPCPort)
- }
-
- // Set the basic status and network info for a node from RPC responses
- func (n *Node) SetInfo(status *rpctypes.ResponseStatus, netinfo *rpctypes.ResponseNetInfo) {
- n.LastSeen = time.Now()
- n.GenesisHash = status.GenesisHash
- n.BlockHeight = status.LatestBlockHeight
- n.NetInfo = netinfo
- // n.Validator
- }
-
- // A node client is used to talk to a node over rpc and websockets
- type NodeClient struct {
- rpc rpcclient.Client
- ws *rpcclient.WSClient
- }
-
- // Create a new client for the node at the given addr
- func NewNodeClient(addr string) *NodeClient {
- return &NodeClient{
- rpc: rpcclient.NewClient(addr, "JSONRPC"),
- ws: rpcclient.NewWSClient(addr),
- }
- }
-
- // A simple wrapper for mediating access to the maps
- type nodeInfo struct {
- host string // the new nodes address
- port uint16
- from string // the peer that told us about this node
- removed bool // whether to remove from nodePool
- }
-
- func (ni nodeInfo) unpack() (string, uint16, string, bool) {
- return ni.host, ni.port, ni.from, ni.removed
- }
-
- // crawler.Node
- //---------------------------------------------------------------------------------------
- // crawler.Crawler
-
- // A crawler has a local node, a set of potential nodes in the nodePool
- // and connected nodes. Maps are only accessed by one go-routine, mediated through channels
- type Crawler struct {
- self *Node
- client *NodeClient
-
- checkQueue chan nodeInfo
- nodePool map[string]*Node
- nodes map[string]*Node
-
- nodeQueue chan *Node
- quit chan struct{}
-
- // waits for checkQueue to empty
- // so we can re-poll all nodes
- wg sync.WaitGroup
- }
-
- // Create a new Crawler using the local RPC server at addr
- func NewCrawler(host string, port uint16) *Crawler {
- return &Crawler{
- self: &Node{Host: host, RPCPort: port},
- client: NewNodeClient(fmt.Sprintf("%s:%d", host, port)),
- checkQueue: make(chan nodeInfo, CheckQueueBufferSize),
- nodePool: make(map[string]*Node),
- nodes: make(map[string]*Node),
- nodeQueue: make(chan *Node, NodeQueueBufferSize),
- quit: make(chan struct{}),
- }
- }
-
- func (c *Crawler) checkNode(ni nodeInfo) {
- c.wg.Add(1)
- c.checkQueue <- ni
- }
-
- func (c *Crawler) Start() error {
- // make sure we can connect
- // to our local node first
- status, err := c.client.rpc.Status()
- if err != nil {
- return err
- }
-
- // connect to weboscket and subscribe to local events
- if err = c.client.ws.Dial(); err != nil {
- return err
- }
- if err = c.client.ws.Subscribe(types.EventStringNewBlock()); err != nil {
- return err
- }
- go c.readLocalEvents()
-
- // add ourselves to the nodes list
- c.nodes[c.self.Address()] = c.self
-
- // get peers from local node
- netinfo, err := c.client.rpc.NetInfo()
- if err != nil {
- return err
- }
-
- // set the info for ourselves
- c.self.SetInfo(status, netinfo)
-
- // fire each peer on the checkQueue
- for _, p := range netinfo.Peers {
- c.checkNode(nodeInfo{
- host: p.Host,
- port: p.RPCPort,
- })
- }
-
- // nodes we hear about get put on the
- // checkQueue and are handled in the checkLoop
- // if its a node we're not already connected to,
- // it gets put it on the nodeQueue and
- // we attempt to connect in the connectLoop
- go c.checkLoop()
- go c.connectLoop()
-
- // finally, a routine with a ticker to poll nodes for peers
- go c.pollNodesRoutine()
-
- return nil
- }
-
- func (c *Crawler) Stop() {
- close(c.quit)
- }
-
- func (c *Crawler) readLocalEvents() {
- // read on our ws for NewBlocks
- }
-
- // check nodes against the nodePool map one at a time
- // acts as a mutex on nodePool
- func (c *Crawler) checkLoop() {
- c.wg.Add(1)
- for {
- // every time the loop restarts
- // it means we processed from the checkQueue
- // (except the first time, hence the extra wg.Add)
- c.wg.Done()
- select {
- case ni := <-c.checkQueue:
- host, port, from, removed := ni.unpack()
- addr := fmt.Sprintf("%s:%d", host, port)
- // check if the node should be removed
- // (eg. if its been connected to or abandoned)
- if removed {
- n, _ := c.nodePool[addr]
- if n.connected {
- c.nodes[addr] = n
- }
- delete(c.nodePool, addr)
- continue
- }
-
- // TODO: if address is badly formed
- // we should punish ni.from
- _ = from
-
- n, ok := c.nodePool[addr]
- // create the node if unknown
- if !ok {
- n = &Node{Host: host, RPCPort: port}
- c.nodes[addr] = n
- } else if n.connected {
- // should be removed soon
- continue
- }
-
- // queue it for connecting to
- c.nodeQueue <- n
-
- case <-c.quit:
- return
- }
- }
- }
-
- // read off the nodeQueue and attempt to connect to nodes
- func (c *Crawler) connectLoop() {
- for {
- select {
- case node := <-c.nodeQueue:
- go c.connectToNode(node)
- case <-c.quit:
- // close all connections
- for addr, node := range c.nodes {
- _, _ = addr, node
- // TODO: close conn
- }
- return
- }
- }
- }
-
- func (c *Crawler) connectToNode(node *Node) {
-
- addr := node.Address()
- node.client = NewNodeClient(addr)
-
- if err := node.client.ws.Dial(); err != nil {
- // set failed, return
- }
-
- // remove from nodePool, add to nodes
- c.checkNode(nodeInfo{
- host: node.Host,
- port: node.RPCPort,
- removed: true,
- })
-
- c.pollNode(node)
-
- // TODO: read loop
- }
-
- func (c *Crawler) pollNode(node *Node) error {
- status, err := node.client.rpc.Status()
- if err != nil {
- return err
- }
-
- // get peers
- netinfo, err := node.client.rpc.NetInfo()
- if err != nil {
- return err
- }
-
- node.SetInfo(status, netinfo)
-
- // fire each peer on the checkQueue
- for _, p := range netinfo.Peers {
- c.checkNode(nodeInfo{
- host: p.Host,
- port: p.RPCPort,
- from: node.Address(),
- })
- }
- return nil
- }
-
- // wait for the checkQueue to empty and poll all the nodes
- func (c *Crawler) pollNodesRoutine() {
- for {
- c.wg.Wait()
-
- ticker := time.Tick(time.Second)
- // wait a few seconds to make sure we really have nothing
- time.Sleep(time.Second * 5)
- ch := make(chan struct{})
- go func() {
- c.wg.Wait()
- ch <- struct{}{}
- }()
-
- //
- select {
- case <-ticker:
- // the checkQueue has filled up again, move on
- continue
- case <-ch:
- // the checkQueue is legit empty,
- // TODO: poll the nodes!
-
- case <-c.quit:
- return
- }
- }
-
- }
|