272 lines
8.3 KiB
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
|
|
}
|