github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/filedao/filedao.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  	"fmt"
    11  	"sync"
    12  
    13  	"github.com/pkg/errors"
    14  	"go.uber.org/zap"
    15  
    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/pkg/log"
    23  )
    24  
    25  const (
    26  	_blockHashHeightMappingNS = "h2h"
    27  	_systemLogNS              = "syl"
    28  )
    29  
    30  var (
    31  	_topHeightKey = []byte("th")
    32  	_topHashKey   = []byte("ts")
    33  	_hashPrefix   = []byte("ha.")
    34  )
    35  
    36  // vars
    37  var (
    38  	ErrFileNotExist     = errors.New("file does not exist")
    39  	ErrFileCantAccess   = errors.New("cannot access file")
    40  	ErrFileInvalid      = errors.New("file format is not valid")
    41  	ErrNotSupported     = errors.New("feature not supported")
    42  	ErrAlreadyExist     = errors.New("block already exist")
    43  	ErrInvalidTipHeight = errors.New("invalid tip height")
    44  	ErrDataCorruption   = errors.New("data is corrupted")
    45  )
    46  
    47  type (
    48  	// BaseFileDAO represents the basic data access object
    49  	BaseFileDAO interface {
    50  		Start(ctx context.Context) error
    51  		Stop(ctx context.Context) error
    52  		Height() (uint64, error)
    53  		GetBlockHash(uint64) (hash.Hash256, error)
    54  		GetBlockHeight(hash.Hash256) (uint64, error)
    55  		GetBlock(hash.Hash256) (*block.Block, error)
    56  		GetBlockByHeight(uint64) (*block.Block, error)
    57  		GetReceipts(uint64) ([]*action.Receipt, error)
    58  		ContainsTransactionLog() bool
    59  		TransactionLogs(uint64) (*iotextypes.TransactionLogs, error)
    60  		PutBlock(context.Context, *block.Block) error
    61  		DeleteTipBlock() error
    62  	}
    63  
    64  	// FileDAO represents the data access object for managing block db file
    65  	FileDAO interface {
    66  		BaseFileDAO
    67  		Header(hash.Hash256) (*block.Header, error)
    68  		HeaderByHeight(uint64) (*block.Header, error)
    69  		FooterByHeight(uint64) (*block.Footer, error)
    70  	}
    71  
    72  	// fileDAO implements FileDAO
    73  	fileDAO struct {
    74  		lock              sync.Mutex
    75  		topIndex          uint64
    76  		splitHeight       uint64
    77  		cfg               db.Config
    78  		currFd            BaseFileDAO
    79  		legacyFd          FileDAO
    80  		v2Fd              *FileV2Manager // a collection of v2 db files
    81  		blockDeserializer *block.Deserializer
    82  	}
    83  )
    84  
    85  // NewFileDAO creates an instance of FileDAO
    86  func NewFileDAO(cfg db.Config, deser *block.Deserializer) (FileDAO, error) {
    87  	header, err := readFileHeader(cfg.DbPath, FileAll)
    88  	if err != nil {
    89  		if err != ErrFileNotExist {
    90  			return nil, err
    91  		}
    92  		// start new chain db using v2 format
    93  		if err := createNewV2File(1, cfg, deser); err != nil {
    94  			return nil, err
    95  		}
    96  		header = &FileHeader{Version: FileV2}
    97  	}
    98  
    99  	switch header.Version {
   100  	case FileLegacyAuxiliary:
   101  		// default chain db file is legacy format, but not master, the master file has been corrupted
   102  		return nil, ErrFileInvalid
   103  	case FileLegacyMaster:
   104  		// master file is legacy format
   105  		return CreateFileDAO(true, cfg, deser)
   106  	case FileV2:
   107  		// master file is v2 format
   108  		return CreateFileDAO(false, cfg, deser)
   109  	default:
   110  		panic(fmt.Errorf("corrupted file version: %s", header.Version))
   111  	}
   112  }
   113  
   114  // NewFileDAOInMemForTest creates an in-memory FileDAO for testing
   115  func NewFileDAOInMemForTest() (FileDAO, error) {
   116  	return newTestInMemFd()
   117  }
   118  
   119  func (fd *fileDAO) Start(ctx context.Context) error {
   120  	if fd.legacyFd != nil {
   121  		if err := fd.legacyFd.Start(ctx); err != nil {
   122  			return err
   123  		}
   124  	}
   125  	if fd.v2Fd != nil {
   126  		if err := fd.v2Fd.Start(ctx); err != nil {
   127  			return err
   128  		}
   129  	}
   130  
   131  	if fd.v2Fd != nil {
   132  		fd.currFd, fd.splitHeight = fd.v2Fd.TopFd()
   133  	} else {
   134  		fd.currFd = fd.legacyFd
   135  	}
   136  	return nil
   137  }
   138  
   139  func (fd *fileDAO) Stop(ctx context.Context) error {
   140  	if fd.legacyFd != nil {
   141  		if err := fd.legacyFd.Stop(ctx); err != nil {
   142  			return err
   143  		}
   144  	}
   145  	if fd.v2Fd != nil {
   146  		return fd.v2Fd.Stop(ctx)
   147  	}
   148  	return nil
   149  }
   150  
   151  func (fd *fileDAO) Height() (uint64, error) {
   152  	return fd.currFd.Height()
   153  }
   154  
   155  func (fd *fileDAO) GetBlockHash(height uint64) (hash.Hash256, error) {
   156  	if fd.v2Fd != nil {
   157  		if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil {
   158  			return v2.GetBlockHash(height)
   159  		}
   160  	}
   161  
   162  	if fd.legacyFd != nil {
   163  		return fd.legacyFd.GetBlockHash(height)
   164  	}
   165  	return hash.ZeroHash256, ErrNotSupported
   166  }
   167  
   168  func (fd *fileDAO) GetBlockHeight(hash hash.Hash256) (uint64, error) {
   169  	var (
   170  		height uint64
   171  		err    error
   172  	)
   173  	if fd.v2Fd != nil {
   174  		if height, err = fd.v2Fd.GetBlockHeight(hash); err == nil {
   175  			return height, nil
   176  		}
   177  	}
   178  
   179  	if fd.legacyFd != nil {
   180  		return fd.legacyFd.GetBlockHeight(hash)
   181  	}
   182  	return 0, err
   183  }
   184  
   185  func (fd *fileDAO) GetBlock(hash hash.Hash256) (*block.Block, error) {
   186  	var (
   187  		blk *block.Block
   188  		err error
   189  	)
   190  	if fd.v2Fd != nil {
   191  		if blk, err = fd.v2Fd.GetBlock(hash); err == nil {
   192  			return blk, nil
   193  		}
   194  	}
   195  
   196  	if fd.legacyFd != nil {
   197  		return fd.legacyFd.GetBlock(hash)
   198  	}
   199  	return nil, err
   200  }
   201  
   202  func (fd *fileDAO) GetBlockByHeight(height uint64) (*block.Block, error) {
   203  	if fd.v2Fd != nil {
   204  		if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil {
   205  			return v2.GetBlockByHeight(height)
   206  		}
   207  	}
   208  
   209  	if fd.legacyFd != nil {
   210  		return fd.legacyFd.GetBlockByHeight(height)
   211  	}
   212  	return nil, ErrNotSupported
   213  }
   214  
   215  func (fd *fileDAO) Header(hash hash.Hash256) (*block.Header, error) {
   216  	var (
   217  		blk *block.Block
   218  		err error
   219  	)
   220  	if fd.v2Fd != nil {
   221  		if blk, err = fd.v2Fd.GetBlock(hash); err == nil {
   222  			return &blk.Header, nil
   223  		}
   224  	}
   225  
   226  	if fd.legacyFd != nil {
   227  		return fd.legacyFd.Header(hash)
   228  	}
   229  	return nil, err
   230  }
   231  
   232  func (fd *fileDAO) HeaderByHeight(height uint64) (*block.Header, error) {
   233  	if fd.v2Fd != nil {
   234  		if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil {
   235  			blk, err := v2.GetBlockByHeight(height)
   236  			if err != nil {
   237  				return nil, err
   238  			}
   239  			return &blk.Header, nil
   240  		}
   241  	}
   242  
   243  	if fd.legacyFd != nil {
   244  		return fd.legacyFd.HeaderByHeight(height)
   245  	}
   246  	return nil, ErrNotSupported
   247  }
   248  
   249  func (fd *fileDAO) FooterByHeight(height uint64) (*block.Footer, error) {
   250  	if fd.v2Fd != nil {
   251  		if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil {
   252  			blk, err := v2.GetBlockByHeight(height)
   253  			if err != nil {
   254  				return nil, err
   255  			}
   256  			return &blk.Footer, nil
   257  		}
   258  	}
   259  
   260  	if fd.legacyFd != nil {
   261  		return fd.legacyFd.FooterByHeight(height)
   262  	}
   263  	return nil, ErrNotSupported
   264  }
   265  
   266  func (fd *fileDAO) GetReceipts(height uint64) ([]*action.Receipt, error) {
   267  	if fd.v2Fd != nil {
   268  		if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil {
   269  			return v2.GetReceipts(height)
   270  		}
   271  	}
   272  
   273  	if fd.legacyFd != nil {
   274  		return fd.legacyFd.GetReceipts(height)
   275  	}
   276  	return nil, ErrNotSupported
   277  }
   278  
   279  func (fd *fileDAO) ContainsTransactionLog() bool {
   280  	// TODO: change to ContainsTransactionLog(uint64)
   281  	return fd.currFd.ContainsTransactionLog()
   282  }
   283  
   284  func (fd *fileDAO) TransactionLogs(height uint64) (*iotextypes.TransactionLogs, error) {
   285  	if fd.v2Fd != nil {
   286  		if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil {
   287  			return v2.TransactionLogs(height)
   288  		}
   289  	}
   290  
   291  	if fd.legacyFd != nil {
   292  		return fd.legacyFd.TransactionLogs(height)
   293  	}
   294  	return nil, ErrNotSupported
   295  }
   296  
   297  func (fd *fileDAO) PutBlock(ctx context.Context, blk *block.Block) error {
   298  	// bail out if block already exists
   299  	h := blk.HashBlock()
   300  	if _, err := fd.GetBlockHeight(h); err == nil {
   301  		log.L().Error("Block already exists.", zap.Uint64("height", blk.Height()), log.Hex("hash", h[:]))
   302  		return ErrAlreadyExist
   303  	}
   304  
   305  	// check if we need to split DB
   306  	if fd.cfg.V2BlocksToSplitDB > 0 {
   307  		if err := fd.prepNextDbFile(blk.Height()); err != nil {
   308  			return err
   309  		}
   310  	}
   311  	return fd.currFd.PutBlock(ctx, blk)
   312  }
   313  
   314  func (fd *fileDAO) prepNextDbFile(height uint64) error {
   315  	tip, err := fd.currFd.Height()
   316  	if err != nil {
   317  		return err
   318  	}
   319  	if height != tip+1 {
   320  		return ErrInvalidTipHeight
   321  	}
   322  
   323  	fd.lock.Lock()
   324  	defer fd.lock.Unlock()
   325  
   326  	if height > fd.splitHeight && height-fd.splitHeight >= fd.cfg.V2BlocksToSplitDB {
   327  		return fd.addNewV2File(height)
   328  	}
   329  	return nil
   330  }
   331  
   332  func (fd *fileDAO) addNewV2File(height uint64) error {
   333  	// create a new v2 file
   334  	cfg := fd.cfg
   335  	cfg.DbPath = kthAuxFileName(cfg.DbPath, fd.topIndex+1)
   336  	v2, err := newFileDAOv2(height, cfg, fd.blockDeserializer)
   337  	if err != nil {
   338  		return err
   339  	}
   340  
   341  	defer func() {
   342  		if err == nil {
   343  			fd.currFd = v2
   344  			fd.topIndex++
   345  			fd.splitHeight = height
   346  		}
   347  	}()
   348  
   349  	// add the new v2 file to existing v2 manager
   350  	ctx := context.Background()
   351  	if fd.v2Fd != nil {
   352  		if err = fd.v2Fd.AddFileDAO(v2, height); err != nil {
   353  			return err
   354  		}
   355  		err = v2.Start(ctx)
   356  		return err
   357  	}
   358  
   359  	// create v2 manager
   360  	fd.v2Fd, _ = newFileV2Manager([]*fileDAOv2{v2})
   361  	err = fd.v2Fd.Start(ctx)
   362  	return err
   363  }
   364  
   365  func (fd *fileDAO) DeleteTipBlock() error {
   366  	return fd.currFd.DeleteTipBlock()
   367  }
   368  
   369  // CreateFileDAO creates FileDAO according to master file
   370  func CreateFileDAO(legacy bool, cfg db.Config, deser *block.Deserializer) (FileDAO, error) {
   371  	fd := fileDAO{splitHeight: 1, cfg: cfg, blockDeserializer: deser}
   372  	fds := []*fileDAOv2{}
   373  	v2Top, v2Files := checkAuxFiles(cfg.DbPath, FileV2)
   374  	if legacy {
   375  		legacyFd, err := newFileDAOLegacy(cfg, deser)
   376  		if err != nil {
   377  			return nil, err
   378  		}
   379  		fd.legacyFd = legacyFd
   380  		fd.topIndex, _ = checkAuxFiles(cfg.DbPath, FileLegacyAuxiliary)
   381  
   382  		// legacy master file with no v2 files, early exit
   383  		if len(v2Files) == 0 {
   384  			return &fd, nil
   385  		}
   386  	} else {
   387  		// v2 master file
   388  		fds = append(fds, openFileDAOv2(cfg, deser))
   389  	}
   390  
   391  	// populate v2 files into v2 manager
   392  	if len(v2Files) > 0 {
   393  		for _, name := range v2Files {
   394  			cfg.DbPath = name
   395  			fds = append(fds, openFileDAOv2(cfg, deser))
   396  		}
   397  
   398  		// v2 file's top index overrides v1's top
   399  		fd.topIndex = v2Top
   400  	}
   401  	v2Fd, err := newFileV2Manager(fds)
   402  	if err != nil {
   403  		return nil, err
   404  	}
   405  	fd.v2Fd = v2Fd
   406  	return &fd, nil
   407  }
   408  
   409  // createNewV2File creates a new v2 chain db file
   410  func createNewV2File(start uint64, cfg db.Config, deser *block.Deserializer) error {
   411  	v2, err := newFileDAOv2(start, cfg, deser)
   412  	if err != nil {
   413  		return err
   414  	}
   415  
   416  	// calling Start() will write the header
   417  	ctx := context.Background()
   418  	if err := v2.Start(ctx); err != nil {
   419  		return err
   420  	}
   421  	return v2.Stop(ctx)
   422  }