go-ethereum区块同步的流程

1 新区快的接收

在eth/handler.go中的handleMsg函数中,包括所有通过p2p发送过来的事件的处理。包括新区快到达事件NewBlockMsg、新交易事件TxMsg、区块头事件BlockHeadersMsg等。我们看对NewBlockMsg的处理:

case msg.Code == NewBlockMsg:
		// Retrieve and decode the propagated block
		var request newBlockData
		if err := msg.Decode(&request); err != nil {
			return errResp(ErrDecode, "%v: %v", msg, err)
		}
		request.Block.ReceivedAt = msg.ReceivedAt
		request.Block.ReceivedFrom = p

		// Mark the peer as owning the block and schedule it for import
		p.MarkBlock(request.Block.Hash())
		pm.fetcher.Enqueue(p.id, request.Block)

		// Assuming the block is importable by the peer, but possibly not yet done so,
		// calculate the head hash and TD that the peer truly must have.
		var (
			trueHead = request.Block.ParentHash()
			trueTD   = new(big.Int).Sub(request.TD, request.Block.Difficulty())
		)
		// Update the peers total difficulty if better than the previous
		if _, td := p.Head(); trueTD.Cmp(td) > 0 {
			p.SetHead(trueHead, trueTD)

			// Schedule a sync if above ours. Note, this will not fire a sync for a gap of
			// a singe block (as the true TD is below the propagated block), however this
			// scenario should easily be covered by the fetcher.
			currentBlock := pm.blockchain.CurrentBlock()
			if trueTD.Cmp(pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())) > 0 {
				go pm.synchronise(p)
			}
		}

首先解析出Block结构数据出来,然后通过pm.fetcher.Enqueque亚入到区块队列中。如果新区块的宗难度td比当前区块的总难度大,则通过 pm.synchronise(p) 向发送该区块的远端节点进行同步。

pm.fetcher.Enqueque函数

// Enqueue tries to fill gaps the fetcher's future import queue.
func (f *Fetcher) Enqueue(peer string, block *types.Block) error {
	op := &inject{
		origin: peer,
		block:  block,
	}
	select {
	case f.inject <- op:
		return nil
	case <-f.quit:
		return errTerminated
	}
}

这个函数将新区块Block通过f.inject通道发送出去。继续追踪f.inject通道。

Fetcher的inject通道

在Fetcher.go的Loop函数中:

// Loop is the main fetcher loop, checking and processing various notification
// events.
func (f *Fetcher) loop() {
    ......
    case op := <-f.inject:
			// A direct block insertion was requested, try and fill any pending gaps
			propBroadcastInMeter.Mark(1)
			f.enqueue(op.origin, op.block)
    ......
}

执行队列压入的函数是fetcher中的enqueque函数。

Fetcher的enqueque函数

/ enqueue schedules a new future import operation, if the block to be imported
// has not yet been seen.
func (f *Fetcher) enqueue(peer string, block *types.Block) {
	hash := block.Hash()

    //防止远端节点的DOS攻击,blockLimit设置为64,从该节点接受的区块在队列中若超过64则不会接受
	count := f.queues[peer] + 1
	if count > blockLimit {
		log.Debug("Discarded propagated block, exceeded allowance", "peer", peer, "number", block.Number(), "hash", hash, "limit", blockLimit)
		propBroadcastDOSMeter.Mark(1)
		f.forgetHash(hash)
		return
	}
	// 丢弃太老或者太新的区块

	if dist := int64(block.NumberU64()) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
		log.Debug("Discarded propagated block, too far away", "peer", peer, "number", block.Number(), "hash", hash, "distance", dist)
		propBroadcastDropMeter.Mark(1)
		f.forgetHash(hash)
		return
	}
	// Schedule the block for future importing
	if _, ok := f.queued[hash]; !ok {
		op := &inject{
			origin: peer,
			block:  block,
		}
		f.queues[peer] = count
		f.queued[hash] = op
		f.queue.Push(op, -int64(block.NumberU64()))//将区块压入队列中等待区块插入
		if f.queueChangeHook != nil {
			f.queueChangeHook(op.block.Hash(), true)
		}
		log.Debug("Queued propagated block", "peer", peer, "number", block.Number(), "hash", hash, "queued", f.queue.Size())
	}
}

2 区块的插入

fetcher对象的构造

在eth/handler.go中的NewProtocolManager函数中构建了fetcher对象:

unc NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode...){
    ......
    inserter := func(blocks types.Blocks) (int, error) {
		// If fast sync is running, deny importing weird blocks
		if atomic.LoadUint32(&manager.fastSync) == 1 {
			log.Warn("Discarded bad propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
			return 0, nil
		}
		atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
		return manager.blockchain.InsertChain(blocks)
	}
	manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer)

	return manager, nil
}

这里首先构造了一个闭包函数inserter,这是执行新区块插入操作的函数。将inserter函数传递给了fetcher对象。

Fetcher对象

fetcher通过Start()函数开启了一个无限循环的协程Loop()

//Start启动了区块同步、接收的通知以及处理哈希通知和区块获取,直到程序结束
func (f *Fetcher) Start() {
	go f.loop()
}

Fetcher.Loop

//Loop是fetcher的主循环,检查和处理各种通知事件
func (f *Fetcher) loop() {
	// Iterate the block fetching until a quit is requested
	fetchTimer := time.NewTimer(0)
	completeTimer := time.NewTimer(0)

	for {
		// Clean up any expired block fetches
		for hash, announce := range f.fetching {
			if time.Since(announce.time) > fetchTimeout {
				f.forgetHash(hash)
			}
		}
		// Import any queued blocks that could potentially fit
		height := f.chainHeight()
		for !f.queue.Empty() {//如果队列不为空
			op := f.queue.PopItem().(*inject)//弹出一个区块
			hash := op.block.Hash()
			if f.queueChangeHook != nil {
				f.queueChangeHook(hash, false)
			}
			// If too high up the chain or phase, continue later
			number := op.block.NumberU64()
			if number > height+1 {//检查区块数
				f.queue.Push(op, -int64(number))
				if f.queueChangeHook != nil {
					f.queueChangeHook(hash, true)
				}
				break
			}
			// Otherwise if fresh and still unknown, try and import
			if number+maxUncleDist < height || f.getBlock(hash) != nil {
				f.forgetBlock(hash)
				continue
			}
			f.insert(op.origin, op.block)//执行插入操作
		}
		// Wait for an outside event to occur
		select {
		case <-f.quit:
			// Fetcher terminating, abort all operations
			return

		case notification := <-f.notify:
			.....
        }
	}
}

loop中对事件的处理,其中比较重要的事件有inject事件和done事件。

     case op := <-f.inject:
			// A direct block insertion was requested, try and fill any pending gaps
			propBroadcastInMeter.Mark(1)
			f.enqueue(op.origin, op.block)

    case hash := <-f.done:
			// A pending import finished, remove all traces of the notification
			f.forgetHash(hash)
			f.forgetBlock(hash)

inject是新区块压入事件。done事件是新区块插入成功的事件。插入成功后,执行forgetBlock()函数将队列中对应的节点的数量减1,然后从队列中删除该区块。

// forgetBlock removes all traces of a queued block from the fetcher's internal
// state.
func (f *Fetcher) forgetBlock(hash common.Hash) {
	if insert := f.queued[hash]; insert != nil {
		f.queues[insert.origin]--
		if f.queues[insert.origin] == 0 {
			delete(f.queues, insert.origin)
		}
		delete(f.queued, hash)
	}
}

Fetcher.insert函数

/ insert spawns a new goroutine to run a block insertion into the chain. If the
// block's number is at the same height as the current import phase, it updates
// the phase states accordingly.
func (f *Fetcher) insert(peer string, block *types.Block) {
	hash := block.Hash()
	// Run the import on a new thread
	log.Debug("Importing propagated block", "peer", peer, "number", block.Number(), "hash", hash)
	go func() {
		defer func() {f.done <- hash }()//退出之前发送f.done事件

		// If the parent's unknown, abort insertion
		parent := f.getBlock(block.ParentHash())
		if parent == nil {
			log.Debug("Unknown parent of propagated block", "peer", peer, "number", block.Number(), "hash", hash, "parent", block.ParentHash())
			return
		}
		// 验证区块
		switch err := f.verifyHeader(block.Header()); err {
		case nil:
			// All ok, quickly propagate to our peers
			propBroadcastOutTimer.UpdateSince(block.ReceivedAt)
			go f.broadcastBlock(block, true)

		case consensus.ErrFutureBlock:
			// Weird future block, don't fail, but neither propagate

		default:
			// Something went very wrong, drop the peer
			log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
			f.dropPeer(peer)
			return
		}

		//执行插入函数
		if _, err := f.insertChain(types.Blocks{block}); err != nil {
			log.Debug("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
			return
		}

		// 若插入成功,则广播区块
		propAnnounceOutTimer.UpdateSince(block.ReceivedAt)
		go f.broadcastBlock(block, false)

		// Invoke the testing hook if needed
		if f.importedHook != nil {
			f.importedHook(block)
		}
	}()
}

真正的插入函数在blockchain.go中。

blockchain.Insertchain函数

// InsertChain attempts to insert the given batch of blocks in to the canonical
// chain or, otherwise, create a fork. If an error is returned it will return
// the index number of the failing block as well an error describing what went
// wrong.
//
// After insertion is done, all accumulated events will be fired.
func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
	n, events, logs, err := bc.insertChain(chain)//插入区块到区块链中
	bc.PostChainEvents(events, logs)//发送事件
	return n, err
}

发送完成通过PostChainEvents函数发送事件给其它订阅者。

PostChainEvents函数

func (bc *BlockChain) PostChainEvents(events []interface{}, logs []*types.Log) {
	log.Info("lzj-log PostChainEvents", "events len",len(events))
	// post event logs for further processing
	if logs != nil {
		bc.logsFeed.Send(logs)
	}
	for _, event := range events {
		switch ev := event.(type) {
		case ChainEvent:
			log.Info("lzj-log send ChainEvent")
			bc.chainFeed.Send(ev)

		case ChainHeadEvent:
			log.Info("lzj-log send ChainHeadEvent")
			bc.chainHeadFeed.Send(ev)

		case ChainSideEvent:
			log.Info("lzj-log send ChainSideEvent")
			bc.chainSideFeed.Send(ev)
		}
	}
}

PostChainEvents在函数里连续发送了3个事件,当第一个事件发送堵塞的时候,剩下的事件会发送不出去。

猜你喜欢

转载自blog.csdn.net/liuzhijun301/article/details/83896895