github.com/turingchain2020/turingchain@v1.1.21/blockchain/download.go (about) 1 // Copyright Turing Corp. 2018 All Rights Reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package blockchain 6 7 import ( 8 "fmt" 9 "sync/atomic" 10 "time" 11 12 "github.com/turingchain2020/turingchain/common" 13 "github.com/turingchain2020/turingchain/types" 14 "github.com/golang/protobuf/proto" 15 ) 16 17 //var 18 var ( 19 tempBlockKey = []byte("TB:") 20 lastTempBlockKey = []byte("LTB:") 21 ) 22 23 //const 24 const ( 25 //waitTimeDownLoad 节点启动之后等待开始快速下载的时间秒,超时就切换到普通同步模式 26 waitTimeDownLoad = 120 27 28 //快速下载时需要的最少peer数量 29 bestPeerCount = 2 30 31 normalDownLoadMode = 0 32 fastDownLoadMode = 1 33 chunkDownLoadMode = 2 34 ) 35 36 //DownLoadInfo blockchain模块下载block处理结构体 37 type DownLoadInfo struct { 38 StartHeight int64 39 EndHeight int64 40 Pids []string 41 } 42 43 //ErrCountInfo 启动download时read一个block失败等待最长时间为2分钟,120秒 44 type ErrCountInfo struct { 45 Height int64 46 Count int64 47 } 48 49 //存储temp block height 对应的block 50 func calcHeightToTempBlockKey(height int64) []byte { 51 return append(tempBlockKey, []byte(fmt.Sprintf("%012d", height))...) 52 } 53 54 //存储last temp block height 55 func calcLastTempBlockHeightKey() []byte { 56 return lastTempBlockKey 57 } 58 59 //GetDownloadSyncStatus 获取下载区块的同步模式 60 func (chain *BlockChain) GetDownloadSyncStatus() int { 61 chain.downLoadModeLock.Lock() 62 defer chain.downLoadModeLock.Unlock() 63 return chain.downloadMode 64 } 65 66 //UpdateDownloadSyncStatus 更新下载区块的同步模式 67 func (chain *BlockChain) UpdateDownloadSyncStatus(mode int) { 68 chain.downLoadModeLock.Lock() 69 defer chain.downLoadModeLock.Unlock() 70 chain.downloadMode = mode 71 } 72 73 //FastDownLoadBlocks 开启快速下载区块的模式 74 func (chain *BlockChain) FastDownLoadBlocks() { 75 curHeight := chain.GetBlockHeight() 76 lastTempHight := chain.GetLastTempBlockHeight() 77 78 synlog.Info("FastDownLoadBlocks", "curHeight", curHeight, "lastTempHight", lastTempHight) 79 80 //需要执行完上次已经下载并临时存贮在db中的blocks 81 if lastTempHight != -1 && lastTempHight > curHeight { 82 chain.ReadBlockToExec(lastTempHight, false) 83 } 84 //1:满足bestpeer数量,并且落后区块数量大于5000个开启快速同步 85 //2:落后区块数量小于5000个不开启快速同步,启动普通同步模式 86 //3:启动二分钟如果还不满足快速下载的条件就直接退出,启动普通同步模式 87 88 startTime := types.Now() 89 90 for { 91 curheight := chain.GetBlockHeight() 92 peerMaxBlkHeight := chain.GetPeerMaxBlkHeight() 93 pids := chain.GetBestChainPids() 94 //节点启动时只有落后最优链batchsyncblocknum个区块时才开启这种下载模式 95 if pids != nil && peerMaxBlkHeight != -1 && curheight+batchsyncblocknum >= peerMaxBlkHeight { 96 chain.UpdateDownloadSyncStatus(normalDownLoadMode) 97 synlog.Info("FastDownLoadBlocks:quit!", "curheight", curheight, "peerMaxBlkHeight", peerMaxBlkHeight) 98 break 99 } else if curheight+batchsyncblocknum < peerMaxBlkHeight && len(pids) >= bestPeerCount { 100 synlog.Info("start download blocks!FastDownLoadBlocks", "curheight", curheight, "peerMaxBlkHeight", peerMaxBlkHeight) 101 go chain.ProcDownLoadBlocks(curheight, peerMaxBlkHeight, false, pids) 102 go chain.ReadBlockToExec(peerMaxBlkHeight, true) 103 break 104 } else if types.Since(startTime) > waitTimeDownLoad*time.Second || chain.cfg.SingleMode { 105 chain.UpdateDownloadSyncStatus(normalDownLoadMode) 106 synlog.Info("FastDownLoadBlocks:waitTimeDownLoad:quit!", "curheight", curheight, "peerMaxBlkHeight", peerMaxBlkHeight, "pids", pids) 107 break 108 } else { 109 synlog.Info("FastDownLoadBlocks task sleep 1 second !") 110 time.Sleep(time.Second) 111 } 112 } 113 } 114 115 //ReadBlockToExec 执行快速下载临时存储在db中的block 116 func (chain *BlockChain) ReadBlockToExec(height int64, isNewStart bool) { 117 synlog.Info("ReadBlockToExec starting!!!", "height", height, "isNewStart", isNewStart) 118 var waitCount ErrCountInfo 119 waitCount.Height = 0 120 waitCount.Count = 0 121 cfg := chain.client.GetConfig() 122 for { 123 select { 124 case <-chain.quit: 125 return 126 default: 127 } 128 curheight := chain.GetBlockHeight() 129 peerMaxBlkHeight := chain.GetPeerMaxBlkHeight() 130 131 // 节点同步阶段自己高度小于最大高度batchsyncblocknum时存储block到db批量处理时不刷盘 132 if peerMaxBlkHeight > curheight+batchsyncblocknum && !chain.cfgBatchSync { 133 atomic.CompareAndSwapInt32(&chain.isbatchsync, 1, 0) 134 } else { 135 atomic.CompareAndSwapInt32(&chain.isbatchsync, 0, 1) 136 } 137 if (curheight >= peerMaxBlkHeight && peerMaxBlkHeight != -1) || curheight >= height { 138 chain.cancelDownLoadFlag(isNewStart) 139 synlog.Info("ReadBlockToExec complete!", "curheight", curheight, "height", height, "peerMaxBlkHeight", peerMaxBlkHeight) 140 break 141 } 142 block, err := chain.ReadBlockByHeight(curheight + 1) 143 if err != nil { 144 //在downLoadTask任务退出后,尝试获取block2分钟,还获取不到就直接退出download下载 145 if isNewStart { 146 if !chain.downLoadTask.InProgress() { 147 if waitCount.Height == curheight+1 { 148 waitCount.Count++ 149 } else { 150 waitCount.Height = curheight + 1 151 waitCount.Count = 1 152 } 153 if waitCount.Count >= 120 { 154 chain.cancelDownLoadFlag(isNewStart) 155 synlog.Error("ReadBlockToExec:ReadBlockByHeight:timeout", "height", curheight+1, "peerMaxBlkHeight", peerMaxBlkHeight, "err", err) 156 break 157 } 158 time.Sleep(time.Second) 159 continue 160 } else { 161 synlog.Info("ReadBlockToExec:ReadBlockByHeight", "height", curheight+1, "peerMaxBlkHeight", peerMaxBlkHeight, "err", err) 162 time.Sleep(time.Second) 163 continue 164 } 165 } else { 166 chain.cancelDownLoadFlag(isNewStart) 167 synlog.Error("ReadBlockToExec:ReadBlockByHeight", "height", curheight+1, "peerMaxBlkHeight", peerMaxBlkHeight, "err", err) 168 break 169 } 170 } 171 _, ismain, isorphan, err := chain.ProcessBlock(false, &types.BlockDetail{Block: block}, "download", true, -1) 172 if err != nil { 173 //执行失败强制结束快速下载模式并切换到普通下载模式 174 if isNewStart && chain.downLoadTask.InProgress() { 175 Err := chain.downLoadTask.Cancel() 176 if Err != nil { 177 synlog.Error("ReadBlockToExec:downLoadTask.Cancel!", "height", block.Height, "hash", common.ToHex(block.Hash(cfg)), "isNewStart", isNewStart, "err", Err) 178 } 179 chain.DefaultDownLoadInfo() 180 } 181 182 //清除快速下载的标识并从缓存中删除此执行失败的区块, 183 chain.cancelDownLoadFlag(isNewStart) 184 chain.blockStore.db.Delete(calcHeightToTempBlockKey(block.Height)) 185 186 synlog.Error("ReadBlockToExec:ProcessBlock:err!", "height", block.Height, "hash", common.ToHex(block.Hash(cfg)), "isNewStart", isNewStart, "err", err) 187 break 188 } 189 synlog.Debug("ReadBlockToExec:ProcessBlock:success!", "height", block.Height, "ismain", ismain, "isorphan", isorphan, "hash", common.ToHex(block.Hash(cfg))) 190 } 191 } 192 193 //CancelDownLoadFlag 清除快速下载模式的一些标志 194 func (chain *BlockChain) cancelDownLoadFlag(isNewStart bool) { 195 if isNewStart { 196 chain.UpdateDownloadSyncStatus(normalDownLoadMode) 197 } 198 chain.DelLastTempBlockHeight() 199 synlog.Info("cancelFastDownLoadFlag", "isNewStart", isNewStart) 200 } 201 202 //ReadBlockByHeight 从数据库中读取快速下载临时存储的block信息 203 func (chain *BlockChain) ReadBlockByHeight(height int64) (*types.Block, error) { 204 blockByte, err := chain.blockStore.db.Get(calcHeightToTempBlockKey(height)) 205 if blockByte == nil || err != nil { 206 return nil, types.ErrHeightNotExist 207 } 208 var block types.Block 209 err = proto.Unmarshal(blockByte, &block) 210 if err != nil { 211 storeLog.Error("ReadBlockByHeight", "err", err) 212 return nil, err 213 } 214 //读取成功之后将将此临时存贮删除 215 err = chain.blockStore.db.Delete(calcHeightToTempBlockKey(height - 1)) 216 if err != nil { 217 storeLog.Error("ReadBlockByHeight:Delete", "height", height, "err", err) 218 } 219 return &block, err 220 } 221 222 //WriteBlockToDbTemp 快速下载的block临时存贮到数据库 223 func (chain *BlockChain) WriteBlockToDbTemp(block *types.Block, lastHeightSave bool) error { 224 if block == nil { 225 panic("WriteBlockToDbTemp block is nil") 226 } 227 sync := true 228 if atomic.LoadInt32(&chain.isbatchsync) == 0 { 229 sync = false 230 } 231 beg := types.Now() 232 defer func() { 233 chainlog.Debug("WriteBlockToDbTemp", "height", block.Height, "sync", sync, "cost", types.Since(beg)) 234 }() 235 newbatch := chain.blockStore.NewBatch(sync) 236 237 blockByte, err := proto.Marshal(block) 238 if err != nil { 239 chainlog.Error("WriteBlockToDbTemp:Marshal", "height", block.Height) 240 } 241 newbatch.Set(calcHeightToTempBlockKey(block.Height), blockByte) 242 if lastHeightSave { 243 heightbytes := types.Encode(&types.Int64{Data: block.Height}) 244 newbatch.Set(calcLastTempBlockHeightKey(), heightbytes) 245 } 246 err = newbatch.Write() 247 if err != nil { 248 panic(err) 249 } 250 return nil 251 } 252 253 //GetLastTempBlockHeight 从数据库中获取快速下载的最新的block高度 254 func (chain *BlockChain) GetLastTempBlockHeight() int64 { 255 heightbytes, err := chain.blockStore.db.Get(calcLastTempBlockHeightKey()) 256 if heightbytes == nil || err != nil { 257 chainlog.Error("GetLastTempBlockHeight", "err", err) 258 return -1 259 } 260 261 var height types.Int64 262 err = types.Decode(heightbytes, &height) 263 if err != nil { 264 chainlog.Error("GetLastTempBlockHeight:Decode", "err", err) 265 return -1 266 } 267 return height.Data 268 } 269 270 //DelLastTempBlockHeight 快速下载结束时删除此标志位 271 func (chain *BlockChain) DelLastTempBlockHeight() { 272 err := chain.blockStore.db.Delete(calcLastTempBlockHeightKey()) 273 if err != nil { 274 synlog.Error("DelLastTempBlockHeight", "err", err) 275 } 276 } 277 278 //ProcDownLoadBlocks 处理下载blocks 279 func (chain *BlockChain) ProcDownLoadBlocks(StartHeight int64, EndHeight int64, chunkDown bool, pids []string) { 280 info := chain.GetDownLoadInfo() 281 282 //可能存在上次DownLoad处理过程中下载区块超时,DownLoad任务退出,但DownLoad没有恢复成默认值 283 if info.StartHeight != -1 || info.EndHeight != -1 { 284 synlog.Info("ProcDownLoadBlocks", "pids", info.Pids, "StartHeight", info.StartHeight, "EndHeight", info.EndHeight) 285 } 286 287 chain.DefaultDownLoadInfo() 288 chain.InitDownLoadInfo(StartHeight, EndHeight, pids) 289 if chunkDown { 290 chain.ReqDownLoadChunkBlocks() 291 } else { 292 chain.ReqDownLoadBlocks() 293 } 294 } 295 296 //InitDownLoadInfo 开始新的DownLoad处理 297 func (chain *BlockChain) InitDownLoadInfo(StartHeight int64, EndHeight int64, pids []string) { 298 chain.downLoadlock.Lock() 299 defer chain.downLoadlock.Unlock() 300 301 chain.downLoadInfo.StartHeight = StartHeight 302 chain.downLoadInfo.EndHeight = EndHeight 303 chain.downLoadInfo.Pids = pids 304 synlog.Debug("InitDownLoadInfo begin", "StartHeight", StartHeight, "EndHeight", EndHeight, "pids", pids) 305 306 } 307 308 //DefaultDownLoadInfo 将DownLoadInfo恢复成默认值 309 func (chain *BlockChain) DefaultDownLoadInfo() { 310 chain.downLoadlock.Lock() 311 defer chain.downLoadlock.Unlock() 312 313 chain.downLoadInfo.StartHeight = -1 314 chain.downLoadInfo.EndHeight = -1 315 chain.downLoadInfo.Pids = nil 316 synlog.Debug("DefaultDownLoadInfo") 317 } 318 319 //GetDownLoadInfo 获取DownLoadInfo 320 func (chain *BlockChain) GetDownLoadInfo() *DownLoadInfo { 321 chain.downLoadlock.Lock() 322 defer chain.downLoadlock.Unlock() 323 return chain.downLoadInfo 324 } 325 326 //UpdateDownLoadStartHeight 更新DownLoad请求的起始block高度 327 func (chain *BlockChain) UpdateDownLoadStartHeight(StartHeight int64) { 328 chain.downLoadlock.Lock() 329 defer chain.downLoadlock.Unlock() 330 331 chain.downLoadInfo.StartHeight = StartHeight 332 synlog.Debug("UpdateDownLoadStartHeight", "StartHeight", chain.downLoadInfo.StartHeight, "EndHeight", chain.downLoadInfo.EndHeight, "pids", len(chain.downLoadInfo.Pids)) 333 } 334 335 //UpdateDownLoadPids 更新bestpeers列表 336 func (chain *BlockChain) UpdateDownLoadPids() { 337 pids := chain.GetBestChainPids() 338 339 chain.downLoadlock.Lock() 340 defer chain.downLoadlock.Unlock() 341 if len(pids) != 0 { 342 chain.downLoadInfo.Pids = pids 343 synlog.Info("UpdateDownLoadPids", "StartHeight", chain.downLoadInfo.StartHeight, "EndHeight", chain.downLoadInfo.EndHeight, "pids", len(chain.downLoadInfo.Pids)) 344 } 345 } 346 347 //ReqDownLoadBlocks 请求DownLoad处理的blocks 348 func (chain *BlockChain) ReqDownLoadBlocks() { 349 info := chain.GetDownLoadInfo() 350 if info.StartHeight != -1 && info.EndHeight != -1 && info.Pids != nil { 351 synlog.Info("ReqDownLoadBlocks", "StartHeight", info.StartHeight, "EndHeight", info.EndHeight, "pids", len(info.Pids)) 352 err := chain.FetchBlock(info.StartHeight, info.EndHeight, info.Pids, true) 353 if err != nil { 354 synlog.Error("ReqDownLoadBlocks:FetchBlock", "err", err) 355 } 356 } 357 } 358 359 //DownLoadTimeOutProc 快速下载模式下载区块超时的处理函数 360 func (chain *BlockChain) DownLoadTimeOutProc(height int64) { 361 info := chain.GetDownLoadInfo() 362 synlog.Info("DownLoadTimeOutProc", "timeoutheight", height, "StartHeight", info.StartHeight, "EndHeight", info.EndHeight) 363 364 // 下载超时需要检测下载的pid是否存在,如果所有下载peer都失连,需要退出本次下载 365 // 在处理分叉时从指定节点下载区块超时时,可能是节点失连导致,此时需要退出本次下载 366 if info.Pids != nil { 367 var exist bool 368 for _, pid := range info.Pids { 369 peerinfo := chain.GetPeerInfo(pid) 370 if peerinfo != nil { 371 exist = true 372 } 373 } 374 if !exist { 375 synlog.Info("DownLoadTimeOutProc:peer not exist!", "info.Pids", info.Pids, "GetPeers", chain.GetPeers()) 376 return 377 } 378 } 379 if info.StartHeight != -1 && info.EndHeight != -1 && info.Pids != nil { 380 //从超时的高度继续下载区块 381 if info.StartHeight > height { 382 chain.UpdateDownLoadStartHeight(height) 383 info.StartHeight = height 384 } 385 synlog.Info("DownLoadTimeOutProc:FetchBlock", "StartHeight", info.StartHeight, "EndHeight", info.EndHeight, "pids", len(info.Pids)) 386 err := chain.FetchBlock(info.StartHeight, info.EndHeight, info.Pids, true) 387 if err != nil { 388 synlog.Error("DownLoadTimeOutProc:FetchBlock", "err", err) 389 } 390 } 391 } 392 393 // DownLoadBlocks 下载区块 394 func (chain *BlockChain) DownLoadBlocks() { 395 if !chain.cfg.DisableShard && chain.cfg.EnableFetchP2pstore { 396 // 1.节点开启时候首先尝试进行chunkDownLoad下载 397 chain.UpdateDownloadSyncStatus(chunkDownLoadMode) // 默认模式是fastDownLoadMode 398 if chain.GetDownloadSyncStatus() == chunkDownLoadMode { 399 chain.ChunkDownLoadBlocks() 400 } 401 } else { 402 // 2.其次尝试开启快速下载模式,目前默认开启 403 if chain.GetDownloadSyncStatus() == fastDownLoadMode { 404 chain.FastDownLoadBlocks() 405 } 406 } 407 } 408 409 //ChunkDownLoadBlocks 开启快速下载区块的模式 410 func (chain *BlockChain) ChunkDownLoadBlocks() { 411 curHeight := chain.GetBlockHeight() 412 lastTempHight := chain.GetLastTempBlockHeight() 413 414 synlog.Info("ChunkDownLoadBlocks", "curHeight", curHeight, "lastTempHight", lastTempHight) 415 416 //需要执行完上次已经下载并临时存贮在db中的blocks 417 if lastTempHight != -1 && lastTempHight > curHeight { 418 chain.ReadBlockToExec(lastTempHight, false) 419 } 420 //1:落后区块数量大于MaxRollBlockNum个开启chunk同步,否则开启快速同步 421 //2:启动二分钟如果还不满足chunk同步,则进行快速同步 422 423 startTime := types.Now() 424 425 for { 426 curheight := chain.GetBlockHeight() 427 peerMaxBlkHeight := chain.GetPeerMaxBlkHeight() 428 targetHeight := chain.calcSafetyChunkHeight(peerMaxBlkHeight) // 节点生成chunk的高度滞后于当前高度 429 pids := chain.GetBestChainPids() 430 //节点启动时只有落后最优链batchsyncblocknum个区块时才开启这种下载模式 431 if pids != nil && peerMaxBlkHeight != -1 && curheight+batchsyncblocknum >= peerMaxBlkHeight { 432 synlog.Info("ChunkDownLoadBlocks:quit!", "curheight", curheight, "peerMaxBlkHeight", peerMaxBlkHeight) 433 chain.UpdateDownloadSyncStatus(normalDownLoadMode) 434 break 435 } else if curheight < targetHeight && pids != nil { 436 synlog.Info("start download blocks!ChunkDownLoadBlocks", "curheight", curheight, "peerMaxBlkHeight", peerMaxBlkHeight, "targetHeight", targetHeight) 437 go chain.ProcDownLoadBlocks(curheight, targetHeight, true, pids) 438 // 下载chunk后在该进程执行临时区块 439 go chain.ReadBlockToExec(targetHeight, true) 440 break 441 } else if types.Since(startTime) > waitTimeDownLoad*time.Second*3 || chain.cfg.SingleMode { 442 synlog.Info("ChunkDownLoadBlocks:waitTimeDownLoad:quit!", "curheight", curheight, "peerMaxBlkHeight", peerMaxBlkHeight, "pids", pids) 443 chain.UpdateDownloadSyncStatus(normalDownLoadMode) 444 break 445 } else { 446 synlog.Info("ChunkDownLoadBlocks task sleep 1 second !") 447 time.Sleep(time.Second) 448 } 449 } 450 } 451 452 //ReqDownLoadChunkBlocks 请求DownLoad处理的blocks 453 func (chain *BlockChain) ReqDownLoadChunkBlocks() { 454 info := chain.GetDownLoadInfo() 455 if info.StartHeight != -1 && info.EndHeight != -1 && info.Pids != nil { 456 synlog.Info("ReqDownLoadChunkBlocks", "StartHeight", info.StartHeight, "EndHeight", info.EndHeight, "pids", len(info.Pids)) 457 err := chain.FetchChunkBlock(info.StartHeight, info.EndHeight, info.Pids, true) 458 if err != nil { 459 synlog.Error("ReqDownLoadChunkBlocks:FetchBlock", "err", err) 460 } 461 } 462 } 463 464 //DownLoadChunkTimeOutProc 快速下载模式下载区块超时的处理函数 465 func (chain *BlockChain) DownLoadChunkTimeOutProc(height int64) { 466 info := chain.GetDownLoadInfo() 467 synlog.Info("DownLoadChunkTimeOutProc", "real chunkNum", height, "info.StartHeight", info.StartHeight, "info.EndHeight", info.EndHeight) 468 // TODO 需要检查当前是否有连接节点,如果没有则可能没有连接节点导致超时 469 if len(chain.GetPeers()) == 0 { 470 synlog.Info("DownLoadChunkTimeOutProc:peers not exist!") 471 return 472 } 473 if info.StartHeight != -1 && info.EndHeight != -1 && info.Pids != nil { 474 //从超时的高度继续下载区块 475 if info.StartHeight > height { 476 chain.UpdateDownLoadStartHeight(height) 477 info.StartHeight = height 478 } 479 synlog.Info("DownLoadChunkTimeOutProc:FetchChunkBlock", "StartHeight", info.StartHeight, "EndHeight", info.EndHeight, "pids", len(info.Pids)) 480 err := chain.FetchChunkBlock(info.StartHeight, info.EndHeight, info.Pids, true) 481 if err != nil { 482 synlog.Error("DownLoadChunkTimeOutProc:FetchChunkBlock", "err", err) 483 } 484 } 485 }