Ethereum, p2p Server principle and implementation

Ethereum p2p principle and implementation

The decentralization of blockchain technology depends on the underlying networking technology. The bottom layer of Ethereum implements p2pServer, which can be roughly divided into three layers.

  • The underlying routing table. It encapsulates kad routing, node data structure and functions such as calculation records, node search, and verification.
  • Middle-level peer abstraction, open message sending interface, server provides peer detection, initialization, event subscription, peer status query, start, stop and other functions
  • The top-level peer and peerset of Ethereum are repackaged. Through the Run function of the protocol, when the peer is started in the middle layer, the peer is obtained, and finally the stable peer is intercepted through a loop and packaged in the peerset for use.

underlying routing table

This simplified question only discusses the Node Discovery Protocol. This layer maintains a bucket of buckets, with a total of 17 buckets, and each bucket has 16 nodes and 10 replacement nodes. When Node is placed, the distance between hash and localNode must be calculated first. Then select a bucket according to the distance and put it into it. When fetching, calculate the target and examples of objects in each bucket one by one. For details, refer to the closest function, which will be posted later.

The distance formula satisfies: f(x,y)=256-8*n-map(x[n+1]^y[n+1]) Note: n is the same number of nodes and map is a negatively correlated mapping relationship.

Simply put, the more similarities, the smaller the value. See Node.go's logdist function for details. Here you need to understand the algorithm Kademlia,

.
├── database.go         //封装node数据库相关操作
├── node.go             //节点数据结构
├── ntp.go              //同步时间  
├── table.go            //路由表
├── udp.go              //网络相关操作

The most important of which is the table object. The table public methods are:

  • newTable instance creation
  • Self local node acquisition
  • ReadRandomNodes reads several nodes randomly
  • Close
  • Resolve finds a node around it
  • Lookup finds neighbors of a node

Let’s analyze these methods one by one:

newTable

  • 1: Generate object instance (get database client, LocalNode etc)
    // If no node database was given, use an in-memory one
    db, err := newNodeDB(nodeDBPath, Version, ourID)
    if err != nil {
        return nil, err
    }
    tab := &Table{
        net:        t,
        db:         db,
        self:       NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)),
        bonding:    make(map[NodeID]*bondproc),
        bondslots:  make(chan struct{}, maxBondingPingPongs),
        refreshReq: make(chan chan struct{}),
        initDone:   make(chan struct{}),
        closeReq:   make(chan struct{}),
        closed:     make(chan struct{}),
        rand:       mrand.New(mrand.NewSource(0)),
        ips:        netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit},
    }
  • 2: Load the bootstrap node and initialize the k bucket.
    if err := tab.setFallbackNodes(bootnodes); err != nil {
        return nil, err
    }
    for i := 0; i < cap(tab.bondslots); i++ {
        tab.bondslots <- struct{}{}
    }
    for i := range tab.buckets {
        tab.buckets[i] = &bucket{
            ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
        }
    }
  • 3: Put the node into the bucket, and generate a coroutine to refresh and verify the node.
    tab.seedRand()
    tab.loadSeedNodes(false)  //载入种子节点
    // Start the background expiration goroutine after loading seeds so that the search for
    // seed nodes also considers older nodes that would otherwise be removed by the
    // expiration.
    tab.db.ensureExpirer()
    go tab.loop()

load seed node

    func (tab *Table) loadSeedNodes(bond bool) {
        seeds := tab.db.querySeeds(seedCount, seedMaxAge)
        //数据库中的种子节点和引导节点合并
        seeds = append(seeds, tab.nursery...) 
        if bond {
            seeds = tab.bondall(seeds)   //节点验证
        }
        for i := range seeds {
            seed := seeds[i]
            age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.bondTime(seed.ID)) }}
            log.Debug("Found seed node in database", "id", seed.ID, "addr", seed.addr(), "age", age)
            tab.add(seed)               //节点入桶
        }
    }

Nodes are added to the bucket, and restrictions such as ip should also be checked.

    func (tab *Table) add(new *Node) {
        tab.mutex.Lock()
        defer tab.mutex.Unlock()

        b := tab.bucket(new.sha)   //获取当前节点对应的桶
        if !tab.bumpOrAdd(b, new) {
            // Node is not in table. Add it to the replacement list.
            tab.addReplacement(b, new)
        }
    }

Bucket selection

    func (tab *Table) bucket(sha common.Hash) *bucket {
        d := logdist(tab.self.sha, sha)  //计算hash举例
        if d <= bucketMinDistance {
            //这里按算法来看,只要hash前三位相等就会到第一个buckets
            return tab.buckets[0]
        }
        return tab.buckets[d-bucketMinDistance-1]
    }

Resolve

Find the Node according to the ID of the Node, first search in the current bucket, and then search again in the surrounding nodes if you don't find it.

    // Resolve searches for a specific node with the given ID.
    // It returns nil if the node could not be found.
    func (tab *Table) Resolve(targetID NodeID) *Node {
        // If the node is present in the local table, no
        // network interaction is required.
        hash := crypto.Keccak256Hash(targetID[:])
        tab.mutex.Lock()
        //查找最近节点
        cl := tab.closest(hash, 1)
        tab.mutex.Unlock()
        if len(cl.entries) > 0 && cl.entries[0].ID == targetID {
            return cl.entries[0]
        }
        // Otherwise, do a network lookup.
        //不存在 搜索邻居节点
        result := tab.Lookup(targetID)
        for _, n := range result {
            if n.ID == targetID {
                return n
            }
        }
        return nil
    }

The function that needs to be understood here is closest, which traverses all nodes of all buckets and finds the closest one

    // closest returns the n nodes in the table that are closest to the
    // given id. The caller must hold tab.mutex.
    func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance {
        // This is a very wasteful way to find the closest nodes but
        // obviously correct. I believe that tree-based buckets would make
        // this easier to implement efficiently.
        close := &nodesByDistance{target: target}
        for _, b := range tab.buckets {
            for _, n := range b.entries {
                close.push(n, nresults)
            }
        }
        return close
    }

    func (h *nodesByDistance) push(n *Node, maxElems int) {
        ix := sort.Search(len(h.entries), func(i int) bool {
            return distcmp(h.target, h.entries[i].sha, n.sha) > 0
        })
        if len(h.entries) < maxElems {
            h.entries = append(h.entries, n)
        }
        if ix == len(h.entries) {
            // farther away than all nodes we already have.
            // if there was room for it, the node is now the last element.
        } else {
            // slide existing entries down to make room
            // this will overwrite the entry we just appended.
            //近的靠前边
            copy(h.entries[ix+1:], h.entries[ix:])
            h.entries[ix] = n
        }
    }

ReadRandomNodes

The overall idea is to copy it out first, then extract the top one bucket by bucket, remove the remaining empty buckets, merge the remaining buckets, and then extract the first node of the bucket in the next round until the given data is filled or The barrels are all empty. Finally, return the number filled in the array.

    // ReadRandomNodes fills the given slice with random nodes from the
    // table. It will not write the same node more than once. The nodes in
    // the slice are copies and can be modified by the caller.
    func (tab *Table) ReadRandomNodes(buf []*Node) (n int) {
        if !tab.isInitDone() {
            return 0
        }
        tab.mutex.Lock()
        defer tab.mutex.Unlock()

        // Find all non-empty buckets and get a fresh slice of their entries.
        var buckets [][]*Node
        //拷贝节点
        for _, b := range tab.buckets {
            if len(b.entries) > 0 {
                buckets = append(buckets, b.entries[:])
            }
        }
        if len(buckets) == 0 {
            return 0
        }
        // Shuffle the buckets.
        for i := len(buckets) - 1; i > 0; i-- {
            j := tab.rand.Intn(len(buckets))
            buckets[i], buckets[j] = buckets[j], buckets[i]
        }
        // Move head of each bucket into buf, removing buckets that become empty.
        var i, j int
        for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) {
            b := buckets[j]
            buf[i] = &(*b[0])  //取第一个节点
            buckets[j] = b[1:] //移除第一个
            if len(b) == 1 {
                //空桶移除
                buckets = append(buckets[:j], buckets[j+1:]...)  
            }
            if len(buckets) == 0 {
                break          
            }
        }
        return i + 1
    }

Lookup

lookup will ask the known nodes to find neighbor nodes, and the neighbor nodes that are looked up will recursively find the nodes around it.

    for {
        // ask the alpha closest nodes that we haven't asked yet
        for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ {
            n := result.entries[i]
            if !asked[n.ID] {
                asked[n.ID] = true
                pendingQueries++   
                go func() {
                    // Find potential neighbors to bond with
                    r, err := tab.net.findnode(n.ID, n.addr(), targetID)
                    if err != nil {
                        // Bump the failure counter to detect and evacuate non-bonded entries
                        fails := tab.db.findFails(n.ID) + 1
                        tab.db.updateFindFails(n.ID, fails)
                        log.Trace("Bumping findnode failure counter", "id", n.ID, "failcount", fails)

                        if fails >= maxFindnodeFailures {
                            log.Trace("Too many findnode failures, dropping", "id", n.ID, "failcount", fails)
                            tab.delete(n)
                        }
                    }
                    reply <- tab.bondall(r)
                }()
            }
        }
        if pendingQueries == 0 {
            // we have asked all closest nodes, stop the search
            break
        }
        // wait for the next reply
        for _, n := range <-reply {    //此处会阻塞请求
            if n != nil && !seen[n.ID] {
                seen[n.ID] = true
                result.push(n, bucketSize)
            }
        }
        pendingQueries--
    }

Maintenance of barrels

After the bucket is initialized, it will enter a loop logic, in which the adjustment period is controlled by three timers.

  • Verify that the timer interval is about 10s
  • Refresh timer interval 30 minutes
  • Persistence timer interval 30s
    revalidate     = time.NewTimer(tab.nextRevalidateTime())
    refresh        = time.NewTicker(refreshInterval)
    copyNodes      = time.NewTicker(copyNodesInterval)

Refresh logic: reload the seed node, find surrounding nodes, random three nodes, and find surrounding nodes of these three nodes.

    func (tab *Table) doRefresh(done chan struct{}) {
        defer close(done)

        tab.loadSeedNodes(true)

        tab.lookup(tab.self.ID, false)

        for i := 0; i < 3; i++ {
            var target NodeID
            crand.Read(target[:])
            tab.lookup(target, false)
        }
    }

Verification logic: verify the last node of each bucket, and put it at the top of the queue if the node passes the verification (the verification process is that the local node sends a ping request to it, and if it responds to pong, it passes)

    last, bi := tab.nodeToRevalidate()  //取最后一个节点
    if last == nil {
        // No non-empty bucket found.
        return
    }

    // Ping the selected node and wait for a pong.
    err := tab.ping(last.ID, last.addr())   //通信验证

    tab.mutex.Lock()
    defer tab.mutex.Unlock()
    b := tab.buckets[bi]
    if err == nil {
        // The node responded, move it to the front.
        log.Debug("Revalidated node", "b", bi, "id", last.ID)
        b.bump(last)    //提到队首
        return
    }

Peer/Server

Related documents

.
├── dial.go          //封装一个任务生成处理结构以及三种任务结构中(此处命名不太精确)
├── message.go       //定义一些数据的读写接口,以及对外的Send/SendItem函数
├── peer.go          //封装了Peer 包括消息读取  
├── rlpx.go          //内部的握手协议
├── server.go        //初始化,维护Peer网络,还有一些对外的接口

This layer will continuously extract nodes from the route. The extracted nodes must be authenticated and added to the peer after the protocol check. Then if no one uses the peer, the peer will be deleted and some nodes will be reselected. Come out and continue this process, and the peers are sold as they are born. This is to use all nodes equally, rather than just relying on a few specific nodes. Therefore, we start from the Server to analyze the whole process.

    Peers()                             //peer对象
    PeerCount()                         //peer数量
    AddPeer(node *discover.Node)        //添加节点
    RemovePeer(node *discover.Node)     //删除节点
    SubscribeEvents(ch chan *PeerEvent) //订阅内部的事件(节点的增加,删除)
    //以上四个属于对外的接口,不影响内部逻辑
    Start()                             //server开始工作
    SetupConn(fd net.Conn, flags connFlag, dialDest *discover.Node)  //启动一个连接,经过两次验证之后,如果通过则加入到peer之中。

Start does three things, generating routing tables and establishing the underlying network. DialState is generated to drive and maintain the update and death of the local peer, and listen to the local interface for information response. Here we mainly analyze the maintenance process of peers. The function is the run function.

run function

Two queues are defined in this function

    runningTasks []task //正在执行的任务
    queuedTasks  []task //尚未执行的任务

Three anonymous functions are defined

    //从正在执行任务中删除任务
	delTask := func(t task) {
		for i := range runningTasks {
			if runningTasks[i] == t {
				runningTasks = append(runningTasks[:i], runningTasks[i+1:]...)
				break
			}
		}
	}
	//开始一批任务
	startTasks := func(ts []task) (rest []task) {
		i := 0
		for ; len(runningTasks) < maxActiveDialTasks && i < len(ts); i++ {
			t := ts[i]
			srv.log.Trace("New dial task", "task", t)
			go func() {
				 t.Do(srv); taskdone <- t  
			}()
			runningTasks = append(runningTasks, t)
		}
		return ts[i:]
	}
    //启动开始一批任务再调用dialstate的newTasks函数生成一批任务,加载到任务队列里面
	scheduleTasks := func() {
		// Start from queue first.
		queuedTasks = append(queuedTasks[:0], startTasks(queuedTasks)...)
		// Query dialer for new tasks and start as many as possible now.
		if len(runningTasks) < maxActiveDialTasks {
			nt := dialstate.newTasks(len(runningTasks)+len(queuedTasks), peers, time.Now())
			queuedTasks = append(queuedTasks, startTasks(nt)...)
		}
	}

A loop is defined to execute the corresponding logic in different channels

	for {
        //调度开始找生成任务
		scheduleTasks()

		select {
		case <-srv.quit://退出
			break running
		case n := <-srv.addstatic: 
            //增加一个节点  该节点最终会生成一个dialTask 
            //并在newTasks的时候加入到读列
			srv.log.Debug("Adding static node", "node", n)
			dialstate.addStatic(n)
		case n := <-srv.removestatic:
            //直接删除该节点 节点不再参与维护,很快就会死掉了
			dialstate.removeStatic(n)
			if p, ok := peers[n.ID]; ok {
				p.Disconnect(DiscRequested)
			}
		case op := <-srv.peerOp:
			//  Peers 和 PeerCount 两个外部接口,只是读取peer信息
			op(peers)
			srv.peerOpDone <- struct{}{}
		case t := <-taskdone:
		    //task完成后会根据不同的任务类型进行相应的处理
			srv.log.Trace("Dial task done", "task", t)
			dialstate.taskDone(t, time.Now())
			delTask(t)
		case c := <-srv.posthandshake:
			//身份验证通过 
			if trusted[c.id] {
				// Ensure that the trusted flag is set before checking against MaxPeers.
				c.flags |= trustedConn
			}
			select {
			case c.cont <- srv.encHandshakeChecks(peers, inboundCount, c):
			case <-srv.quit:
				break running
			}
		case c := <-srv.addpeer:
			//身份协议验证通过 加入队列
			err := srv.protoHandshakeChecks(peers, inboundCount, c)
			if err == nil {
				// The handshakes are done and it passed all checks.
				p := newPeer(c, srv.Protocols)
				// If message events are enabled, pass the peerFeed
				// to the peer
				if srv.EnableMsgEvents {
					p.events = &srv.peerFeed
				}
				name := truncateName(c.name)
				srv.log.Debug("Adding p2p peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1)
				go srv.runPeer(p)   //触发事件 此处是最上层截取peer的位置,如果此物没有外部影响,那么这个peer很快就被销毁了
				peerAdd++
				fmt.Printf("--count %d--- add %d-- del %d--\n",len(peers),peerAdd,peerDel)
				
				peers[c.id] = p
				if p.Inbound() {
					inboundCount++
				}
			}
			// The dialer logic relies on the assumption that
			// dial tasks complete after the peer has been added or
			// discarded. Unblock the task last.
			select {
			case c.cont <- err:
			case <-srv.quit:
				break running
			}
		case pd := <-srv.delpeer:
			//移除peer
			d := common.PrettyDuration(mclock.Now() - pd.created)
			pd.log.Debug("Removing p2p peer", "duration", d, "peers", len(peers)-1, "req", pd.requested, "err", pd.err)
			delete(peers, pd.ID())
			peerDel++
			fmt.Printf("--count %d--- add %d-- del %d--\n",len(peers),peerAdd,peerDel)
			if pd.Inbound() {
				inboundCount--
			}
		}
	}

Remember the above code and look at it one by one:

scheduleTasks

scheduleTasks schedules and generates tasks. Among the generated tasks, there is a dialTask ​​task. The task structure is as follows

    type dialTask struct {
        flags        connFlag
        dest         *discover.Node
        lastResolved time.Time
        resolveDelay time.Duration
    }

    func (t *dialTask) Do(srv *Server) {
        if t.dest.Incomplete() {
            if !t.resolve(srv) {
                return
            }
        }
        err := t.dial(srv, t.dest)  //此处会调用到setupConn函数
        if err != nil {
            log.Trace("Dial error", "task", t, "err", err)
            // Try resolving the ID of static nodes if dialing failed.
            if _, ok := err.(*dialError); ok && t.flags&staticDialedConn != 0 {
                if t.resolve(srv) {
                    t.dial(srv, t.dest)
                }
            }
        }
    }

dial finally calls back to the setupConn function. The function only retains a few key sentences, which is a bit long.

    func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) error {

        //身份验证码 获取设备,标识等信息
        if c.id, err = c.doEncHandshake(srv.PrivateKey, dialDest); err != 
        //此处会往chanel中添加连接对象,最终触发循环中的posthandshake分支
        err = srv.checkpoint(c, srv.posthandshake)  
        //协议验证
        phs, err := c.doProtoHandshake(srv.ourHandshake)
        c.caps, c.name = phs.Caps, phs.Name
        //此处会往chanel中添加连接对象 最终触发循环中的addpeer分支
        err = srv.checkpoint(c, srv.addpeer)
    }

The posthandshake branch is only verified, and addpeer does more things. The important thing is to execute the runPeer function.

    func (srv *Server) runPeer(p *Peer) {
        // 广播 peer add
        srv.peerFeed.Send(&PeerEvent{
            Type: PeerEventTypeAdd,
            Peer: p.ID(),
        })

        // run the protocol
        remoteRequested, err := p.run() //

        // 广播 peer drop
        srv.peerFeed.Send(&PeerEvent{
            Type:  PeerEventTypeDrop,
            Peer:  p.ID(),
            Error: err.Error(),
        })
        //移除peer
        srv.delpeer <- peerDrop{p, err, remoteRequested}
    }

    func (p *Peer) run() (remoteRequested bool, err error) {
        //*************
        writeStart <- struct{}{}
        p.startProtocols(writeStart, writeErr)
        //*************
        //这一句阻塞性确保了peer的存活
        p.wg.Wait()  
    }

    func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) {
        p.wg.Add(len(p.running))
        for _, proto := range p.running {
            proto := proto
            proto.closed = p.closed
            proto.wstart = writeStart
            proto.werr = writeErr
            var rw MsgReadWriter = proto
            if p.events != nil {
                rw = newMsgEventer(rw, p.events, p.ID(), proto.Name)
            }
            p.log.Trace(fmt.Sprintf("Starting protocol %s/%d", proto.Name, proto.Version))
            go func() {
                //其他的都是为这一句做准备的,在以太坊中p2p就是靠这一句对上层暴露peer对象
                err := proto.Run(p, rw)
                if err == nil {
                    p.log.Trace(fmt.Sprintf("Protocol %s/%d returned", proto.Name, proto.Version))
                    err = errProtocolReturned
                } else if err != io.EOF {
                    p.log.Trace(fmt.Sprintf("Protocol %s/%d failed", proto.Name, proto.Version), "err", err)
                }
                p.protoErr <- err
                p.wg.Done()  
            }()
        }
    }

In this way, it is possible to figure out an idea of ​​scheduleTasks execution to generate the dialTask ​​task. During the execution of the dialTask ​​task, the two channels of posthandshake and addPeer are filled one by one. When addPeer is executed, the Peer object is exposed to the upper layer, the delpeer is filled after completion, and finally the Peer is deleted.

generation of tasks

See the comments in the code for details

    func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task {
        if s.start.IsZero() {
            s.start = now
        }

        var newtasks []task
        //这里声明了一个添加任务的函数  
        addDial := func(flag connFlag, n *discover.Node) bool {
            if err := s.checkDial(n, peers); err != nil {
                log.Trace("Skipping dial candidate", "id", n.ID, "addr", &net.TCPAddr{IP: n.IP, Port: int(n.TCP)}, "err", err)
                return false
            }
            s.dialing[n.ID] = flag  //排除掉已经再测试的
            newtasks = append(newtasks, &dialTask{flags: flag, dest: n})
            return true
        }

        // Compute number of dynamic dials necessary at this point.
        needDynDials := s.maxDynDials     //当前系统中最大连接数目
        for _, p := range peers {        //扣除已建立链接的peer
            if p.rw.is(dynDialedConn) {
                needDynDials--
            }
        }
        for _, flag := range s.dialing {  //扣除已建立链接的peer
            if flag&dynDialedConn != 0 {
                needDynDials--
            }
        }

        //外部命令添加的节点 这种节点不占用needDynDials数目,
        //是为了保证手动加的节点能够起效
        for id, t := range s.static {
            err := s.checkDial(t.dest, peers)
            switch err {
            case errNotWhitelisted, errSelf:
                log.Warn("Removing static dial candidate", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)}, "err", err)
                delete(s.static, t.dest.ID)
            case nil:
                s.dialing[id] = t.flags
                newtasks = append(newtasks, t)
            }
        }
        // If we don't have any peers whatsoever, try to dial a random bootnode. This
        // scenario is useful for the testnet (and private networks) where the discovery
        // table might be full of mostly bad peers, making it hard to find good ones.
        if len(peers) == 0 && len(s.bootnodes) > 0 && needDynDials > 0 && 
        //检查引导节点  因为引导节点比搜索到的节点更大概率靠谱 因而比较靠前
        now.Sub(s.start) > fallbackInterval {
            bootnode := s.bootnodes[0]
            s.bootnodes = append(s.bootnodes[:0], s.bootnodes[1:]...)
            s.bootnodes = append(s.bootnodes, bootnode)

            if addDial(dynDialedConn, bootnode) {
                needDynDials--
            }
        }
        //随机的从路由中抽取最大节点的二分之一
        randomCandidates := needDynDials / 2
        if randomCandidates > 0 {
            n := s.ntab.ReadRandomNodes(s.randomNodes)
            for i := 0; i < randomCandidates && i < n; i++ {
                if addDial(dynDialedConn, s.randomNodes[i]) {
                    needDynDials--
                }
            }
        }
        // 从lookupbuf中抽取
        i := 0
        for ; i < len(s.lookupBuf) && needDynDials > 0; i++ {
            if addDial(dynDialedConn, s.lookupBuf[i]) {
                needDynDials--
            }
        }
        s.lookupBuf = s.lookupBuf[:copy(s.lookupBuf, s.lookupBuf[i:])]
        // 如果还是不够,路由再去搜索节点
        if len(s.lookupBuf) < needDynDials && !s.lookupRunning {
            s.lookupRunning = true
            newtasks = append(newtasks, &discoverTask{})
        }

        // wait
        if nRunning == 0 && len(newtasks) == 0 && s.hist.Len() > 0 {
            t := &waitExpireTask{s.hist.min().exp.Sub(now)}
            newtasks = append(newtasks, t)
        }
        return newtasks
    }

message sending

The other is Send in message. The SendItem function implements MsgWriter objects that can call this function to write. I don’t think it is necessary here. It can be encapsulated in the peer. function.

    func Send(w MsgWriter, msgcode uint64, data interface{}) error {
        size, r, err := rlp.EncodeToReader(data)
        if err != nil {
            return err
        }
        return w.WriteMsg(Msg{Code: msgcode, Size: uint32(size), Payload: r})
    }

    func SendItems(w MsgWriter, msgcode uint64, elems ...interface{}) error {
        return Send(w, msgcode, elems)
    }

Ethereum upper layer call

File: go-ethereum/eth/handler.go

	manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions))
	for i, version := range ProtocolVersions {
		// Skip protocol version if incompatible with the mode of operation
		if mode == downloader.FastSync && version < eth63 {
			continue
		}
		// Compatible; initialise the sub-protocol
		version := version // Closure for the run
		manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{
			Name:    ProtocolName,
			Version: version,
			Length:  ProtocolLengths[i],
			Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
				peer := manager.newPeer(int(version), p, rw)
				select {
				case manager.newPeerCh <- peer:
					manager.wg.Add(1)
					defer manager.wg.Done()
                    //此处如果顺利会进入for循环 如果失败返回错误我会销毁掉这个peer
					return manager.handle(peer)  
				case <-manager.quitSync:
					return p2p.DiscQuitting
				}
			},
			NodeInfo: func() interface{} {
				return manager.NodeInfo()
			},
			PeerInfo: func(id discover.NodeID) interface{} {
				if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
					return p.Info()
				}
				return nil
			},
		})
	}

File: go-ethereum/eth/peer.go defines two structs, Peer and PeerSet Peer encapsulates the underlying p2p.Peer, and combines some business-related methods, such as SendTransactions, SendNewBlock, etc. PeerSet is used to save the eligible Peers that are finally retained.

    type peer struct {
        id string

        *p2p.Peer
        rw p2p.MsgReadWriter

        version  int         // Protocol version negotiated
        forkDrop *time.Timer // Timed connection dropper if forks aren't validated in time

        head common.Hash
        td   *big.Int
        lock sync.RWMutex

        knownTxs    *set.Set // Set of transaction hashes known to be known by this peer
        knownBlocks *set.Set // Set of block hashes known to be known by this peer
    }

    type peerSet struct {
        peers  map[string]*peer
        lock   sync.RWMutex
        closed bool
    }

refer to

Source: https://github.com/ethereum/go-ethereum/tree/master/p2p

Kademlia algorithm: https://en.wikipedia.org/wiki/Kademlia

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325014975&siteId=291194637