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

     1  // Copyright (c) 2019 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 blockdao
     7  
     8  import (
     9  	"context"
    10  	"sync/atomic"
    11  
    12  	"github.com/pkg/errors"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"go.uber.org/zap"
    15  
    16  	"github.com/iotexproject/go-pkgs/cache"
    17  	"github.com/iotexproject/go-pkgs/hash"
    18  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    19  
    20  	"github.com/iotexproject/iotex-core/action"
    21  	"github.com/iotexproject/iotex-core/blockchain/block"
    22  	"github.com/iotexproject/iotex-core/pkg/lifecycle"
    23  	"github.com/iotexproject/iotex-core/pkg/log"
    24  	"github.com/iotexproject/iotex-core/pkg/prometheustimer"
    25  )
    26  
    27  // vars
    28  var (
    29  	_cacheMtc = prometheus.NewCounterVec(
    30  		prometheus.CounterOpts{
    31  			Name: "iotex_blockdao_cache",
    32  			Help: "IoTeX blockdao cache counter.",
    33  		},
    34  		[]string{"result"},
    35  	)
    36  )
    37  
    38  type (
    39  	// BlockDAO represents the block data access object
    40  	BlockDAO interface {
    41  		Start(ctx context.Context) error
    42  		Stop(ctx context.Context) error
    43  		Height() (uint64, error)
    44  		GetBlockHash(uint64) (hash.Hash256, error)
    45  		GetBlockHeight(hash.Hash256) (uint64, error)
    46  		GetBlock(hash.Hash256) (*block.Block, error)
    47  		GetBlockByHeight(uint64) (*block.Block, error)
    48  		GetReceipts(uint64) ([]*action.Receipt, error)
    49  		ContainsTransactionLog() bool
    50  		TransactionLogs(uint64) (*iotextypes.TransactionLogs, error)
    51  		PutBlock(context.Context, *block.Block) error
    52  		Header(hash.Hash256) (*block.Header, error)
    53  		HeaderByHeight(uint64) (*block.Header, error)
    54  		FooterByHeight(uint64) (*block.Footer, error)
    55  	}
    56  
    57  	blockDAO struct {
    58  		blockStore   BlockDAO
    59  		indexers     []BlockIndexer
    60  		timerFactory *prometheustimer.TimerFactory
    61  		lifecycle    lifecycle.Lifecycle
    62  		headerCache  cache.LRUCache
    63  		bodyCache    cache.LRUCache
    64  		footerCache  cache.LRUCache
    65  		tipHeight    uint64
    66  	}
    67  )
    68  
    69  // NewBlockDAOWithIndexersAndCache returns a BlockDAO with indexers which will consume blocks appended, and
    70  // caches which will speed up reading
    71  func NewBlockDAOWithIndexersAndCache(blkStore BlockDAO, indexers []BlockIndexer, cacheSize int) BlockDAO {
    72  	if blkStore == nil {
    73  		return nil
    74  	}
    75  
    76  	blockDAO := &blockDAO{
    77  		blockStore: blkStore,
    78  		indexers:   indexers,
    79  	}
    80  
    81  	blockDAO.lifecycle.Add(blkStore)
    82  	for _, indexer := range indexers {
    83  		blockDAO.lifecycle.Add(indexer)
    84  	}
    85  	if cacheSize > 0 {
    86  		blockDAO.headerCache = cache.NewThreadSafeLruCache(cacheSize)
    87  		blockDAO.bodyCache = cache.NewThreadSafeLruCache(cacheSize)
    88  		blockDAO.footerCache = cache.NewThreadSafeLruCache(cacheSize)
    89  	}
    90  	timerFactory, err := prometheustimer.New(
    91  		"iotex_block_dao_perf",
    92  		"Performance of block DAO",
    93  		[]string{"type"},
    94  		[]string{"default"},
    95  	)
    96  	if err != nil {
    97  		return nil
    98  	}
    99  	blockDAO.timerFactory = timerFactory
   100  	return blockDAO
   101  }
   102  
   103  // Start starts block DAO and initiates the top height if it doesn't exist
   104  func (dao *blockDAO) Start(ctx context.Context) error {
   105  	err := dao.lifecycle.OnStart(ctx)
   106  	if err != nil {
   107  		return errors.Wrap(err, "failed to start child services")
   108  	}
   109  
   110  	tipHeight, err := dao.blockStore.Height()
   111  	if err != nil {
   112  		return err
   113  	}
   114  	atomic.StoreUint64(&dao.tipHeight, tipHeight)
   115  	return dao.checkIndexers(ctx)
   116  }
   117  
   118  func (dao *blockDAO) checkIndexers(ctx context.Context) error {
   119  	checker := NewBlockIndexerChecker(dao)
   120  	for i, indexer := range dao.indexers {
   121  		if err := checker.CheckIndexer(ctx, indexer, 0, func(height uint64) {
   122  			if height%5000 == 0 {
   123  				log.L().Info(
   124  					"indexer is catching up.",
   125  					zap.Int("indexer", i),
   126  					zap.Uint64("height", height),
   127  				)
   128  			}
   129  		}); err != nil {
   130  			return err
   131  		}
   132  		log.L().Info(
   133  			"indexer is up to date.",
   134  			zap.Int("indexer", i),
   135  		)
   136  	}
   137  	return nil
   138  }
   139  
   140  func (dao *blockDAO) Stop(ctx context.Context) error { return dao.lifecycle.OnStop(ctx) }
   141  
   142  func (dao *blockDAO) GetBlockHash(height uint64) (hash.Hash256, error) {
   143  	timer := dao.timerFactory.NewTimer("get_block_hash")
   144  	defer timer.End()
   145  	return dao.blockStore.GetBlockHash(height)
   146  }
   147  
   148  func (dao *blockDAO) GetBlockHeight(hash hash.Hash256) (uint64, error) {
   149  	timer := dao.timerFactory.NewTimer("get_block_height")
   150  	defer timer.End()
   151  	return dao.blockStore.GetBlockHeight(hash)
   152  }
   153  
   154  func (dao *blockDAO) GetBlock(hash hash.Hash256) (*block.Block, error) {
   155  	timer := dao.timerFactory.NewTimer("get_block")
   156  	defer timer.End()
   157  	return dao.blockStore.GetBlock(hash)
   158  }
   159  
   160  func (dao *blockDAO) GetBlockByHeight(height uint64) (*block.Block, error) {
   161  	timer := dao.timerFactory.NewTimer("get_block_byheight")
   162  	defer timer.End()
   163  	return dao.blockStore.GetBlockByHeight(height)
   164  }
   165  
   166  func (dao *blockDAO) HeaderByHeight(height uint64) (*block.Header, error) {
   167  	if v, ok := lruCacheGet(dao.headerCache, height); ok {
   168  		_cacheMtc.WithLabelValues("hit_header").Inc()
   169  		return v.(*block.Header), nil
   170  	}
   171  	_cacheMtc.WithLabelValues("miss_header").Inc()
   172  
   173  	header, err := dao.blockStore.HeaderByHeight(height)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	lruCachePut(dao.headerCache, height, header)
   179  	return header, nil
   180  }
   181  
   182  func (dao *blockDAO) FooterByHeight(height uint64) (*block.Footer, error) {
   183  	if v, ok := lruCacheGet(dao.footerCache, height); ok {
   184  		_cacheMtc.WithLabelValues("hit_footer").Inc()
   185  		return v.(*block.Footer), nil
   186  	}
   187  	_cacheMtc.WithLabelValues("miss_footer").Inc()
   188  
   189  	footer, err := dao.blockStore.FooterByHeight(height)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	lruCachePut(dao.footerCache, height, footer)
   195  	return footer, nil
   196  }
   197  
   198  func (dao *blockDAO) Height() (uint64, error) {
   199  	return dao.blockStore.Height()
   200  }
   201  
   202  func (dao *blockDAO) Header(h hash.Hash256) (*block.Header, error) {
   203  	if header, ok := lruCacheGet(dao.headerCache, h); ok {
   204  		_cacheMtc.WithLabelValues("hit_header").Inc()
   205  		return header.(*block.Header), nil
   206  	}
   207  	_cacheMtc.WithLabelValues("miss_header").Inc()
   208  
   209  	header, err := dao.blockStore.Header(h)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	lruCachePut(dao.headerCache, h, header)
   215  	return header, nil
   216  }
   217  
   218  func (dao *blockDAO) GetReceipts(height uint64) ([]*action.Receipt, error) {
   219  	timer := dao.timerFactory.NewTimer("get_receipt")
   220  	defer timer.End()
   221  	return dao.blockStore.GetReceipts(height)
   222  }
   223  
   224  func (dao *blockDAO) ContainsTransactionLog() bool {
   225  	return dao.blockStore.ContainsTransactionLog()
   226  }
   227  
   228  func (dao *blockDAO) TransactionLogs(height uint64) (*iotextypes.TransactionLogs, error) {
   229  	timer := dao.timerFactory.NewTimer("get_transactionlog")
   230  	defer timer.End()
   231  	return dao.blockStore.TransactionLogs(height)
   232  }
   233  
   234  func (dao *blockDAO) PutBlock(ctx context.Context, blk *block.Block) error {
   235  	timer := dao.timerFactory.NewTimer("put_block")
   236  	if err := dao.blockStore.PutBlock(ctx, blk); err != nil {
   237  		timer.End()
   238  		return err
   239  	}
   240  	atomic.StoreUint64(&dao.tipHeight, blk.Height())
   241  	header := blk.Header
   242  	lruCachePut(dao.headerCache, blk.Height(), &header)
   243  	lruCachePut(dao.headerCache, header.HashHeader(), &header)
   244  	timer.End()
   245  
   246  	// index the block if there's indexer
   247  	timer = dao.timerFactory.NewTimer("index_block")
   248  	defer timer.End()
   249  	for _, indexer := range dao.indexers {
   250  		if err := indexer.PutBlock(ctx, blk); err != nil {
   251  			return err
   252  		}
   253  	}
   254  	return nil
   255  }
   256  
   257  func lruCacheGet(c cache.LRUCache, key interface{}) (interface{}, bool) {
   258  	if c != nil {
   259  		return c.Get(key)
   260  	}
   261  	return nil, false
   262  }
   263  
   264  func lruCachePut(c cache.LRUCache, k, v interface{}) {
   265  	if c != nil {
   266  		c.Add(k, v)
   267  	}
   268  }