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