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 }