这篇跟上一篇的区别是同步数据完成之后,以太坊的工作流程是怎样的
初始化
eth/handler.go
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() }
初始化Start的时候会新建4个goroutine:
txBroadcastLoop: 当收到新的tx时,会将它发送到连接的Peer
MinedBroadcastLoop:当挖出新的区块或者收到新的区块时,会将其发送到连接的Peer
syncer:有新的Peer连接时,会新建goroutine从td最高的Peer同步区块信息
txsyncLoop:当监听到txsyncCh有数据时(新连接peer时会把自身的txpool写到这个channel),新建goroutine发送tx记录到连接的Peer
广播交易
订阅tx消息
上面Start()的时候订阅了txCh消息
func (pool *TxPool) SubscribeTxPreEvent(ch chan<- TxPreEvent) event.Subscription { return pool.scope.Track(pool.txFeed.Subscribe(ch)) }
当自身节点收到了客户端的交易或者收到其他节点发来的交易加到txpool中时,Send订阅消息:core/tx_pool.go
func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) { // We've directly injected a replacement transaction, notify subsystems go pool.txFeed.Send(TxPreEvent{tx}) }这样txBroadcastLoop中就读到了tx:
func (self *ProtocolManager) txBroadcastLoop() { for { select { case event := <-self.txCh: self.BroadcastTx(event.Tx.Hash(), event.Tx) // Err() channel will be closed when unsubscribing. case <-self.txSub.Err(): return } } }
然后BroadCastTx就是发送TxMsg到所有连接的Peer,对端Peer收到Tx后加入自己的Txpool中
这样就达到交易扩散的目的
广播区块
上面Start()的时候订阅了NewMinedBlockEvent
pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{})
这是event.TypeMux自带的功能
当打包新区块成功时,会Post这个event:miner/worker.go
func (self *worker) wait() { for { mustCommitNewWork := true for result := range self.recv { atomic.AddInt32(&self.atWork, -1) if result == nil { continue } block := result.Block work := result.Work // Update the block hash in all logs since it is now available and not when the // receipt/log of individual transactions were created. for _, r := range work.receipts { for _, l := range r.Logs { l.BlockHash = block.Hash() } } for _, log := range work.state.Logs() { log.BlockHash = block.Hash() } stat, err := self.chain.WriteBlockWithState(block, work.receipts, work.state) if err != nil { log.Error("Failed writing block to chain", "err", err) continue } // check if canon block and write transactions if stat == core.CanonStatTy { // implicit by posting ChainHeadEvent mustCommitNewWork = false } // Broadcast the block and announce chain insertion event self.mux.Post(core.NewMinedBlockEvent{Block: block}) var ( events []interface{} logs = work.state.Logs() ) events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) if stat == core.CanonStatTy { events = append(events, core.ChainHeadEvent{Block: block}) } self.chain.PostChainEvents(events, logs) // Insert the block into the set of pending ones to wait for confirmations self.unconfirmed.Insert(block.NumberU64(), block.Hash()) if mustCommitNewWork { self.commitNewWork() } } } }
1 收到挖矿产生的block后WriteBlockWithState()写入本地leveldb
2 Post NewMinedBlockEvent
func (self *ProtocolManager) minedBroadcastLoop() { // automatically stops if unsubscribe for obj := range self.minedBlockSub.Chan() { switch ev := obj.Data.(type) { case core.NewMinedBlockEvent: self.BroadcastBlock(ev.Block, true) // First propagate block to peers self.BroadcastBlock(ev.Block, false) // Only then announce to the rest } } }
这里收到新的区块后,调用BroadcastBlock广播区块,看代码是调用了两次,一次第二个参数为true,一次为fasle
func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { hash := block.Hash() peers := pm.peers.PeersWithoutBlock(hash) // If propagation is requested, send to a subset of the peer if propagate { // Calculate the TD of the block (it's not imported yet, so block.Td is not valid) var td *big.Int if parent := pm.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil { td = new(big.Int).Add(block.Difficulty(), pm.blockchain.GetTd(block.ParentHash(), block.NumberU64()-1)) } else { log.Error("Propagating dangling block", "number", block.Number(), "hash", hash) return } // Send the block to a subset of our peers transfer := peers[:int(math.Sqrt(float64(len(peers))))] for _, peer := range transfer { peer.SendNewBlock(block, td) } log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) return } // Otherwise if the block is indeed in out own chain, announce it if pm.blockchain.HasBlock(hash, block.NumberU64()) { for _, peer := range peers { peer.SendNewBlockHashes([]common.Hash{hash}, []uint64{block.NumberU64()}) } log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) } }
第二个参数:
true:从节点中找开平方根个节点广播整个区块
false:所有节点广播区块的hash;其他节点收到区块的hash,再发送消息获取区块
这么做的目的是减少本节点的瞬时网络负载,免得一次广播太多的block造成自己的网络拥堵
这样就实现了区块的扩散