github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/common/ledger/blkstorage/rollback.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package blkstorage
     8  
     9  import (
    10  	"os"
    11  
    12  	"github.com/hechain20/hechain/common/ledger/util/leveldbhelper"
    13  	"github.com/hechain20/hechain/internal/fileutil"
    14  	"github.com/hechain20/hechain/protoutil"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  type rollbackMgr struct {
    19  	ledgerID       string
    20  	ledgerDir      string
    21  	indexDir       string
    22  	dbProvider     *leveldbhelper.Provider
    23  	indexStore     *blockIndex
    24  	targetBlockNum uint64
    25  	reusableBatch  *leveldbhelper.UpdateBatch
    26  }
    27  
    28  // Rollback reverts changes made to the block store beyond a given block number.
    29  func Rollback(blockStorageDir, ledgerID string, targetBlockNum uint64, indexConfig *IndexConfig) error {
    30  	r, err := newRollbackMgr(blockStorageDir, ledgerID, indexConfig, targetBlockNum)
    31  	if err != nil {
    32  		return err
    33  	}
    34  	defer r.dbProvider.Close()
    35  
    36  	if err := recordHeightIfGreaterThanPreviousRecording(r.ledgerDir); err != nil {
    37  		return err
    38  	}
    39  
    40  	logger.Infof("Rolling back block index to block number [%d]", targetBlockNum)
    41  	if err := r.rollbackBlockIndex(); err != nil {
    42  		return err
    43  	}
    44  
    45  	logger.Infof("Rolling back block files to block number [%d]", targetBlockNum)
    46  	if err := r.rollbackBlockFiles(); err != nil {
    47  		return err
    48  	}
    49  
    50  	return nil
    51  }
    52  
    53  func newRollbackMgr(blockStorageDir, ledgerID string, indexConfig *IndexConfig, targetBlockNum uint64) (*rollbackMgr, error) {
    54  	r := &rollbackMgr{}
    55  
    56  	r.ledgerID = ledgerID
    57  	conf := &Conf{blockStorageDir: blockStorageDir}
    58  	r.ledgerDir = conf.getLedgerBlockDir(ledgerID)
    59  	r.targetBlockNum = targetBlockNum
    60  
    61  	r.indexDir = conf.getIndexDir()
    62  	var err error
    63  	r.dbProvider, err = leveldbhelper.NewProvider(
    64  		&leveldbhelper.Conf{
    65  			DBPath:         r.indexDir,
    66  			ExpectedFormat: dataFormatVersion(indexConfig),
    67  		},
    68  	)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	indexDB := r.dbProvider.GetDBHandle(ledgerID)
    73  	r.indexStore, err = newBlockIndex(indexConfig, indexDB)
    74  	r.reusableBatch = r.indexStore.db.NewUpdateBatch()
    75  	return r, err
    76  }
    77  
    78  func (r *rollbackMgr) rollbackBlockIndex() error {
    79  	lastBlockNumber, err := r.indexStore.getLastBlockIndexed()
    80  	if err == errIndexSavePointKeyNotPresent {
    81  		return nil
    82  	}
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	// we remove index associated with only 10 blocks at a time
    88  	// to avoid overuse of memory occupied by the leveldb batch.
    89  	// If we assume a block size of 2000 transactions and 4 indices
    90  	// per transaction and 2 index per block, the total number of
    91  	// index keys to be added in the batch would be 80020. Even if a
    92  	// key takes 100 bytes (overestimation), the memory utilization
    93  	// of a batch of 10 blocks would be 7 MB only.
    94  	batchLimit := uint64(10)
    95  
    96  	// start each iteration of the loop with full range for deletion
    97  	// and shrink the range to batchLimit if the range is greater than batchLimit
    98  	start, end := r.targetBlockNum+1, lastBlockNumber
    99  	for end >= start {
   100  		if end-start >= batchLimit {
   101  			start = end - batchLimit + 1 // 1 is added as range is inclusive
   102  		}
   103  		logger.Infof("Deleting index associated with block number [%d] to [%d]", start, end)
   104  		if err := r.deleteIndexEntriesRange(start, end); err != nil {
   105  			return err
   106  		}
   107  		start, end = r.targetBlockNum+1, start-1
   108  	}
   109  	return nil
   110  }
   111  
   112  func (r *rollbackMgr) deleteIndexEntriesRange(startBlkNum, endBlkNum uint64) error {
   113  	// TODO: when more than half of the blocks' indices are to be deleted, it
   114  	// might be efficient to drop the whole index database rather than deleting
   115  	// entries. However, if there is more than more than 1 channel, dropping of
   116  	// index would impact the time taken to recover the peer. We need to analyze
   117  	// a bit before making a decision on rollback vs drop of index. FAB-15672
   118  	r.reusableBatch.Reset()
   119  	lp, err := r.indexStore.getBlockLocByBlockNum(startBlkNum)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	stream, err := newBlockStream(r.ledgerDir, lp.fileSuffixNum, int64(lp.offset), -1)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	defer stream.close()
   129  
   130  	numberOfBlocksToRetrieve := endBlkNum - startBlkNum + 1
   131  	for numberOfBlocksToRetrieve > 0 {
   132  		blockBytes, _, err := stream.nextBlockBytesAndPlacementInfo()
   133  		if err != nil {
   134  			return err
   135  		}
   136  		blockInfo, err := extractSerializedBlockInfo(blockBytes)
   137  		if err != nil {
   138  			return err
   139  		}
   140  		addIndexEntriesToBeDeleted(r.reusableBatch, blockInfo, r.indexStore)
   141  		numberOfBlocksToRetrieve--
   142  	}
   143  
   144  	r.reusableBatch.Put(indexSavePointKey, encodeBlockNum(startBlkNum-1))
   145  	return r.indexStore.db.WriteBatch(r.reusableBatch, true)
   146  }
   147  
   148  func addIndexEntriesToBeDeleted(batch *leveldbhelper.UpdateBatch, blockInfo *serializedBlockInfo, indexStore *blockIndex) error {
   149  	if indexStore.isAttributeIndexed(IndexableAttrBlockHash) {
   150  		batch.Delete(constructBlockHashKey(protoutil.BlockHeaderHash(blockInfo.blockHeader)))
   151  	}
   152  
   153  	if indexStore.isAttributeIndexed(IndexableAttrBlockNum) {
   154  		batch.Delete(constructBlockNumKey(blockInfo.blockHeader.Number))
   155  	}
   156  
   157  	if indexStore.isAttributeIndexed(IndexableAttrBlockNumTranNum) {
   158  		for txIndex := range blockInfo.txOffsets {
   159  			batch.Delete(constructBlockNumTranNumKey(blockInfo.blockHeader.Number, uint64(txIndex)))
   160  		}
   161  	}
   162  
   163  	if indexStore.isAttributeIndexed(IndexableAttrTxID) {
   164  		for i, txOffset := range blockInfo.txOffsets {
   165  			batch.Delete(constructTxIDKey(txOffset.txID, blockInfo.blockHeader.Number, uint64(i)))
   166  		}
   167  	}
   168  	return nil
   169  }
   170  
   171  func (r *rollbackMgr) rollbackBlockFiles() error {
   172  	logger.Infof("Deleting blockfilesInfo")
   173  	if err := r.indexStore.db.Delete(blkMgrInfoKey, true); err != nil {
   174  		return err
   175  	}
   176  	// must not use index for block location search since the index can be behind the target block
   177  	targetFileNum, err := binarySearchFileNumForBlock(r.ledgerDir, r.targetBlockNum)
   178  	if err != nil {
   179  		return err
   180  	}
   181  	lastFileNum, err := retrieveLastFileSuffix(r.ledgerDir)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	logger.Infof("Removing all block files with suffixNum in the range [%d] to [%d]",
   187  		targetFileNum+1, lastFileNum)
   188  
   189  	for n := lastFileNum; n >= targetFileNum+1; n-- {
   190  		filepath := deriveBlockfilePath(r.ledgerDir, n)
   191  		if err := os.Remove(filepath); err != nil {
   192  			return errors.Wrapf(err, "error removing the block file [%s]", filepath)
   193  		}
   194  	}
   195  
   196  	logger.Infof("Truncating block file [%d] to the end boundary of block number [%d]", targetFileNum, r.targetBlockNum)
   197  	endOffset, err := calculateEndOffSet(r.ledgerDir, targetFileNum, r.targetBlockNum)
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	filePath := deriveBlockfilePath(r.ledgerDir, targetFileNum)
   203  	if err := os.Truncate(filePath, endOffset); err != nil {
   204  		return errors.Wrapf(err, "error trucating the block file [%s]", filePath)
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  func calculateEndOffSet(ledgerDir string, targetBlkFileNum int, blockNum uint64) (int64, error) {
   211  	stream, err := newBlockfileStream(ledgerDir, targetBlkFileNum, 0)
   212  	if err != nil {
   213  		return 0, err
   214  	}
   215  	defer stream.close()
   216  	for {
   217  		blockBytes, err := stream.nextBlockBytes()
   218  		if err != nil {
   219  			return 0, err
   220  		}
   221  		blockInfo, err := extractSerializedBlockInfo(blockBytes)
   222  		if err != nil {
   223  			return 0, err
   224  		}
   225  		if blockInfo.blockHeader.Number == blockNum {
   226  			break
   227  		}
   228  	}
   229  	return stream.currentOffset, nil
   230  }
   231  
   232  // ValidateRollbackParams performs necessary validation on the input given for
   233  // the rollback operation.
   234  func ValidateRollbackParams(blockStorageDir, ledgerID string, targetBlockNum uint64) error {
   235  	logger.Infof("Validating the rollback parameters: ledgerID [%s], block number [%d]",
   236  		ledgerID, targetBlockNum)
   237  	conf := &Conf{blockStorageDir: blockStorageDir}
   238  	ledgerDir := conf.getLedgerBlockDir(ledgerID)
   239  	if err := validateLedgerID(ledgerDir, ledgerID); err != nil {
   240  		return err
   241  	}
   242  	if err := validateTargetBlkNum(ledgerDir, targetBlockNum); err != nil {
   243  		return err
   244  	}
   245  	return nil
   246  }
   247  
   248  func validateLedgerID(ledgerDir, ledgerID string) error {
   249  	logger.Debugf("Validating the existence of ledgerID [%s]", ledgerID)
   250  	exists, err := fileutil.DirExists(ledgerDir)
   251  	if err != nil {
   252  		return err
   253  	}
   254  	if !exists {
   255  		return errors.Errorf("ledgerID [%s] does not exist", ledgerID)
   256  	}
   257  	return nil
   258  }
   259  
   260  func validateTargetBlkNum(ledgerDir string, targetBlockNum uint64) error {
   261  	logger.Debugf("Validating the given block number [%d] against the ledger block height", targetBlockNum)
   262  	blkfilesInfo, err := constructBlockfilesInfo(ledgerDir)
   263  	if err != nil {
   264  		return err
   265  	}
   266  	if blkfilesInfo.lastPersistedBlock <= targetBlockNum {
   267  		return errors.Errorf("target block number [%d] should be less than the biggest block number [%d]",
   268  			targetBlockNum, blkfilesInfo.lastPersistedBlock)
   269  	}
   270  	return nil
   271  }