以太坊之工作流程(同步完数据之后)

这篇跟上一篇的区别是同步数据完成之后,以太坊的工作流程是怎样的

初始化

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造成自己的网络拥堵

这样就实现了区块的扩散






你可能感兴趣的:(区块链)