go_study/fabric-main/common/ledger/blkstorage/rollback.go

272 lines
8.3 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package blkstorage
import (
"os"
"github.com/hyperledger/fabric/common/ledger/util/leveldbhelper"
"github.com/hyperledger/fabric/internal/fileutil"
"github.com/hyperledger/fabric/protoutil"
"github.com/pkg/errors"
)
type rollbackMgr struct {
ledgerID string
ledgerDir string
indexDir string
dbProvider *leveldbhelper.Provider
indexStore *blockIndex
targetBlockNum uint64
reusableBatch *leveldbhelper.UpdateBatch
}
// Rollback reverts changes made to the block store beyond a given block number.
func Rollback(blockStorageDir, ledgerID string, targetBlockNum uint64, indexConfig *IndexConfig) error {
r, err := newRollbackMgr(blockStorageDir, ledgerID, indexConfig, targetBlockNum)
if err != nil {
return err
}
defer r.dbProvider.Close()
if err := recordHeightIfGreaterThanPreviousRecording(r.ledgerDir); err != nil {
return err
}
logger.Infof("Rolling back block index to block number [%d]", targetBlockNum)
if err := r.rollbackBlockIndex(); err != nil {
return err
}
logger.Infof("Rolling back block files to block number [%d]", targetBlockNum)
if err := r.rollbackBlockFiles(); err != nil {
return err
}
return nil
}
func newRollbackMgr(blockStorageDir, ledgerID string, indexConfig *IndexConfig, targetBlockNum uint64) (*rollbackMgr, error) {
r := &rollbackMgr{}
r.ledgerID = ledgerID
conf := &Conf{blockStorageDir: blockStorageDir}
r.ledgerDir = conf.getLedgerBlockDir(ledgerID)
r.targetBlockNum = targetBlockNum
r.indexDir = conf.getIndexDir()
var err error
r.dbProvider, err = leveldbhelper.NewProvider(
&leveldbhelper.Conf{
DBPath: r.indexDir,
ExpectedFormat: dataFormatVersion(indexConfig),
},
)
if err != nil {
return nil, err
}
indexDB := r.dbProvider.GetDBHandle(ledgerID)
r.indexStore, err = newBlockIndex(indexConfig, indexDB)
r.reusableBatch = r.indexStore.db.NewUpdateBatch()
return r, err
}
func (r *rollbackMgr) rollbackBlockIndex() error {
lastBlockNumber, err := r.indexStore.getLastBlockIndexed()
if err == errIndexSavePointKeyNotPresent {
return nil
}
if err != nil {
return err
}
// we remove index associated with only 10 blocks at a time
// to avoid overuse of memory occupied by the leveldb batch.
// If we assume a block size of 2000 transactions and 4 indices
// per transaction and 2 index per block, the total number of
// index keys to be added in the batch would be 80020. Even if a
// key takes 100 bytes (overestimation), the memory utilization
// of a batch of 10 blocks would be 7 MB only.
batchLimit := uint64(10)
// start each iteration of the loop with full range for deletion
// and shrink the range to batchLimit if the range is greater than batchLimit
start, end := r.targetBlockNum+1, lastBlockNumber
for end >= start {
if end-start >= batchLimit {
start = end - batchLimit + 1 // 1 is added as range is inclusive
}
logger.Infof("Deleting index associated with block number [%d] to [%d]", start, end)
if err := r.deleteIndexEntriesRange(start, end); err != nil {
return err
}
start, end = r.targetBlockNum+1, start-1
}
return nil
}
func (r *rollbackMgr) deleteIndexEntriesRange(startBlkNum, endBlkNum uint64) error {
// TODO: when more than half of the blocks' indices are to be deleted, it
// might be efficient to drop the whole index database rather than deleting
// entries. However, if there is more than more than 1 channel, dropping of
// index would impact the time taken to recover the peer. We need to analyze
// a bit before making a decision on rollback vs drop of index. FAB-15672
r.reusableBatch.Reset()
lp, err := r.indexStore.getBlockLocByBlockNum(startBlkNum)
if err != nil {
return err
}
stream, err := newBlockStream(r.ledgerDir, lp.fileSuffixNum, int64(lp.offset), -1)
if err != nil {
return err
}
defer stream.close()
numberOfBlocksToRetrieve := endBlkNum - startBlkNum + 1
for numberOfBlocksToRetrieve > 0 {
blockBytes, _, err := stream.nextBlockBytesAndPlacementInfo()
if err != nil {
return err
}
blockInfo, err := extractSerializedBlockInfo(blockBytes)
if err != nil {
return err
}
addIndexEntriesToBeDeleted(r.reusableBatch, blockInfo, r.indexStore)
numberOfBlocksToRetrieve--
}
r.reusableBatch.Put(indexSavePointKey, encodeBlockNum(startBlkNum-1))
return r.indexStore.db.WriteBatch(r.reusableBatch, true)
}
func addIndexEntriesToBeDeleted(batch *leveldbhelper.UpdateBatch, blockInfo *serializedBlockInfo, indexStore *blockIndex) error {
if indexStore.isAttributeIndexed(IndexableAttrBlockHash) {
batch.Delete(constructBlockHashKey(protoutil.BlockHeaderHash(blockInfo.blockHeader)))
}
if indexStore.isAttributeIndexed(IndexableAttrBlockNum) {
batch.Delete(constructBlockNumKey(blockInfo.blockHeader.Number))
}
if indexStore.isAttributeIndexed(IndexableAttrBlockNumTranNum) {
for txIndex := range blockInfo.txOffsets {
batch.Delete(constructBlockNumTranNumKey(blockInfo.blockHeader.Number, uint64(txIndex)))
}
}
if indexStore.isAttributeIndexed(IndexableAttrTxID) {
for i, txOffset := range blockInfo.txOffsets {
batch.Delete(constructTxIDKey(txOffset.txID, blockInfo.blockHeader.Number, uint64(i)))
}
}
return nil
}
func (r *rollbackMgr) rollbackBlockFiles() error {
logger.Infof("Deleting blockfilesInfo")
if err := r.indexStore.db.Delete(blkMgrInfoKey, true); err != nil {
return err
}
// must not use index for block location search since the index can be behind the target block
targetFileNum, err := binarySearchFileNumForBlock(r.ledgerDir, r.targetBlockNum)
if err != nil {
return err
}
lastFileNum, err := retrieveLastFileSuffix(r.ledgerDir)
if err != nil {
return err
}
logger.Infof("Removing all block files with suffixNum in the range [%d] to [%d]",
targetFileNum+1, lastFileNum)
for n := lastFileNum; n >= targetFileNum+1; n-- {
filepath := deriveBlockfilePath(r.ledgerDir, n)
if err := os.Remove(filepath); err != nil {
return errors.Wrapf(err, "error removing the block file [%s]", filepath)
}
}
logger.Infof("Truncating block file [%d] to the end boundary of block number [%d]", targetFileNum, r.targetBlockNum)
endOffset, err := calculateEndOffSet(r.ledgerDir, targetFileNum, r.targetBlockNum)
if err != nil {
return err
}
filePath := deriveBlockfilePath(r.ledgerDir, targetFileNum)
if err := os.Truncate(filePath, endOffset); err != nil {
return errors.Wrapf(err, "error trucating the block file [%s]", filePath)
}
return nil
}
func calculateEndOffSet(ledgerDir string, targetBlkFileNum int, blockNum uint64) (int64, error) {
stream, err := newBlockfileStream(ledgerDir, targetBlkFileNum, 0)
if err != nil {
return 0, err
}
defer stream.close()
for {
blockBytes, err := stream.nextBlockBytes()
if err != nil {
return 0, err
}
blockInfo, err := extractSerializedBlockInfo(blockBytes)
if err != nil {
return 0, err
}
if blockInfo.blockHeader.Number == blockNum {
break
}
}
return stream.currentOffset, nil
}
// ValidateRollbackParams performs necessary validation on the input given for
// the rollback operation.
func ValidateRollbackParams(blockStorageDir, ledgerID string, targetBlockNum uint64) error {
logger.Infof("Validating the rollback parameters: ledgerID [%s], block number [%d]",
ledgerID, targetBlockNum)
conf := &Conf{blockStorageDir: blockStorageDir}
ledgerDir := conf.getLedgerBlockDir(ledgerID)
if err := validateLedgerID(ledgerDir, ledgerID); err != nil {
return err
}
if err := validateTargetBlkNum(ledgerDir, targetBlockNum); err != nil {
return err
}
return nil
}
func validateLedgerID(ledgerDir, ledgerID string) error {
logger.Debugf("Validating the existence of ledgerID [%s]", ledgerID)
exists, err := fileutil.DirExists(ledgerDir)
if err != nil {
return err
}
if !exists {
return errors.Errorf("ledgerID [%s] does not exist", ledgerID)
}
return nil
}
func validateTargetBlkNum(ledgerDir string, targetBlockNum uint64) error {
logger.Debugf("Validating the given block number [%d] against the ledger block height", targetBlockNum)
blkfilesInfo, err := constructBlockfilesInfo(ledgerDir)
if err != nil {
return err
}
if blkfilesInfo.lastPersistedBlock <= targetBlockNum {
return errors.Errorf("target block number [%d] should be less than the biggest block number [%d]",
targetBlockNum, blkfilesInfo.lastPersistedBlock)
}
return nil
}