Brotherhood Block Chain Introduction Course ETF Source Code Analysis p2p-peer.go Source Code Analysis

Keywords: Blockchain network

In the second half of 2018, the industry of block chain is gradually fading away from the impetuosity and rationality at the beginning of its development. On the surface, it seems that the demand and status of relevant talents are declining. But in fact, it is the gradual decline of the initial bubble that gives people more attention to the real technology of the block chain.

nat means network address translation. This part of the source code is relatively independent and single, so it is not analyzed here for the time being. You just need to understand the basic functions.

nat has two network protocols, upnp and pmp.

UPnP application scenario (pmp is a protocol similar to upnp)

If users access the Internet through NAT, and need to use such software as BC, eMule and other P2P, then UPnP function will bring great convenience. UPnP can automatically map the port numbers of BC and eMule to the public network, so that users on the public network can also initiate connections on the private side of NAT.

The main function is to provide an interface to map IP + ports of the intranet to IP + ports of routers. This means that the program on the Intranet has an IP address on the extranet, so that users of the public network can access you directly. Otherwise, you need to drill holes in UDP for access.

UDP Protocol in p2p

Nowadays most users run in an intranet environment. The ports monitored in the Intranet environment can not be accessed directly by other public network programs. It needs to go through a process of drilling holes. Only then can the two sides connect. This is called UDP holing.

In the p2p code. peer represents a well-established network link. There may be multiple protocols running on one link. For example, the ETH agreement. Swarm's agreement. Or Whisper's agreement.

Structure of peer

    type protoRW struct {
        Protocol
        in     chan Msg        // receices read messages
        closed <-chan struct{} // receives when peer is shutting down
        wstart <-chan struct{} // receives when write may start
        werr   chan<- error    // for write results
        offset uint64
        w      MsgWriter
    }

    // Protocol represents a P2P subprotocol implementation.
    type Protocol struct {
        // Name should contain the official protocol name,
        // often a three-letter word.
        Name string
    
        // Version should contain the version number of the protocol.
        Version uint
    
        // Length should contain the number of message codes used
        // by the protocol.
        Length uint64
    
        // Run is called in a new groutine when the protocol has been
        // negotiated with a peer. It should read and write messages from
        // rw. The Payload for each message must be fully consumed.
        //
        // The peer connection is closed when Start returns. It should return
        // any protocol-level error (such as an I/O error) that is
        // encountered.
        Run func(peer *Peer, rw MsgReadWriter) error
    
        // NodeInfo is an optional helper method to retrieve protocol specific metadata
        // about the host node.
        NodeInfo func() interface{}
    
        // PeerInfo is an optional helper method to retrieve protocol specific metadata
        // about a certain peer in the network. If an info retrieval function is set,
        // but returns nil, it is assumed that the protocol handshake is still running.
        PeerInfo func(id discover.NodeID) interface{}
    }

    // Peer represents a connected remote node.
    type Peer struct {
        rw      *conn
        running map[string]*protoRW   //Operating protocols
        log     log.Logger
        created mclock.AbsTime
    
        wg       sync.WaitGroup
        protoErr chan error
        closed   chan struct{}
        disc     chan DiscReason
    
        // events receives message send / receive events if set
        events *event.Feed
    }

Creation of peer to find protomap supported by current peer according to matching

func newPeer(conn *conn, protocols []Protocol) *Peer {
    protomap := matchProtocols(protocols, conn.caps, conn)
    p := &Peer{
        rw:       conn,
        running:  protomap,
        created:  mclock.Now(),
        disc:     make(chan DiscReason),
        protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop
        closed:   make(chan struct{}),
        log:      log.New("id", conn.id, "conn", conn.flags),
    }
    return p
}

The peer startup starts two goroutine threads. One is reading. One is to perform ping operations.

func (p *Peer) run() (remoteRequested bool, err error) {
    var (
        writeStart = make(chan struct{}, 1)  //A pipe used to control when it can be written.
        writeErr   = make(chan error, 1)
        readErr    = make(chan error, 1)
        reason     DiscReason // sent to the peer
    )
    p.wg.Add(2)
    go p.readLoop(readErr)
    go p.pingLoop()

    // Start all protocol handlers.
    writeStart <- struct{}{}
    //Start all protocols.
    p.startProtocols(writeStart, writeErr)

    // Wait for an error or disconnect.
loop:
    for {
        select {
        case err = <-writeErr:
            // A write finished. Allow the next write to start if
            // there was no error.
            if err != nil {
                reason = DiscNetworkError
                break loop
            }
            writeStart <- struct{}{}
        case err = <-readErr:
            if r, ok := err.(DiscReason); ok {
                remoteRequested = true
                reason = r
            } else {
                reason = DiscNetworkError
            }
            break loop
        case err = <-p.protoErr:
            reason = discReasonForError(err)
            break loop
        case err = <-p.disc:
            break loop
        }
    }

    close(p.closed)
    p.rw.close(reason)
    p.wg.Wait()
    return remoteRequested, err
}

startProtocols method, which traverses all protocols.

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))
        // This is equivalent to opening a goroutine for each protocol. Call its Run method.
        go func() {
            // The proto.Run(p, rw) method should be a dead cycle. If returned, an error was encountered.
            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()
        }()
    }
}

Look back at the readLoop method. This method is also a dead cycle. Call p.rw to read an Msg (the RW is actually the object of the frameRLPx mentioned earlier, that is, the object after the subframe. Then the corresponding processing is carried out according to the type of Msg, if the type of Msg is the type of protocol running internally. Then send it to the proto.in queue of the corresponding protocol.

func (p *Peer) readLoop(errc chan<- error) {
    defer p.wg.Done()
    for {
        msg, err := p.rw.ReadMsg()
        if err != nil {
            errc <- err
            return
        }
        msg.ReceivedAt = time.Now()
        if err = p.handle(msg); err != nil {
            errc <- err
            return
        }
    }
}
func (p *Peer) handle(msg Msg) error {
    switch {
    case msg.Code == pingMsg:
        msg.Discard()
        go SendItems(p.rw, pongMsg)
    case msg.Code == discMsg:
        var reason [1]DiscReason
        // This is the last message. We don't need to discard or
        // check errors because, the connection will be closed after it.
        rlp.Decode(msg.Payload, &reason)
        return reason[0]
    case msg.Code < baseProtocolLength:
        // ignore other base protocol messages
        return msg.Discard()
    default:
        // it's a subprotocol message
        proto, err := p.getProto(msg.Code)
        if err != nil {
            return fmt.Errorf("msg code out of range: %v", msg.Code)
        }
        select {
        case proto.in <- msg:
            return nil
        case <-p.closed:
            return io.EOF
        }
    }
    return nil
}

Look at Ping Loop. This method is simple. It's sending Ping Msg messages to the opposite end on a regular basis.

Finally, look at protoRW's read and write methods. You can see that both read and write are blocking

Posted by leeharvey09 on Fri, 25 Jan 2019 18:57:13 -0800