github.com/turingchain2020/turingchain@v1.1.21/blockchain/export_block.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 "bytes" 9 "errors" 10 "fmt" 11 "io/ioutil" 12 "os/user" 13 "path/filepath" 14 "sync/atomic" 15 "syscall" 16 17 "github.com/turingchain2020/turingchain/common" 18 dbm "github.com/turingchain2020/turingchain/common/db" 19 "github.com/turingchain2020/turingchain/types" 20 ) 21 22 var ( 23 fileHeaderKey = []byte("F:header:") 24 endBlockKey = []byte("F:endblock:") 25 blockHeightKey = []byte("F:blockH:") 26 blockCount int64 = 1024 //只处理当前高度减去1024之前的区块,避免导出侧链的数据 27 dbCache int32 = 4 28 exportlog = chainlog.New("submodule", "export") 29 ) 30 31 // errors 32 var ( 33 ErrIsOrphan = errors.New("ErrIsOrphan") 34 ErrIsSideChain = errors.New("ErrIsSideChain") 35 ErrBlockHeightDiscontinuous = errors.New("ErrBlockHeightDiscontinuous") 36 ErrCurHeightMoreThanEndHeight = errors.New("ErrCurHeightMoreThanEndHeight") 37 ) 38 39 func calcblockHeightKey(height int64) []byte { 40 return append(blockHeightKey, []byte(fmt.Sprintf("%012d", height))...) 41 } 42 43 //ExportBlockProc 通过title导出对应的区块到指定文件中 44 func (chain *BlockChain) ExportBlockProc(title string, dir string, startHeight int64) { 45 //获取文件路径 46 dataDir := getDataDir(dir) 47 err := chain.ExportBlock(title, dataDir, startHeight) 48 exportlog.Info("ExportBlockProc:complete", "title", title, "dir", dir, "err", err) 49 syscall.Exit(0) 50 } 51 52 //ImportBlockProc 导入区块的处理,区块导入结束后退出整个系统 53 func (chain *BlockChain) ImportBlockProc(filename string, dir string) { 54 55 //获取文件路径,空字符串默认当前目录下 56 dataDir := getDataDir(dir) 57 58 //从文件导入区块期间,执行区块设置成不刷磁盘,提高写入数据库的效率 59 if !chain.cfgBatchSync { 60 atomic.CompareAndSwapInt32(&chain.isbatchsync, 1, 0) 61 } 62 err := chain.ImportBlock(filename, dataDir) 63 exportlog.Info("importBlock:complete", "filename", filename, "dir", dir, "err", err) 64 syscall.Exit(0) 65 } 66 67 //ExportBlock 通过指定title和起始高度将block信息导出到一个指定文件中。 68 // title:turingchain/turingchaincoin 69 // startHeight:需要导入/导出的起始高度 70 // dbPath:存储到指定路径,默认当前目录下 71 func (chain *BlockChain) ExportBlock(title, dbPath string, startHeight int64) error { 72 exportlog.Info("exportBlock", "title", title, "startHeight", startHeight, "dbPath", dbPath) 73 74 if startHeight < 0 { 75 return types.ErrInvalidParam 76 } 77 cfg := chain.client.GetConfig() 78 cfgTitle := cfg.GetTitle() 79 //title检测 80 if len(title) == 0 { 81 title = cfgTitle 82 } 83 if title != cfgTitle { 84 exportlog.Error("exportBlock", "title", title, "configTitle", cfgTitle) 85 return types.ErrInvalidParam 86 } 87 //获取blockstore中当前区块的最新高度 88 curheight, err := LoadBlockStoreHeight(chain.blockStore.db) 89 if err != nil || curheight < 0 { 90 exportlog.Error("exportBlock:LoadBlockStoreHeight", "curheight", curheight, "err", err.Error()) 91 return err 92 } 93 94 if startHeight+blockCount >= curheight { 95 exportlog.Info("exportBlock:startHeight+blockCount >= height", "curheight", curheight) 96 return types.ErrInvalidParam 97 } 98 99 exportdb := dbm.NewDB(title, chain.cfg.Driver, dbPath, dbCache) 100 defer exportdb.Close() 101 102 var fileExist bool 103 104 //判断导出文件是否已经存在,校验已经存在的导出文件头信息 105 oldfileHeader, err := getFileHeader(exportdb) 106 if err == nil && oldfileHeader != nil { 107 newfileHeader := types.FileHeader{ 108 Title: title, 109 Driver: chain.cfg.Driver, 110 TestNet: cfg.IsTestNet(), 111 } 112 if !isValidFileHeader(oldfileHeader, &newfileHeader) { 113 exportlog.Error("exportBlock:inValidFileHeader", "oldfileHeader", oldfileHeader, "newfileHeader", newfileHeader) 114 return types.ErrInValidFileHeader 115 } 116 //需要在已有导出的endheight区块继续接着导出,需要校验endHeight 117 endBlock, err := getEndBlock(exportdb) 118 if err != nil { 119 exportlog.Error("exportBlock:EndHeight", "error", err) 120 return err 121 } 122 if endBlock.Height < startHeight || endBlock.Height > curheight-blockCount { 123 exportlog.Error("exportBlock:endHeight<startHeight", "endHeight", endBlock.Height, "startHeight", startHeight, "error", err) 124 return types.ErrBlockHeight 125 } 126 127 // 需要校验block是否连续 128 block, err := chain.blockStore.LoadBlock(endBlock.Height+1, nil) 129 if err != nil { 130 exportlog.Error("exportBlock:LoadBlock", "Height", endBlock.Height+1, "error", err) 131 return err 132 } 133 parentHash := block.Block.ParentHash 134 if !bytes.Equal(endBlock.Hash, parentHash) { 135 exportlog.Error("exportBlock:block discontinuous", "endHeight", endBlock.Height, "hash", common.ToHex(endBlock.Hash), "nextHeight", endBlock.Height+1, "parentHash", common.ToHex(parentHash), "hash", common.ToHex(block.Block.Hash(cfg))) 136 return types.ErrBlockHashNoMatch 137 } 138 fileExist = true 139 startHeight = endBlock.Height + 1 140 } 141 142 batch := exportdb.NewBatch(false) 143 // 设置导出文件头信息 144 if !fileExist { 145 fileHeader := types.FileHeader{ 146 Title: title, 147 Driver: chain.cfg.Driver, 148 TestNet: cfg.IsTestNet(), 149 StartHeight: startHeight, 150 } 151 setFileHeader(batch, &fileHeader) 152 153 endBlock := types.EndBlock{ 154 Height: startHeight, 155 Hash: zeroHash[:], 156 } 157 setEndBlock(batch, &endBlock) 158 159 err = batch.Write() 160 if err != nil { 161 exportlog.Error("exportBlock:batch.Write()", "error", err) 162 return err 163 } 164 batch.Reset() 165 } 166 return chain.exportMainBlock(startHeight, curheight-blockCount, batch) 167 } 168 169 //导出主链block信息到文件中 170 func (chain *BlockChain) exportMainBlock(startHeight, endheight int64, batch dbm.Batch) error { 171 cfg := chain.client.GetConfig() 172 var count = 0 173 for height := startHeight; height <= endheight; height++ { 174 block, err := chain.blockStore.LoadBlock(height, nil) 175 if err != nil { 176 exportlog.Error("exportMainBlock:LoadBlock", "height", height, "error", err) 177 return err 178 } 179 count += block.Size() 180 blockinfo := types.Encode(block.Block) 181 batch.Set(calcblockHeightKey(height), blockinfo) 182 183 endBlock := types.EndBlock{ 184 Height: height, 185 Hash: block.Block.Hash(cfg), 186 } 187 setEndBlock(batch, &endBlock) 188 189 if count > types.MaxBlockSizePerTime { 190 exportlog.Info("exportBlock", "height", height) 191 err := batch.Write() 192 if err != nil { 193 storeLog.Error("exportMainBlock:batch.Write()", "height", height, "error", err) 194 return err 195 } 196 batch.Reset() 197 count = 0 198 } 199 } 200 if count > 0 { 201 err := batch.Write() 202 if err != nil { 203 exportlog.Error("exportMainBlock:batch.Write()", "height", endheight, "error", err) 204 return err 205 } 206 exportlog.Info("exportBlock:complete!", "endheight", endheight) 207 batch.Reset() 208 } 209 return nil 210 } 211 212 //ImportBlock 通过指定文件导入block 213 func (chain *BlockChain) ImportBlock(filename, dbPath string) error { 214 cfg := chain.client.GetConfig() 215 if len(filename) == 0 { 216 filename = cfg.GetTitle() 217 } 218 219 db := dbm.NewDB(filename, chain.cfg.Driver, dbPath, dbCache) 220 defer db.Close() 221 222 //获取文件头信息并做基本的校验 223 newfileHeader := types.FileHeader{ 224 Title: cfg.GetTitle(), 225 Driver: chain.cfg.Driver, 226 TestNet: cfg.IsTestNet(), 227 } 228 fileHeader, err := getFileHeader(db) 229 230 if err != nil || fileHeader.StartHeight < 0 || !isValidFileHeader(fileHeader, &newfileHeader) { 231 exportlog.Error("importBlock:fileHeader", "filename", filename, "dbPath", dbPath, "fileHeader", fileHeader, "cfg.fileHeader", newfileHeader, "err", err) 232 return types.ErrInValidFileHeader 233 } 234 startHeight := fileHeader.StartHeight 235 236 endBlock, err := getEndBlock(db) 237 if err != nil { 238 exportlog.Error("importBlock:getEndBlock", "error", err) 239 return err 240 } 241 endHeight := endBlock.Height 242 243 // 获取当前链的区块高度,并校验当前高度和要导入的文件中的startHeight高度 244 //当前高度小于startHeight时,区块高度无法连续返回错误 245 //从当前高度继续读取区块并处理 246 curheight := chain.GetBlockHeight() 247 248 if curheight < startHeight { 249 exportlog.Error("importBlock", "curheight", curheight, "startHeight", startHeight) 250 return ErrBlockHeightDiscontinuous 251 } 252 if curheight > endHeight { 253 exportlog.Error("importBlock", "curheight", curheight, "endHeight", endHeight) 254 return ErrCurHeightMoreThanEndHeight 255 } 256 if curheight >= startHeight { 257 startHeight = curheight + 1 258 } 259 260 //从文件开始导入block 261 for i := startHeight; i <= endHeight; i++ { 262 block, err := getBlock(db, i) 263 if err != nil { 264 exportlog.Error("importBlock:getBlock", "Height", i, "err", err) 265 return err 266 } 267 err = chain.mainChainImport(block) 268 if err != nil { 269 exportlog.Error("importBlock:mainChainImport", "Height", i, "err", err) 270 return err 271 } 272 } 273 return nil 274 } 275 276 //mainChainImport 主链的导入区块处理函数 277 func (chain *BlockChain) mainChainImport(block *types.Block) error { 278 cfg := chain.client.GetConfig() 279 blockDetail := types.BlockDetail{ 280 Block: block, 281 } 282 exportlog.Info("mainChainImport", "height", block.Height, "Hash", common.ToHex(block.Hash(cfg)), "ParentHash", common.ToHex(block.ParentHash)) 283 284 _, isMainChain, isOrphan, err := chain.ProcessBlock(false, &blockDetail, "import", true, -1) 285 if err == types.ErrBlockExist { 286 return nil 287 } else if err != nil { 288 return err 289 } 290 if !isMainChain { 291 return ErrIsSideChain 292 } 293 if isOrphan { 294 return ErrIsOrphan 295 } 296 return nil 297 } 298 299 //isValidFileHeader 校验文件头信息 300 func isValidFileHeader(oldFileHeader, newFileHeader *types.FileHeader) bool { 301 if oldFileHeader.Title != newFileHeader.Title || 302 oldFileHeader.Driver != newFileHeader.Driver || 303 oldFileHeader.TestNet != newFileHeader.TestNet { 304 return false 305 } 306 return true 307 } 308 309 //getFileHeader获取文件头信息 310 func getFileHeader(db dbm.DB) (*types.FileHeader, error) { 311 headertitle, err := db.Get(fileHeaderKey) 312 if err != nil { 313 return nil, err 314 } 315 var fileHeader types.FileHeader 316 err = types.Decode(headertitle, &fileHeader) 317 if err != nil { 318 exportlog.Error("getFileHeader", "headertitle", string(headertitle), "err", err) 319 return nil, err 320 } 321 return &fileHeader, nil 322 } 323 324 //setFileHeader 设置文件头信息到数据库中 325 func setFileHeader(batch dbm.Batch, fileHeader *types.FileHeader) { 326 fileHeaderinfo := types.Encode(fileHeader) 327 batch.Set(fileHeaderKey, fileHeaderinfo) 328 } 329 330 //getEndBlock 获取endblock的信息 331 func getEndBlock(db dbm.DB) (*types.EndBlock, error) { 332 var endBlock types.EndBlock 333 334 storeEndHeight, err := db.Get(endBlockKey) 335 if err != nil { 336 exportlog.Error("getEndBlock", "error", err) 337 return nil, err 338 } 339 err = types.Decode(storeEndHeight, &endBlock) 340 if err != nil || endBlock.Height < 0 { 341 exportlog.Error("getEndBlock:Unmarshal", "storeEndHeight", string(storeEndHeight), "error", err) 342 return nil, err 343 } 344 return &endBlock, nil 345 } 346 347 //setEndBlock 设置endblock的信息 348 func setEndBlock(batch dbm.Batch, endBlock *types.EndBlock) { 349 endBlockinfo := types.Encode(endBlock) 350 batch.Set(endBlockKey, endBlockinfo) 351 } 352 353 //从数据库中获取对应高度的block信息 354 func getBlock(db dbm.DB, height int64) (*types.Block, error) { 355 data, err := db.Get(calcblockHeightKey(height)) 356 if err != nil { 357 exportlog.Error("getBlock:storeblock", "Height", height, "err", err) 358 return nil, err 359 } 360 var block types.Block 361 err = types.Decode(data, &block) 362 if err != nil { 363 exportlog.Error("getBlock:Decode", "err", err) 364 return nil, err 365 } 366 return &block, nil 367 } 368 369 // getDataDir 获取文件路径 "/something/~/something/" 370 func getDataDir(datadir string) string { 371 372 if len(datadir) >= 2 && datadir[:2] == "~/" { 373 usr, err := user.Current() 374 if err != nil { 375 panic(err) 376 } 377 dir := usr.HomeDir 378 datadir = filepath.Join(dir, datadir[2:]) 379 } 380 if len(datadir) >= 6 && datadir[:6] == "$TEMP/" { 381 dir, err := ioutil.TempDir("", "turingchaindatadir-") 382 if err != nil { 383 panic(err) 384 } 385 datadir = filepath.Join(dir, datadir[6:]) 386 } 387 return datadir 388 }