github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/filedao/filedao_legacy.go (about)

     1  // Copyright (c) 2020 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package filedao
     7  
     8  import (
     9  	"context"
    10  	"os"
    11  	"sync"
    12  	"sync/atomic"
    13  
    14  	"github.com/iotexproject/go-pkgs/cache"
    15  	"github.com/iotexproject/go-pkgs/hash"
    16  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    17  	"github.com/pkg/errors"
    18  	"go.uber.org/zap"
    19  	"google.golang.org/protobuf/proto"
    20  
    21  	"github.com/iotexproject/iotex-core/action"
    22  	"github.com/iotexproject/iotex-core/blockchain/block"
    23  	"github.com/iotexproject/iotex-core/db"
    24  	"github.com/iotexproject/iotex-core/db/batch"
    25  	"github.com/iotexproject/iotex-core/pkg/compress"
    26  	"github.com/iotexproject/iotex-core/pkg/enc"
    27  	"github.com/iotexproject/iotex-core/pkg/lifecycle"
    28  	"github.com/iotexproject/iotex-core/pkg/log"
    29  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    30  )
    31  
    32  const (
    33  	_blockNS       = "blk"
    34  	_blockHeaderNS = "bhr"
    35  	_blockBodyNS   = "bbd"
    36  	_blockFooterNS = "bfr"
    37  	_receiptsNS    = "rpt"
    38  )
    39  
    40  var (
    41  	_heightPrefix       = []byte("he.")
    42  	_heightToFileBucket = []byte("h2f")
    43  	// patternLen         = len("00000000.db")
    44  	// suffixLen          = len(".db")
    45  )
    46  
    47  type (
    48  	// fileDAOLegacy handles chain db file before file split activation at v1.1.2
    49  	fileDAOLegacy struct {
    50  		compressBlock bool
    51  		lifecycle     lifecycle.Lifecycle
    52  		cfg           db.Config
    53  		mutex         sync.RWMutex // for create new db file
    54  		topIndex      atomic.Value
    55  		htf           db.RangeIndex
    56  		kvStore       db.KVStore
    57  		kvStores      cache.LRUCache //store like map[index]db.KVStore,index from 1...N
    58  		deser         *block.Deserializer
    59  	}
    60  )
    61  
    62  // newFileDAOLegacy creates a new legacy file
    63  func newFileDAOLegacy(cfg db.Config, deser *block.Deserializer) (FileDAO, error) {
    64  	return &fileDAOLegacy{
    65  		compressBlock: cfg.CompressLegacy,
    66  		cfg:           cfg,
    67  		kvStore:       db.NewBoltDB(cfg),
    68  		kvStores:      cache.NewThreadSafeLruCache(0),
    69  		deser:         deser,
    70  	}, nil
    71  }
    72  
    73  func (fd *fileDAOLegacy) Start(ctx context.Context) error {
    74  	if err := fd.kvStore.Start(ctx); err != nil {
    75  		return err
    76  	}
    77  
    78  	// set init height value and transaction log flag
    79  	if _, err := fd.kvStore.Get(_blockNS, _topHeightKey); err != nil &&
    80  		errors.Cause(err) == db.ErrNotExist {
    81  		zero8bytes := make([]byte, 8)
    82  		if err := fd.kvStore.Put(_blockNS, _topHeightKey, zero8bytes); err != nil {
    83  			return errors.Wrap(err, "failed to write initial value for top height")
    84  		}
    85  		if err := fd.kvStore.Put(_systemLogNS, zero8bytes, []byte(_systemLogNS)); err != nil {
    86  			return errors.Wrap(err, "failed to write initial value for transaction log")
    87  		}
    88  	}
    89  
    90  	// loop thru all legacy files
    91  	base := fd.cfg.DbPath
    92  	_, files := checkAuxFiles(base, FileLegacyAuxiliary)
    93  	var maxN uint64
    94  	for _, file := range files {
    95  		index, ok := isAuxFile(file, base)
    96  		if !ok {
    97  			continue
    98  		}
    99  		if _, _, err := fd.openDB(index); err != nil {
   100  			return err
   101  		}
   102  		if uint64(index) > maxN {
   103  			maxN = uint64(index)
   104  		}
   105  	}
   106  	if maxN == 0 {
   107  		maxN = 1
   108  	}
   109  	fd.topIndex.Store(maxN)
   110  	return nil
   111  }
   112  
   113  func (fd *fileDAOLegacy) Stop(ctx context.Context) error {
   114  	if err := fd.lifecycle.OnStop(ctx); err != nil {
   115  		return err
   116  	}
   117  	return fd.kvStore.Stop(ctx)
   118  }
   119  
   120  func (fd *fileDAOLegacy) Height() (uint64, error) {
   121  	value, err := getValueMustBe8Bytes(fd.kvStore, _blockNS, _topHeightKey)
   122  	if err != nil {
   123  		return 0, errors.Wrap(err, "failed to get top height")
   124  	}
   125  	return enc.MachineEndian.Uint64(value), nil
   126  }
   127  
   128  func (fd *fileDAOLegacy) GetBlockHash(height uint64) (hash.Hash256, error) {
   129  	if height == 0 {
   130  		return block.GenesisHash(), nil
   131  	}
   132  	h := hash.ZeroHash256
   133  	value, err := fd.kvStore.Get(_blockHashHeightMappingNS, heightKey(height))
   134  	if err != nil {
   135  		return h, errors.Wrap(err, "failed to get block hash")
   136  	}
   137  	if len(h) != len(value) {
   138  		return h, errors.Wrapf(err, "blockhash is broken with length = %d", len(value))
   139  	}
   140  	copy(h[:], value)
   141  	return h, nil
   142  }
   143  
   144  func (fd *fileDAOLegacy) GetBlockHeight(h hash.Hash256) (uint64, error) {
   145  	if h == block.GenesisHash() {
   146  		return 0, nil
   147  	}
   148  	value, err := getValueMustBe8Bytes(fd.kvStore, _blockHashHeightMappingNS, hashKey(h))
   149  	if err != nil {
   150  		return 0, errors.Wrap(err, "failed to get block height")
   151  	}
   152  	return enc.MachineEndian.Uint64(value), nil
   153  }
   154  
   155  func (fd *fileDAOLegacy) GetBlock(h hash.Hash256) (*block.Block, error) {
   156  	if h == block.GenesisHash() {
   157  		return block.GenesisBlock(), nil
   158  	}
   159  	header, err := fd.Header(h)
   160  	if err != nil {
   161  		return nil, errors.Wrapf(err, "failed to get block header %x", h)
   162  	}
   163  	body, err := fd.body(h)
   164  	if err != nil {
   165  		return nil, errors.Wrapf(err, "failed to get block body %x", h)
   166  	}
   167  	footer, err := fd.footer(h)
   168  	if err != nil {
   169  		return nil, errors.Wrapf(err, "failed to get block footer %x", h)
   170  	}
   171  	return &block.Block{
   172  		Header: *header,
   173  		Body:   *body,
   174  		Footer: *footer,
   175  	}, nil
   176  }
   177  
   178  func (fd *fileDAOLegacy) GetBlockByHeight(height uint64) (*block.Block, error) {
   179  	hash, err := fd.GetBlockHash(height)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	return fd.GetBlock(hash)
   184  }
   185  
   186  func (fd *fileDAOLegacy) HeaderByHeight(height uint64) (*block.Header, error) {
   187  	hash, err := fd.GetBlockHash(height)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	return fd.Header(hash)
   192  }
   193  
   194  func (fd *fileDAOLegacy) FooterByHeight(height uint64) (*block.Footer, error) {
   195  	hash, err := fd.GetBlockHash(height)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	return fd.footer(hash)
   200  }
   201  
   202  func (fd *fileDAOLegacy) GetReceipts(height uint64) ([]*action.Receipt, error) {
   203  	kvStore, _, err := fd.getDBFromHeight(height)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	value, err := kvStore.Get(_receiptsNS, byteutil.Uint64ToBytes(height))
   208  	if err != nil {
   209  		return nil, errors.Wrapf(err, "failed to get receipts of block %d", height)
   210  	}
   211  	if len(value) == 0 {
   212  		return nil, errors.Wrap(ErrDataCorruption, "block receipts missing")
   213  	}
   214  	receiptsPb := &iotextypes.Receipts{}
   215  	if err := proto.Unmarshal(value, receiptsPb); err != nil {
   216  		return nil, errors.Wrap(err, "failed to unmarshal block receipts")
   217  	}
   218  
   219  	var blockReceipts []*action.Receipt
   220  	for _, receiptPb := range receiptsPb.Receipts {
   221  		receipt := &action.Receipt{}
   222  		receipt.ConvertFromReceiptPb(receiptPb)
   223  		blockReceipts = append(blockReceipts, receipt)
   224  	}
   225  	return blockReceipts, nil
   226  }
   227  
   228  func (fd *fileDAOLegacy) Header(h hash.Hash256) (*block.Header, error) {
   229  	value, err := fd.getBlockValue(_blockHeaderNS, h)
   230  	if err != nil {
   231  		return nil, errors.Wrapf(err, "failed to get block header %x", h)
   232  	}
   233  	if fd.compressBlock {
   234  		value, err = compress.DecompGzip(value)
   235  		if err != nil {
   236  			return nil, errors.Wrapf(err, "error when decompressing a block header %x", h)
   237  		}
   238  	}
   239  	if len(value) == 0 {
   240  		return nil, errors.Wrapf(ErrDataCorruption, "block header %x is missing", h)
   241  	}
   242  
   243  	header := &block.Header{}
   244  	if err := header.Deserialize(value); err != nil {
   245  		return nil, errors.Wrapf(err, "failed to deserialize block header %x", h)
   246  	}
   247  	return header, nil
   248  }
   249  
   250  func (fd *fileDAOLegacy) body(h hash.Hash256) (*block.Body, error) {
   251  	value, err := fd.getBlockValue(_blockBodyNS, h)
   252  	if err != nil {
   253  		return nil, errors.Wrapf(err, "failed to get block body %x", h)
   254  	}
   255  	if fd.compressBlock {
   256  		value, err = compress.DecompGzip(value)
   257  		if err != nil {
   258  			return nil, errors.Wrapf(err, "error when decompressing a block body %x", h)
   259  		}
   260  	}
   261  
   262  	if len(value) == 0 {
   263  		// block body could be empty
   264  		return &block.Body{}, nil
   265  	}
   266  	return fd.deser.DeserializeBody(value)
   267  }
   268  
   269  func (fd *fileDAOLegacy) footer(h hash.Hash256) (*block.Footer, error) {
   270  	value, err := fd.getBlockValue(_blockFooterNS, h)
   271  	if err != nil {
   272  		return nil, errors.Wrapf(err, "failed to get block footer %x", h)
   273  	}
   274  	if fd.compressBlock {
   275  		value, err = compress.DecompGzip(value)
   276  		if err != nil {
   277  			return nil, errors.Wrapf(err, "error when decompressing a block footer %x", h)
   278  		}
   279  	}
   280  	if len(value) == 0 {
   281  		return nil, errors.Wrapf(ErrDataCorruption, "block footer %x is missing", h)
   282  	}
   283  
   284  	footer := &block.Footer{}
   285  	if err := footer.Deserialize(value); err != nil {
   286  		return nil, errors.Wrapf(err, "failed to deserialize block footer %x", h)
   287  	}
   288  	return footer, nil
   289  }
   290  
   291  func (fd *fileDAOLegacy) ContainsTransactionLog() bool {
   292  	sys, err := fd.kvStore.Get(_systemLogNS, make([]byte, 8))
   293  	return err == nil && string(sys) == _systemLogNS
   294  }
   295  
   296  func (fd *fileDAOLegacy) TransactionLogs(height uint64) (*iotextypes.TransactionLogs, error) {
   297  	if !fd.ContainsTransactionLog() {
   298  		return nil, ErrNotSupported
   299  	}
   300  
   301  	kvStore, _, err := fd.getDBFromHeight(height)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	logsBytes, err := kvStore.Get(_systemLogNS, heightKey(height))
   306  	if err != nil {
   307  		return nil, errors.Wrap(err, "failed to get transaction log")
   308  	}
   309  	return block.DeserializeSystemLogPb(logsBytes)
   310  }
   311  
   312  func (fd *fileDAOLegacy) PutBlock(ctx context.Context, blk *block.Block) error {
   313  	serHeader, err := blk.Header.Serialize()
   314  	if err != nil {
   315  		return errors.Wrap(err, "failed to serialize block header")
   316  	}
   317  	serBody, err := blk.Body.Serialize()
   318  	if err != nil {
   319  		return errors.Wrap(err, "failed to serialize block body")
   320  	}
   321  	serFooter, err := blk.Footer.Serialize()
   322  	if err != nil {
   323  		return errors.Wrap(err, "failed to serialize block footer")
   324  	}
   325  	if fd.compressBlock {
   326  		serHeader, err = compress.CompGzip(serHeader)
   327  		if err != nil {
   328  			return errors.Wrapf(err, "error when compressing a block header")
   329  		}
   330  		serBody, err = compress.CompGzip(serBody)
   331  		if err != nil {
   332  			return errors.Wrapf(err, "error when compressing a block body")
   333  		}
   334  		serFooter, err = compress.CompGzip(serFooter)
   335  		if err != nil {
   336  			return errors.Wrapf(err, "error when compressing a block footer")
   337  		}
   338  	}
   339  	batchForBlock := batch.NewBatch()
   340  	hash := blk.HashBlock()
   341  	batchForBlock.Put(_blockHeaderNS, hash[:], serHeader, "failed to put block header")
   342  	batchForBlock.Put(_blockBodyNS, hash[:], serBody, "failed to put block body")
   343  	batchForBlock.Put(_blockFooterNS, hash[:], serFooter, "failed to put block footer")
   344  	blkHeight := blk.Height()
   345  	heightKey := heightKey(blkHeight)
   346  	if fd.ContainsTransactionLog() {
   347  		if sysLog := blk.TransactionLog(); sysLog != nil {
   348  			batchForBlock.Put(_systemLogNS, heightKey, sysLog.Serialize(), "failed to put transaction log")
   349  		}
   350  	}
   351  	kv, _, err := fd.getTopDB(blkHeight)
   352  	if err != nil {
   353  		return err
   354  	}
   355  	// write receipts
   356  	if blk.Receipts != nil {
   357  		receipts := iotextypes.Receipts{}
   358  		for _, r := range blk.Receipts {
   359  			receipts.Receipts = append(receipts.Receipts, r.ConvertToReceiptPb())
   360  		}
   361  		if receiptsBytes, err := proto.Marshal(&receipts); err == nil {
   362  			batchForBlock.Put(_receiptsNS, byteutil.Uint64ToBytes(blkHeight), receiptsBytes, "failed to put receipts")
   363  		} else {
   364  			log.L().Error("failed to serialize receipits for block", zap.Uint64("height", blkHeight))
   365  		}
   366  	}
   367  	if err := kv.WriteBatch(batchForBlock); err != nil {
   368  		return err
   369  	}
   370  
   371  	b := batch.NewBatch()
   372  	heightValue := byteutil.Uint64ToBytes(blkHeight)
   373  	hashKey := hashKey(hash)
   374  	b.Put(_blockHashHeightMappingNS, hashKey, heightValue, "failed to put hash -> height mapping")
   375  	b.Put(_blockHashHeightMappingNS, heightKey, hash[:], "failed to put height -> hash mapping")
   376  	tipHeight, err := fd.kvStore.Get(_blockNS, _topHeightKey)
   377  	if err != nil {
   378  		return errors.Wrap(err, "failed to get top height")
   379  	}
   380  	if blkHeight > enc.MachineEndian.Uint64(tipHeight) {
   381  		b.Put(_blockNS, _topHeightKey, heightValue, "failed to put top height")
   382  		b.Put(_blockNS, _topHashKey, hash[:], "failed to put top hash")
   383  	}
   384  	return fd.kvStore.WriteBatch(b)
   385  }
   386  
   387  func (fd *fileDAOLegacy) DeleteTipBlock() error {
   388  	// First obtain tip height from db
   389  	height, err := fd.Height()
   390  	if err != nil {
   391  		return errors.Wrap(err, "failed to get tip height")
   392  	}
   393  	if height == 0 {
   394  		// should not delete genesis block
   395  		return errors.New("cannot delete genesis block")
   396  	}
   397  	// Obtain tip block hash
   398  	hash, err := fd.getTipHash()
   399  	if err != nil {
   400  		return errors.Wrap(err, "failed to get tip block hash")
   401  	}
   402  
   403  	b := batch.NewBatch()
   404  	batchForBlock := batch.NewBatch()
   405  	whichDB, _, err := fd.getDBFromHeight(height)
   406  	if err != nil {
   407  		return err
   408  	}
   409  	// Delete hash -> block mapping
   410  	batchForBlock.Delete(_blockHeaderNS, hash[:], "failed to delete block header")
   411  	batchForBlock.Delete(_blockBodyNS, hash[:], "failed to delete block body")
   412  	batchForBlock.Delete(_blockFooterNS, hash[:], "failed to delete block footer")
   413  	// delete receipt
   414  	batchForBlock.Delete(_receiptsNS, byteutil.Uint64ToBytes(height), "failed to delete receipt")
   415  	// Delete hash -> height mapping
   416  	hashKey := hashKey(hash)
   417  	b.Delete(_blockHashHeightMappingNS, hashKey, "failed to delete hash -> height mapping")
   418  	// Delete height -> hash mapping
   419  	heightKey := heightKey(height)
   420  	b.Delete(_blockHashHeightMappingNS, heightKey, "failed to delete height -> hash mapping")
   421  
   422  	// Update tip height
   423  	b.Put(_blockNS, _topHeightKey, byteutil.Uint64ToBytes(height-1), "failed to put top height")
   424  	// Update tip hash
   425  	hash2, err := fd.GetBlockHash(height - 1)
   426  	if err != nil {
   427  		return errors.Wrap(err, "failed to get tip block hash")
   428  	}
   429  	b.Put(_blockNS, _topHashKey, hash2[:], "failed to put top hash")
   430  
   431  	if err := fd.kvStore.WriteBatch(b); err != nil {
   432  		return err
   433  	}
   434  	return whichDB.WriteBatch(batchForBlock)
   435  }
   436  
   437  // getTipHash returns the blockchain tip hash
   438  func (fd *fileDAOLegacy) getTipHash() (hash.Hash256, error) {
   439  	value, err := fd.kvStore.Get(_blockNS, _topHashKey)
   440  	if err != nil {
   441  		return hash.ZeroHash256, errors.Wrap(err, "failed to get tip hash")
   442  	}
   443  	return hash.BytesToHash256(value), nil
   444  }
   445  
   446  func (fd *fileDAOLegacy) indexFile(height uint64, index []byte) error {
   447  	fd.mutex.Lock()
   448  	defer fd.mutex.Unlock()
   449  
   450  	if fd.htf == nil {
   451  		htf, err := db.NewRangeIndex(fd.kvStore, _heightToFileBucket, make([]byte, 8))
   452  		if err != nil {
   453  			return err
   454  		}
   455  		fd.htf = htf
   456  	}
   457  	return fd.htf.Insert(height, index)
   458  }
   459  
   460  // getFileIndex return the db filename
   461  func (fd *fileDAOLegacy) getFileIndex(height uint64) ([]byte, error) {
   462  	fd.mutex.RLock()
   463  	defer fd.mutex.RUnlock()
   464  
   465  	if fd.htf == nil {
   466  		htf, err := db.NewRangeIndex(fd.kvStore, _heightToFileBucket, make([]byte, 8))
   467  		if err != nil {
   468  			return nil, err
   469  		}
   470  		fd.htf = htf
   471  	}
   472  	return fd.htf.Get(height)
   473  }
   474  
   475  func (fd *fileDAOLegacy) getTopDB(blkHeight uint64) (kvStore db.KVStore, index uint64, err error) {
   476  	if fd.cfg.SplitDBSizeMB == 0 || blkHeight <= fd.cfg.SplitDBHeight {
   477  		return fd.kvStore, 0, nil
   478  	}
   479  	topIndex := fd.topIndex.Load().(uint64)
   480  	dat, err := os.Stat(kthAuxFileName(fd.cfg.DbPath, topIndex))
   481  	if err != nil {
   482  		if !os.IsNotExist(err) {
   483  			// something wrong getting FileInfo
   484  			return
   485  		}
   486  		// index the height --> file index mapping
   487  		if err = fd.indexFile(blkHeight, byteutil.Uint64ToBytesBigEndian(topIndex)); err != nil {
   488  			return
   489  		}
   490  		// db file does not exist, create it
   491  		return fd.openDB(topIndex)
   492  	}
   493  	// other errors except file does not exist
   494  	if err != nil {
   495  		return
   496  	}
   497  	// file exists,but need create new db
   498  	if uint64(dat.Size()) > fd.cfg.SplitDBSize() {
   499  		kvStore, index, _ = fd.openDB(topIndex + 1)
   500  		fd.topIndex.Store(index)
   501  		// index the height --> file index mapping
   502  		_ = fd.indexFile(blkHeight, byteutil.Uint64ToBytesBigEndian(index))
   503  		return
   504  	}
   505  	// db exist,need load from kvStores
   506  	kv, ok := fd.kvStores.Get(topIndex)
   507  	if ok {
   508  		kvStore, ok = kv.(db.KVStore)
   509  		if !ok {
   510  			err = errors.New("db convert error")
   511  		}
   512  		index = topIndex
   513  		return
   514  	}
   515  	// file exists,but not opened
   516  	return fd.openDB(topIndex)
   517  }
   518  
   519  // getDBFromHash returns db of this block stored
   520  func (fd *fileDAOLegacy) getDBFromHash(h hash.Hash256) (db.KVStore, uint64, error) {
   521  	height, err := fd.GetBlockHeight(h)
   522  	if err != nil {
   523  		return nil, 0, err
   524  	}
   525  	return fd.getDBFromHeight(height)
   526  }
   527  
   528  func (fd *fileDAOLegacy) getDBFromHeight(blkHeight uint64) (kvStore db.KVStore, index uint64, err error) {
   529  	if fd.cfg.SplitDBSizeMB == 0 {
   530  		return fd.kvStore, 0, nil
   531  	}
   532  	if blkHeight <= fd.cfg.SplitDBHeight {
   533  		return fd.kvStore, 0, nil
   534  	}
   535  	// get file index
   536  	value, err := fd.getFileIndex(blkHeight)
   537  	if err != nil {
   538  		return
   539  	}
   540  	return fd.getDBFromIndex(byteutil.BytesToUint64BigEndian(value))
   541  }
   542  
   543  func (fd *fileDAOLegacy) getDBFromIndex(idx uint64) (kvStore db.KVStore, index uint64, err error) {
   544  	if idx == 0 {
   545  		return fd.kvStore, 0, nil
   546  	}
   547  	kv, ok := fd.kvStores.Get(idx)
   548  	if ok {
   549  		kvStore, ok = kv.(db.KVStore)
   550  		if !ok {
   551  			err = errors.New("db convert error")
   552  		}
   553  		index = idx
   554  		return
   555  	}
   556  	// if user rm some db files manully,then call this method will create new file
   557  	return fd.openDB(idx)
   558  }
   559  
   560  // getBlockValue get block's data from db,if this db failed,it will try the previous one
   561  func (fd *fileDAOLegacy) getBlockValue(ns string, h hash.Hash256) ([]byte, error) {
   562  	whichDB, index, err := fd.getDBFromHash(h)
   563  	if err != nil {
   564  		return nil, err
   565  	}
   566  	value, err := whichDB.Get(ns, h[:])
   567  	if errors.Cause(err) == db.ErrNotExist {
   568  		idx := index - 1
   569  		if index == 0 {
   570  			idx = 0
   571  		}
   572  		db, _, err := fd.getDBFromIndex(idx)
   573  		if err != nil {
   574  			return nil, err
   575  		}
   576  		value, err = db.Get(ns, h[:])
   577  		if err != nil {
   578  			return nil, err
   579  		}
   580  	}
   581  	return value, err
   582  }
   583  
   584  // openDB open file if exists, or create new file
   585  func (fd *fileDAOLegacy) openDB(idx uint64) (kvStore db.KVStore, index uint64, err error) {
   586  	if idx == 0 {
   587  		return fd.kvStore, 0, nil
   588  	}
   589  	cfg := fd.cfg
   590  	cfg.DbPath = kthAuxFileName(cfg.DbPath, idx)
   591  
   592  	fd.mutex.Lock()
   593  	defer fd.mutex.Unlock()
   594  	// open or create this db file
   595  	var newFile bool
   596  	_, err = os.Stat(cfg.DbPath)
   597  	if err != nil {
   598  		if !os.IsNotExist(err) {
   599  			// something wrong getting FileInfo
   600  			return
   601  		}
   602  		newFile = true
   603  	}
   604  
   605  	kvStore = db.NewBoltDB(cfg)
   606  	fd.kvStores.Add(idx, kvStore)
   607  	err = kvStore.Start(context.Background())
   608  	if err != nil {
   609  		return
   610  	}
   611  
   612  	if newFile {
   613  		if err = kvStore.Put(_systemLogNS, make([]byte, 8), []byte(_systemLogNS)); err != nil {
   614  			return
   615  		}
   616  	}
   617  	fd.lifecycle.Add(kvStore)
   618  	index = idx
   619  	return
   620  }
   621  
   622  func heightKey(height uint64) []byte {
   623  	return append(_heightPrefix, byteutil.Uint64ToBytes(height)...)
   624  }