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  }