github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/common/ledger/blkstorage/rollback.go (about)

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