github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/eth/sync.go (about)

     1  
     2  //<developer>
     3  //    <name>linapex 曹一峰</name>
     4  //    <email>linapex@163.com</email>
     5  //    <wx>superexc</wx>
     6  //    <qqgroup>128148617</qqgroup>
     7  //    <url>https://jsq.ink</url>
     8  //    <role>pku engineer</role>
     9  //    <date>2019-03-16 12:09:39</date>
    10  //</624342636584505344>
    11  
    12  
    13  package eth
    14  
    15  import (
    16  	"math/rand"
    17  	"sync/atomic"
    18  	"time"
    19  
    20  	"github.com/ethereum/go-ethereum/common"
    21  	"github.com/ethereum/go-ethereum/core/types"
    22  	"github.com/ethereum/go-ethereum/eth/downloader"
    23  	"github.com/ethereum/go-ethereum/log"
    24  	"github.com/ethereum/go-ethereum/p2p/discover"
    25  )
    26  
    27  const (
    28  forceSyncCycle      = 10 * time.Second //强制同步的时间间隔,即使没有可用的对等机
    29  minDesiredPeerCount = 5                //开始同步所需的对等机数量
    30  
    31  //这是由TxSyncLoop发送的事务包的目标大小。
    32  //如果单个事务超过此大小,包可能会大于此值。
    33  	txsyncPackSize = 100 * 1024
    34  )
    35  
    36  type txsync struct {
    37  	p   *peer
    38  	txs []*types.Transaction
    39  }
    40  
    41  //SyncTransactions开始将所有当前挂起的事务发送给给定的对等方。
    42  func (pm *ProtocolManager) syncTransactions(p *peer) {
    43  	var txs types.Transactions
    44  	pending, _ := pm.txpool.Pending()
    45  	for _, batch := range pending {
    46  		txs = append(txs, batch...)
    47  	}
    48  	if len(txs) == 0 {
    49  		return
    50  	}
    51  	select {
    52  	case pm.txsyncCh <- &txsync{p, txs}:
    53  	case <-pm.quitSync:
    54  	}
    55  }
    56  
    57  //TxSyncLoop负责每个新事务的初始事务同步
    58  //连接。当一个新的对等点出现时,我们会中继所有当前挂起的
    59  //交易。为了尽量减少出口带宽的使用,我们发送
    60  //一次将小数据包中的事务打包到一个对等机。
    61  func (pm *ProtocolManager) txsyncLoop() {
    62  	var (
    63  		pending = make(map[discover.NodeID]*txsync)
    64  sending = false               //发送是否处于活动状态
    65  pack    = new(txsync)         //正在发送的包
    66  done    = make(chan error, 1) //发送的结果
    67  	)
    68  
    69  //发送从同步开始发送一组事务。
    70  	send := func(s *txsync) {
    71  //用达到目标大小的事务填充包。
    72  		size := common.StorageSize(0)
    73  		pack.p = s.p
    74  		pack.txs = pack.txs[:0]
    75  		for i := 0; i < len(s.txs) && size < txsyncPackSize; i++ {
    76  			pack.txs = append(pack.txs, s.txs[i])
    77  			size += s.txs[i].Size()
    78  		}
    79  //删除将发送的事务。
    80  		s.txs = s.txs[:copy(s.txs, s.txs[len(pack.txs):])]
    81  		if len(s.txs) == 0 {
    82  			delete(pending, s.p.ID())
    83  		}
    84  //在后台发送包。
    85  		s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size)
    86  		sending = true
    87  		go func() { done <- pack.p.SendTransactions(pack.txs) }()
    88  	}
    89  
    90  //选择下一个挂起的同步。
    91  	pick := func() *txsync {
    92  		if len(pending) == 0 {
    93  			return nil
    94  		}
    95  		n := rand.Intn(len(pending)) + 1
    96  		for _, s := range pending {
    97  			if n--; n == 0 {
    98  				return s
    99  			}
   100  		}
   101  		return nil
   102  	}
   103  
   104  	for {
   105  		select {
   106  		case s := <-pm.txsyncCh:
   107  			pending[s.p.ID()] = s
   108  			if !sending {
   109  				send(s)
   110  			}
   111  		case err := <-done:
   112  			sending = false
   113  //停止跟踪导致发送失败的对等机。
   114  			if err != nil {
   115  				pack.p.Log().Debug("Transaction send failed", "err", err)
   116  				delete(pending, pack.p.ID())
   117  			}
   118  //安排下一次发送。
   119  			if s := pick(); s != nil {
   120  				send(s)
   121  			}
   122  		case <-pm.quitSync:
   123  			return
   124  		}
   125  	}
   126  }
   127  
   128  //同步器负责定期与网络同步,两者都是
   129  //下载哈希和块以及处理公告处理程序。
   130  func (pm *ProtocolManager) syncer() {
   131  //启动并确保清除同步机制
   132  	pm.fetcher.Start()
   133  	defer pm.fetcher.Stop()
   134  	defer pm.downloader.Terminate()
   135  
   136  //等待不同事件触发同步操作
   137  	forceSync := time.NewTicker(forceSyncCycle)
   138  	defer forceSync.Stop()
   139  
   140  	for {
   141  		select {
   142  		case <-pm.newPeerCh:
   143  //确保我们有同行可供选择,然后同步
   144  			if pm.peers.Len() < minDesiredPeerCount {
   145  				break
   146  			}
   147  			go pm.synchronise(pm.peers.BestPeer())
   148  
   149  		case <-forceSync.C:
   150  //即使没有足够的对等点,也强制同步
   151  			go pm.synchronise(pm.peers.BestPeer())
   152  
   153  		case <-pm.noMorePeers:
   154  			return
   155  		}
   156  	}
   157  }
   158  
   159  //同步尝试同步我们的本地块链与远程对等。
   160  func (pm *ProtocolManager) synchronise(peer *peer) {
   161  //如果没有对等点,则短路
   162  	if peer == nil {
   163  		return
   164  	}
   165  //确保同行的TD高于我们自己的TD
   166  	currentBlock := pm.blockchain.CurrentBlock()
   167  	td := pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
   168  
   169  	pHead, pTd := peer.Head()
   170  	if pTd.Cmp(td) <= 0 {
   171  		return
   172  	}
   173  //否则,尝试与下载程序同步
   174  	mode := downloader.FullSync
   175  	if atomic.LoadUint32(&pm.fastSync) == 1 {
   176  //已显式请求并授予快速同步
   177  		mode = downloader.FastSync
   178  	} else if currentBlock.NumberU64() == 0 && pm.blockchain.CurrentFastBlock().NumberU64() > 0 {
   179  //数据库似乎是空的,因为当前块是Genesis。然而快速
   180  //块在前面,因此在某个点为该节点启用了快速同步。
   181  //唯一可能发生这种情况的场景是用户手动(或通过
   182  //坏块)将快速同步节点回滚到同步点以下。在这种情况下
   183  //但是重新启用快速同步是安全的。
   184  		atomic.StoreUint32(&pm.fastSync, 1)
   185  		mode = downloader.FastSync
   186  	}
   187  
   188  	if mode == downloader.FastSync {
   189  //确保我们正在同步的对等机的总难度更高。
   190  		if pm.blockchain.GetTdByHash(pm.blockchain.CurrentFastBlock().Hash()).Cmp(pTd) >= 0 {
   191  			return
   192  		}
   193  	}
   194  
   195  //运行同步循环,并禁用快速同步(如果我们已通过透视图块)
   196  	if err := pm.downloader.Synchronise(peer.id, pHead, pTd, mode); err != nil {
   197  		return
   198  	}
   199  	if atomic.LoadUint32(&pm.fastSync) == 1 {
   200  		log.Info("Fast sync complete, auto disabling")
   201  		atomic.StoreUint32(&pm.fastSync, 0)
   202  	}
   203  atomic.StoreUint32(&pm.acceptTxs, 1) //标记初始同步完成
   204  	if head := pm.blockchain.CurrentBlock(); head.NumberU64() > 0 {
   205  //我们已经完成了一个同步循环,通知所有对等方新状态。这条路是
   206  //在需要通知网关节点的星型拓扑网络中至关重要
   207  //它的所有过时的新块的可用性对等。这次失败
   208  //场景通常会出现在私人网络和黑客网络中,
   209  //连接性能下降,但对主网来说也应该是健康的
   210  //更可靠地更新对等点或本地TD状态。
   211  		go pm.BroadcastBlock(head, false)
   212  	}
   213  }
   214