以太坊源码分析之三网络分析之二网络的数据交互

以太坊源码分析之三网络分析之二网络的数据交互
一、 网络的数据交互准备 
在前面提到过启动节点时会启动P2P网络的监听(包括UDP和TCP),
首先看创建一个Service,在前文的makeFullNode 函数时,使用了RegisterEthService这个函数,在这个函数里:
func RegisterEthService(stack *node.Node, cfg *eth.Config) {
var err error
if cfg.SyncMode == downloader.LightSync {
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
            //注册一个轻量级的服务,SPV等
return les.New(ctx, cfg)
})
} else {
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
            //注册一个全节点并在配置情况下添加轻量级服务,也就是全节点既支持全同//步也支持轻同步,默认为FastSync,但新的以太坊中明确定义FastSync和//LightSync两种同步方式在参数设置中废弃
fullNode, err := eth.New(ctx, cfg)
if fullNode != nil && cfg.LightServ > 0 {
ls, _ := les.NewLesServer(fullNode, cfg)
fullNode.AddLesServer(ls)
}
return fullNode, err
})
}
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
}
有必要说明一下这个定义:
const (
    FullSync  SyncMode = iota // 同步完整的区块信息
    FastSync                  // 快速同步header,然后再由header同步全部内容
    LightSync                 // 类似比特币,只下载header并在之后终止
)
在flag.go中,默认定义:
defaultSyncMode = eth.DefaultConfig.SyncMode
SyncModeFlag    = TextMarshalerFlag{
Name:  "syncmode",
Usage: `Blockchain sync mode ("fast", "full", or "light")`,
Value: &defaultSyncMode,
}
而在eth/config.go中可以看到:
var DefaultConfig = Config{
SyncMode: downloader.FastSync, //默认是快速的
…….
}
那么服务的同步方式到底怎么设置呢?在 eth/handler(注意,les/handler.go其它两个服务都有类似情况):
func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkId uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) {
……
// Figure out whether to allow fast sync or not
if mode == downloader.FastSync && blockchain.CurrentBlock().NumberU64() > 0 {
log.Warn("Blockchain not empty, fast sync disabled")
mode = downloader.FullSync
}
if mode == downloader.FastSync {
manager.fastSync = uint32(1)
}
……
}
这说明了什么?说明以太坊启动时的同步模式使用的是快速的,当header的编号为0时,继续使用fastSync,当其不为0时,自动转成fullSync。
这里需要注意的是,这是指在不传递启动参数时,但刚刚也说过了,传递的参数只能是fullSync一种,另外的两种fastSync和LigthSync已经废弃。
说了一些相对的题外话,再扯回来接着看服务。
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
if config.SyncMode == downloader.LightSync {
        //这里就是上文提到的les模式(les/backend.go),在当前这种情况下的错误警告
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
}
if !config.SyncMode.IsValid() {
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
}
    //打存储数据库,用的是leveldb。
chainDb, err := CreateDB(ctx, config, "chaindata")
if err != nil {
return nil, err
}


    //如果需要,启动后台进程升级数据库
stopDbUpgrade := upgradeDeduplicateData(chainDb)
    //创世区块的设置,根据条件判断从配置文件或者数据库加载
chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis)
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
return nil, genesisErr
}
log.Info("Initialised chain configuration", "config", chainConfig)


    //eth变量声明
eth := &Ethereum{
config:         config,
chainDb:        chainDb,
chainConfig:    chainConfig,
eventMux:       ctx.EventMux,
accountManager: ctx.AccountManager,
engine:         CreateConsensusEngine(ctx, &config.Ethash, chainConfig, chainDb),
shutdownChan:   make(chan bool),
stopDbUpgrade:  stopDbUpgrade,
networkId:      config.NetworkId,
gasPrice:       config.GasPrice,
etherbase:      config.Etherbase,
bloomRequests:  make(chan chan *bloombits.Retrieval),
bloomIndexer:   NewBloomIndexer(chainDb, params.BloomBitsBlocks),
}


    //版本比较
log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId)


if !config.SkipBcVersionCheck {
bcVersion := core.GetBlockChainVersion(chainDb)
if bcVersion != core.BlockChainVersion && bcVersion != 0 {
return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d). Run geth upgradedb.\n", bcVersion, core.BlockChainVersion)
}
core.WriteBlockChainVersion(chainDb, core.BlockChainVersion)
}
var (
vmConfig    = vm.Config{EnablePreimageRecording: config.EnablePreimageRecording}
cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout}
)


    //eth内部变量的初始化:1、初始化区块链
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, eth.chainConfig, eth.engine, vmConfig)
if err != nil {
return nil, err
}
// Rewind the chain in case of an incompatible config upgrade.
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
eth.blockchain.SetHead(compat.RewindTo)
core.WriteChainConfig(chainDb, genesisHash, chainConfig)
}
  //2、bloom启动
eth.bloomIndexer.Start(eth.blockchain)


if config.TxPool.Journal != "" {
config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
}
    //3、交易池初始化,和比特币类似,可以存储本地和网络传送的交易
eth.txPool = core.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain)
    //4、P2P协议管理初始化
if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb); err != nil {
return nil, err
}
    //5、挖矿初始化
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
eth.miner.SetExtra(makeExtraData(config.ExtraData))


eth.ApiBackend = &EthApiBackend{eth, nil}
gpoParams := config.GPO
if gpoParams.Default == nil {
gpoParams.Default = config.GasPrice
}
    6、gas预言机初始化
eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams)


return eth, nil
}
在前面的StartNode中会调用:
……
for kind, service := range services {
// Start the next service, stopping all previous upon failure
if err := service.Start(running); err != nil {
      ……
}
……
}
好,看一下它:
func (s *Ethereum) Start(srvr *p2p.Server) error {
// Start the bloom bits servicing goroutines
    //启动布隆过滤器
s.startBloomHandlers()


// Start the RPC service
    //启动RPC服务
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.NetVersion())


// Figure out a max peers count based on the server limits
    //配置最大启动节点
maxPeers := srvr.MaxPeers
if s.config.LightServ > 0 {
if s.config.LightPeers >= srvr.MaxPeers {
return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, srvr.MaxPeers)
}
maxPeers -= s.config.LightPeers
}
// Start the networking layer and the light server if requested
s.protocolManager.Start(maxPeers)//启动最大节点同步数
if s.lesServer != nil {
s.lesServer.Start(srvr)//如果包含轻量级服务也启动之
}
return nil
}
都写到注释里了,没啥太需要关心注意的。
func (pm *ProtocolManager) Start(maxPeers int) {
pm.maxPeers = maxPeers


// broadcast transactions
pm.txCh = make(chan core.TxPreEvent, txChanSize)
pm.txSub = pm.txpool.SubscribeTxPreEvent(pm.txCh)
go pm.txBroadcastLoop()


// broadcast mined blocks
pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{})
go pm.minedBroadcastLoop()


// start sync handlers
go pm.syncer()
go pm.txsyncLoop()
}
服务的启动主要是交易池事件的定阅,启动广播和挖矿的事件订阅。最后启动同步循环。


二、 网络数据通信


在以太坊的网络通信中,是通过Peer来发送和接收数据的,他通过handle来处理回调:
在Server.go中:


func (srv *Server) listenLoop() {
……
fd, err = srv.listener.Accept()  //fd的类型是net.Conn
……
}
而它又被:
// SetupConn runs the handshakes and attempts to add the connection
// as a peer. It returns when the connection has been added as a peer
// or the handshakes have failed.
func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *discover.Node) error {
……
    //注意此处的srv.newTransport(fd)
c := &conn{fd: fd, transport: srv.newTransport(fd), flags: flags, cont: make(chan error)}
err := srv.setupConn(c, flags, dialDest)  如果正常添加新peer
……
return err
}
上面的注意中在:
func (srv *Server) Start() (err error) {
if srv.newTransport == nil {
srv.newTransport = newRLPX   //重定义成了rlpx进行加密
}
}


func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) error {
……
c.caps, c.name = phs.Caps, phs.Name
err = srv.checkpoint(c, srv.addpeer)  //这里看下面一个函数run
if err != nil {
clog.Trace("Rejected peer", "err", err)
return err
}
// If the checks completed successfully, runPeer has now been
// launched by run.
……
return nil
}


func (srv *Server) run(dialstate dialer) {
……
case c := <-srv.addpeer:
// At this point the connection is past the protocol handshake.
// Its capabilities are known and the remote identity is verified.
err := srv.protoHandshakeChecks(peers, inboundCount, c)
if err == nil {
// The handshakes are done and it passed all checks.
//这里接着上面的通道接收新的conn,并依此创建peer
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)  //运行
peers[c.id] = p    //添加到Set中
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
}
……
}
看runPeer(server.go):
func (srv *Server) runPeer(p *Peer) {
if srv.newPeerHook != nil {
srv.newPeerHook(p)
}


// broadcast peer add
srv.peerFeed.Send(&PeerEvent{
Type: PeerEventTypeAdd,
Peer: p.ID(),
})


// run the protocol 运行自己的创建的协议
remoteRequested, err := p.run()


// broadcast peer drop
srv.peerFeed.Send(&PeerEvent{
Type:  PeerEventTypeDrop,
Peer:  p.ID(),
Error: err.Error(),
})


// Note: run waits for existing peers to be sent on srv.delpeer
// before returning, so this send should not select on srv.quit.
srv.delpeer <- peerDrop{p, err, remoteRequested}
}
看peer.go中的run:
func (p *Peer) run() (remoteRequested bool, err error) {
var (
writeStart = make(chan struct{}, 1)
writeErr   = make(chan error, 1)
readErr    = make(chan error, 1)
reason     DiscReason // sent to the peer
)
p.wg.Add(2)
    //读和ping
go p.readLoop(readErr)
go p.pingLoop()


// Start all protocol handlers.
writeStart <- struct{}{}
    //这里启动相应的协议
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
}
继续看readloop:


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()
        //这就是前面提到的handle的回调
if err = p.handle(msg); err != nil {
errc <- err
return
}
}
}
看一下handle:
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
}
看那个子协议处理:
// getProto finds the protocol responsible for handling
// the given message code.
func (p *Peer) getProto(code uint64) (*protoRW, error) {
for _, proto := range p.running {
if code >= proto.offset && code < proto.offset+proto.Length {
return proto, nil
}
}
return nil, newPeerError(errInvalidMsgCode, "%d", code)
}
看看p.running是个什么东东,在前面创建peer时,用了:
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
}
回到Peer的Run函数,看启动协议的函数:
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() {
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()
}()
}
}
上面的proto.Run(p, rw)的Run哪来的,看一下最初初始化PM时的代码(handle.go):
func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkId uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) {
……
// Initiate a sub-protocol for every implemented version we can handle
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
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()
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
},
})
}
……


return manager, nil
}
这样就把相关的Peer创建好了。这里需要注意的是newPeer是eth/peer.go,要和p2p/peer.go区别开来。
创建好后,就相当于在Socket编程中创建了一个链接,就可以直接接收数据了,比如在rlpx.go中:
func (rw *rlpxFrameRW) WriteMsg(msg Msg) error {
……
// write frame MAC. egress MAC hash is up to date because
// frame content was written to it as well.
fmacseed := rw.egressMAC.Sum(nil)
mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed)
_, err := rw.conn.Write(mac)
return err
}
func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) {
// read the header
headbuf := make([]byte, 32)
if _, err := io.ReadFull(rw.conn, headbuf); err != nil {
return msg, err
}
……
return msg, nil
}
读写操作直接落到了IO上。下一次说一下整个的消息流通过程,做为对这一篇的一个补充。

猜你喜欢

转载自blog.csdn.net/fpcc/article/details/80643147