github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/filedao/filedao_v2.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  	"sync/atomic"
    11  	"unsafe"
    12  
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/iotexproject/go-pkgs/cache"
    16  	"github.com/iotexproject/go-pkgs/hash"
    17  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    18  
    19  	"github.com/iotexproject/iotex-core/action"
    20  	"github.com/iotexproject/iotex-core/blockchain/block"
    21  	"github.com/iotexproject/iotex-core/db"
    22  	"github.com/iotexproject/iotex-core/db/batch"
    23  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    24  )
    25  
    26  // namespace for hash, block, and header storage
    27  const (
    28  	_hashDataNS   = "hsh"
    29  	_blockDataNS  = "bdn"
    30  	_headerDataNs = "hdr"
    31  )
    32  
    33  var (
    34  	_fileHeaderKey = []byte("fh")
    35  )
    36  
    37  type (
    38  	// fileDAOv2 handles chain db file after file split activation at v1.1.2
    39  	fileDAOv2 struct {
    40  		filename        string
    41  		header          *FileHeader
    42  		tip             *FileTip
    43  		blkBuffer       *stagingBuffer
    44  		blkStorePbCache cache.LRUCache
    45  		blkCache        cache.LRUCache
    46  		receiptCache    cache.LRUCache
    47  		kvStore         db.KVStore
    48  		batch           batch.KVStoreBatch
    49  		hashStore       db.CountingIndex // store block hash
    50  		blkStore        db.CountingIndex // store raw blocks
    51  		sysStore        db.CountingIndex // store transaction log
    52  		deser           *block.Deserializer
    53  	}
    54  )
    55  
    56  // newFileDAOv2 creates a new v2 file
    57  func newFileDAOv2(bottom uint64, cfg db.Config, deser *block.Deserializer) (*fileDAOv2, error) {
    58  	if bottom == 0 {
    59  		return nil, ErrNotSupported
    60  	}
    61  
    62  	fd := fileDAOv2{
    63  		filename: cfg.DbPath,
    64  		header: &FileHeader{
    65  			Version:        FileV2,
    66  			Compressor:     cfg.Compressor,
    67  			BlockStoreSize: uint64(cfg.BlockStoreBatchSize),
    68  			Start:          bottom,
    69  		},
    70  		tip: &FileTip{
    71  			Height: bottom - 1,
    72  		},
    73  		blkStorePbCache: cache.NewThreadSafeLruCache(16),
    74  		blkCache:        cache.NewThreadSafeLruCache(256),
    75  		receiptCache:    cache.NewThreadSafeLruCache(256),
    76  		kvStore:         db.NewBoltDB(cfg),
    77  		batch:           batch.NewBatch(),
    78  		deser:           deser,
    79  	}
    80  	return &fd, nil
    81  }
    82  
    83  // openFileDAOv2 opens an existing v2 file
    84  func openFileDAOv2(cfg db.Config, deser *block.Deserializer) *fileDAOv2 {
    85  	return &fileDAOv2{
    86  		filename:        cfg.DbPath,
    87  		blkStorePbCache: cache.NewThreadSafeLruCache(16),
    88  		blkCache:        cache.NewThreadSafeLruCache(256),
    89  		receiptCache:    cache.NewThreadSafeLruCache(256),
    90  		kvStore:         db.NewBoltDB(cfg),
    91  		batch:           batch.NewBatch(),
    92  		deser:           deser,
    93  	}
    94  }
    95  
    96  func (fd *fileDAOv2) Start(ctx context.Context) error {
    97  	if err := fd.kvStore.Start(ctx); err != nil {
    98  		return err
    99  	}
   100  
   101  	// check file header
   102  	header, err := ReadHeaderV2(fd.kvStore)
   103  	if err != nil {
   104  		if errors.Cause(err) != db.ErrBucketNotExist && errors.Cause(err) != db.ErrNotExist {
   105  			return errors.Wrap(err, "failed to get file header")
   106  		}
   107  		// write file header and tip
   108  		if err = WriteHeaderV2(fd.kvStore, fd.header); err != nil {
   109  			return err
   110  		}
   111  		if err = WriteTip(fd.kvStore, _headerDataNs, _topHeightKey, fd.tip); err != nil {
   112  			return err
   113  		}
   114  	} else {
   115  		fd.header = header
   116  		// read file tip
   117  		if fd.tip, err = ReadTip(fd.kvStore, _headerDataNs, _topHeightKey); err != nil {
   118  			return err
   119  		}
   120  	}
   121  
   122  	// create counting index for hash, blk, and transaction log
   123  	if fd.hashStore, err = db.NewCountingIndexNX(fd.kvStore, []byte(_hashDataNS)); err != nil {
   124  		return err
   125  	}
   126  	if fd.blkStore, err = db.NewCountingIndexNX(fd.kvStore, []byte(_blockDataNS)); err != nil {
   127  		return err
   128  	}
   129  	if fd.sysStore, err = db.NewCountingIndexNX(fd.kvStore, []byte(_systemLogNS)); err != nil {
   130  		return err
   131  	}
   132  
   133  	// populate staging buffer
   134  	if fd.blkBuffer, err = fd.populateStagingBuffer(); err != nil {
   135  		return err
   136  	}
   137  	return nil
   138  }
   139  
   140  func (fd *fileDAOv2) Stop(ctx context.Context) error {
   141  	return fd.kvStore.Stop(ctx)
   142  }
   143  
   144  func (fd *fileDAOv2) Height() (uint64, error) {
   145  	tip := fd.loadTip()
   146  	return tip.Height, nil
   147  }
   148  
   149  func (fd *fileDAOv2) Bottom() (uint64, error) {
   150  	return fd.header.Start, nil
   151  }
   152  
   153  func (fd *fileDAOv2) ContainsHeight(height uint64) bool {
   154  	return fd.header.Start <= height && height <= fd.loadTip().Height
   155  }
   156  
   157  func (fd *fileDAOv2) GetBlockHash(height uint64) (hash.Hash256, error) {
   158  	if height == 0 {
   159  		return block.GenesisHash(), nil
   160  	}
   161  	if !fd.ContainsHeight(height) {
   162  		return hash.ZeroHash256, db.ErrNotExist
   163  	}
   164  	h, err := fd.hashStore.Get(height - fd.header.Start)
   165  	if err != nil {
   166  		return hash.ZeroHash256, errors.Wrap(err, "failed to get block hash")
   167  	}
   168  	return hash.BytesToHash256(h), nil
   169  }
   170  
   171  func (fd *fileDAOv2) GetBlockHeight(h hash.Hash256) (uint64, error) {
   172  	if h == block.GenesisHash() {
   173  		return 0, nil
   174  	}
   175  	value, err := getValueMustBe8Bytes(fd.kvStore, _blockHashHeightMappingNS, hashKey(h))
   176  	if err != nil {
   177  		return 0, errors.Wrap(err, "failed to get block height")
   178  	}
   179  	return byteutil.BytesToUint64BigEndian(value), nil
   180  }
   181  
   182  func (fd *fileDAOv2) GetBlock(h hash.Hash256) (*block.Block, error) {
   183  	height, err := fd.GetBlockHeight(h)
   184  	if err != nil {
   185  		return nil, errors.Wrap(err, "failed to get block")
   186  	}
   187  	return fd.GetBlockByHeight(height)
   188  }
   189  
   190  func (fd *fileDAOv2) GetBlockByHeight(height uint64) (*block.Block, error) {
   191  	if height == 0 {
   192  		return block.GenesisBlock(), nil
   193  	}
   194  	blk, err := fd.getBlock(height)
   195  	if err != nil {
   196  		return nil, errors.Wrapf(err, "failed to get block at height %d", height)
   197  	}
   198  	return blk, nil
   199  }
   200  
   201  func (fd *fileDAOv2) GetReceipts(height uint64) ([]*action.Receipt, error) {
   202  	receipts, err := fd.getReceipt(height)
   203  	if err != nil {
   204  		return nil, errors.Wrapf(err, "failed to get receipts at height %d", height)
   205  	}
   206  	return receipts, nil
   207  }
   208  
   209  func (fd *fileDAOv2) ContainsTransactionLog() bool {
   210  	return true
   211  }
   212  
   213  func (fd *fileDAOv2) TransactionLogs(height uint64) (*iotextypes.TransactionLogs, error) {
   214  	if !fd.ContainsHeight(height) {
   215  		return nil, ErrNotSupported
   216  	}
   217  
   218  	value, err := fd.sysStore.Get(height - fd.header.Start)
   219  	if err != nil {
   220  		return nil, errors.Wrapf(err, "failed to get transaction log at height %d", height)
   221  	}
   222  	value, err = decompBytes(value, fd.header.Compressor)
   223  	if err != nil {
   224  		return nil, errors.Wrapf(err, "failed to get transaction log at height %d", height)
   225  	}
   226  	return block.DeserializeSystemLogPb(value)
   227  }
   228  
   229  func (fd *fileDAOv2) PutBlock(_ context.Context, blk *block.Block) error {
   230  	tip := fd.loadTip()
   231  	if blk.Height() != tip.Height+1 {
   232  		return ErrInvalidTipHeight
   233  	}
   234  
   235  	// write tip hash and hash-height mapping
   236  	if err := fd.putTipHashHeightMapping(blk); err != nil {
   237  		return errors.Wrap(err, "failed to write hash-height mapping")
   238  	}
   239  
   240  	// write block data
   241  	if err := fd.putBlock(blk); err != nil {
   242  		return errors.Wrap(err, "failed to write block")
   243  	}
   244  
   245  	// write receipt and transaction log
   246  	if err := fd.putTransactionLog(blk); err != nil {
   247  		return errors.Wrap(err, "failed to write receipt")
   248  	}
   249  
   250  	if err := fd.kvStore.WriteBatch(fd.batch); err != nil {
   251  		return errors.Wrapf(err, "failed to put block at height %d", blk.Height())
   252  	}
   253  	fd.batch.Clear()
   254  	// update file tip
   255  	tip = &FileTip{Height: blk.Height(), Hash: blk.HashBlock()}
   256  	fd.storeTip(tip)
   257  	return nil
   258  }
   259  
   260  func (fd *fileDAOv2) DeleteTipBlock() error {
   261  	tip := fd.loadTip()
   262  	height := tip.Height
   263  
   264  	if !fd.ContainsHeight(height) {
   265  		// cannot delete block that does not exist
   266  		return ErrNotSupported
   267  	}
   268  
   269  	// delete hash
   270  	if err := fd.hashStore.Revert(1); err != nil {
   271  		return err
   272  	}
   273  	// delete tip of block storage, if new tip height < lowest block stored in it
   274  	if height-1 < fd.lowestBlockOfStoreTip() {
   275  		if err := fd.blkStore.Revert(1); err != nil {
   276  			return err
   277  		}
   278  	}
   279  	// delete transaction log
   280  	if err := fd.sysStore.Revert(1); err != nil {
   281  		return err
   282  	}
   283  
   284  	// delete hash -> height mapping
   285  	fd.batch.Delete(_blockHashHeightMappingNS, hashKey(tip.Hash), "failed to delete hash -> height mapping")
   286  
   287  	// update file tip
   288  	var (
   289  		h   = hash.ZeroHash256
   290  		err error
   291  	)
   292  	if height > fd.header.Start {
   293  		h, err = fd.GetBlockHash(height - 1)
   294  		if err != nil {
   295  			return err
   296  		}
   297  	}
   298  	tip = &FileTip{Height: height - 1, Hash: h}
   299  	ser, err := tip.Serialize()
   300  	if err != nil {
   301  		return err
   302  	}
   303  	fd.batch.Put(_headerDataNs, _topHeightKey, ser, "failed to put file tip")
   304  
   305  	if err := fd.kvStore.WriteBatch(fd.batch); err != nil {
   306  		return err
   307  	}
   308  	fd.batch.Clear()
   309  	fd.storeTip(tip)
   310  	return nil
   311  }
   312  
   313  func (fd *fileDAOv2) loadTip() *FileTip {
   314  	p := (*unsafe.Pointer)(unsafe.Pointer(&fd.tip))
   315  	return (*FileTip)(atomic.LoadPointer(p))
   316  }
   317  
   318  func (fd *fileDAOv2) storeTip(tip *FileTip) {
   319  	p := (*unsafe.Pointer)(unsafe.Pointer(&fd.tip))
   320  	atomic.StorePointer(p, unsafe.Pointer(tip))
   321  }