github.com/sykesm/fabric@v1.1.0-preview.0.20200129034918-2aa12b1a0181/common/ledger/blkstorage/fsblkstorage/rollback.go (about)

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