github.com/kchristidis/fabric@v1.0.4-0.20171028114726-837acd08cde1/common/ledger/blkstorage/fsblkstorage/blockfile_mgr.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fsblkstorage 18 19 import ( 20 "fmt" 21 "math" 22 "sync" 23 "sync/atomic" 24 25 "github.com/golang/protobuf/proto" 26 "github.com/hyperledger/fabric/common/flogging" 27 "github.com/hyperledger/fabric/common/ledger/blkstorage" 28 "github.com/hyperledger/fabric/common/ledger/util" 29 "github.com/hyperledger/fabric/common/ledger/util/leveldbhelper" 30 "github.com/hyperledger/fabric/protos/common" 31 "github.com/hyperledger/fabric/protos/peer" 32 putil "github.com/hyperledger/fabric/protos/utils" 33 ) 34 35 var logger = flogging.MustGetLogger("fsblkstorage") 36 37 const ( 38 blockfilePrefix = "blockfile_" 39 ) 40 41 var ( 42 blkMgrInfoKey = []byte("blkMgrInfo") 43 ) 44 45 type blockfileMgr struct { 46 rootDir string 47 conf *Conf 48 db *leveldbhelper.DBHandle 49 index index 50 cpInfo *checkpointInfo 51 cpInfoCond *sync.Cond 52 currentFileWriter *blockfileWriter 53 bcInfo atomic.Value 54 } 55 56 /* 57 Creates a new manager that will manage the files used for block persistence. 58 This manager manages the file system FS including 59 -- the directory where the files are stored 60 -- the individual files where the blocks are stored 61 -- the checkpoint which tracks the latest file being persisted to 62 -- the index which tracks what block and transaction is in what file 63 When a new blockfile manager is started (i.e. only on start-up), it checks 64 if this start-up is the first time the system is coming up or is this a restart 65 of the system. 66 67 The blockfile manager stores blocks of data into a file system. That file 68 storage is done by creating sequentially numbered files of a configured size 69 i.e blockfile_000000, blockfile_000001, etc.. 70 71 Each transcation in a block is stored with information about the number of 72 bytes in that transaction 73 Adding txLoc [fileSuffixNum=0, offset=3, bytesLength=104] for tx [1:0] to index 74 Adding txLoc [fileSuffixNum=0, offset=107, bytesLength=104] for tx [1:1] to index 75 Each block is stored with the total encoded length of that block as well as the 76 tx location offsets. 77 78 Remember that these steps are only done once at start-up of the system. 79 At start up a new manager: 80 *) Checks if the directory for storing files exists, if not creates the dir 81 *) Checks if the key value database exists, if not creates one 82 (will create a db dir) 83 *) Determines the checkpoint information (cpinfo) used for storage 84 -- Loads from db if exist, if not instantiate a new cpinfo 85 -- If cpinfo was loaded from db, compares to FS 86 -- If cpinfo and file system are not in sync, syncs cpInfo from FS 87 *) Starts a new file writer 88 -- truncates file per cpinfo to remove any excess past last block 89 *) Determines the index information used to find tx and blocks in 90 the file blkstorage 91 -- Instantiates a new blockIdxInfo 92 -- Loads the index from the db if exists 93 -- syncIndex comparing the last block indexed to what is in the FS 94 -- If index and file system are not in sync, syncs index from the FS 95 *) Updates blockchain info used by the APIs 96 */ 97 func newBlockfileMgr(id string, conf *Conf, indexConfig *blkstorage.IndexConfig, indexStore *leveldbhelper.DBHandle) *blockfileMgr { 98 logger.Debugf("newBlockfileMgr() initializing file-based block storage for ledger: %s ", id) 99 //Determine the root directory for the blockfile storage, if it does not exist create it 100 rootDir := conf.getLedgerBlockDir(id) 101 _, err := util.CreateDirIfMissing(rootDir) 102 if err != nil { 103 panic(fmt.Sprintf("Error: %s", err)) 104 } 105 // Instantiate the manager, i.e. blockFileMgr structure 106 mgr := &blockfileMgr{rootDir: rootDir, conf: conf, db: indexStore} 107 108 // cp = checkpointInfo, retrieve from the database the file suffix or number of where blocks were stored. 109 // It also retrieves the current size of that file and the last block number that was written to that file. 110 // At init checkpointInfo:latestFileChunkSuffixNum=[0], latestFileChunksize=[0], lastBlockNumber=[0] 111 cpInfo, err := mgr.loadCurrentInfo() 112 if err != nil { 113 panic(fmt.Sprintf("Could not get block file info for current block file from db: %s", err)) 114 } 115 if cpInfo == nil { //if no cpInfo stored in db initiate to zero 116 cpInfo = &checkpointInfo{0, 0, true, 0} 117 err = mgr.saveCurrentInfo(cpInfo, true) 118 if err != nil { 119 panic(fmt.Sprintf("Could not save next block file info to db: %s", err)) 120 } 121 } 122 //Verify that the checkpoint stored in db is accurate with what is actually stored in block file system 123 // If not the same, sync the cpInfo and the file system 124 syncCPInfoFromFS(rootDir, cpInfo) 125 //Open a writer to the file identified by the number and truncate it to only contain the latest block 126 // that was completely saved (file system, index, cpinfo, etc) 127 currentFileWriter, err := newBlockfileWriter(deriveBlockfilePath(rootDir, cpInfo.latestFileChunkSuffixNum)) 128 if err != nil { 129 panic(fmt.Sprintf("Could not open writer to current file: %s", err)) 130 } 131 //Truncate the file to remove excess past last block 132 err = currentFileWriter.truncateFile(cpInfo.latestFileChunksize) 133 if err != nil { 134 panic(fmt.Sprintf("Could not truncate current file to known size in db: %s", err)) 135 } 136 137 // Create a new KeyValue store database handler for the blocks index in the keyvalue database 138 mgr.index = newBlockIndex(indexConfig, indexStore) 139 140 // Update the manager with the checkpoint info and the file writer 141 mgr.cpInfo = cpInfo 142 mgr.currentFileWriter = currentFileWriter 143 // Create a checkpoint condition (event) variable, for the goroutine waiting for 144 // or announcing the occurrence of an event. 145 mgr.cpInfoCond = sync.NewCond(&sync.Mutex{}) 146 147 // Verify that the index stored in db is accurate with what is actually stored in block file system 148 // If not the same, sync the index and the file system 149 mgr.syncIndex() 150 151 // init BlockchainInfo for external API's 152 bcInfo := &common.BlockchainInfo{ 153 Height: 0, 154 CurrentBlockHash: nil, 155 PreviousBlockHash: nil} 156 157 //If start up is a restart of an existing storage, update BlockchainInfo for external API's 158 if !cpInfo.isChainEmpty { 159 lastBlockHeader, err := mgr.retrieveBlockHeaderByNumber(cpInfo.lastBlockNumber) 160 if err != nil { 161 panic(fmt.Sprintf("Could not retrieve header of the last block form file: %s", err)) 162 } 163 lastBlockHash := lastBlockHeader.Hash() 164 previousBlockHash := lastBlockHeader.PreviousHash 165 bcInfo = &common.BlockchainInfo{ 166 Height: cpInfo.lastBlockNumber + 1, 167 CurrentBlockHash: lastBlockHash, 168 PreviousBlockHash: previousBlockHash} 169 } 170 mgr.bcInfo.Store(bcInfo) 171 //return the new manager (blockfileMgr) 172 return mgr 173 } 174 175 //cp = checkpointInfo, from the database gets the file suffix and the size of 176 // the file of where the last block was written. Also retrieves contains the 177 // last block number that was written. At init 178 //checkpointInfo:latestFileChunkSuffixNum=[0], latestFileChunksize=[0], lastBlockNumber=[0] 179 func syncCPInfoFromFS(rootDir string, cpInfo *checkpointInfo) { 180 logger.Debugf("Starting checkpoint=%s", cpInfo) 181 //Checks if the file suffix of where the last block was written exists 182 filePath := deriveBlockfilePath(rootDir, cpInfo.latestFileChunkSuffixNum) 183 exists, size, err := util.FileExists(filePath) 184 if err != nil { 185 panic(fmt.Sprintf("Error in checking whether file [%s] exists: %s", filePath, err)) 186 } 187 logger.Debugf("status of file [%s]: exists=[%t], size=[%d]", filePath, exists, size) 188 //Test is !exists because when file number is first used the file does not exist yet 189 //checks that the file exists and that the size of the file is what is stored in cpinfo 190 //status of file [/tmp/tests/ledger/blkstorage/fsblkstorage/blocks/blockfile_000000]: exists=[false], size=[0] 191 if !exists || int(size) == cpInfo.latestFileChunksize { 192 // check point info is in sync with the file on disk 193 return 194 } 195 //Scan the file system to verify that the checkpoint info stored in db is correct 196 endOffsetLastBlock, numBlocks, err := scanForLastCompleteBlock( 197 rootDir, cpInfo.latestFileChunkSuffixNum, int64(cpInfo.latestFileChunksize)) 198 if err != nil { 199 panic(fmt.Sprintf("Could not open current file for detecting last block in the file: %s", err)) 200 } 201 cpInfo.latestFileChunksize = int(endOffsetLastBlock) 202 if numBlocks == 0 { 203 return 204 } 205 //Updates the checkpoint info for the actual last block number stored and it's end location 206 if cpInfo.isChainEmpty { 207 cpInfo.lastBlockNumber = uint64(numBlocks - 1) 208 } else { 209 cpInfo.lastBlockNumber += uint64(numBlocks) 210 } 211 cpInfo.isChainEmpty = false 212 logger.Debugf("Checkpoint after updates by scanning the last file segment:%s", cpInfo) 213 } 214 215 func deriveBlockfilePath(rootDir string, suffixNum int) string { 216 return rootDir + "/" + blockfilePrefix + fmt.Sprintf("%06d", suffixNum) 217 } 218 219 func (mgr *blockfileMgr) close() { 220 mgr.currentFileWriter.close() 221 } 222 223 func (mgr *blockfileMgr) moveToNextFile() { 224 cpInfo := &checkpointInfo{ 225 latestFileChunkSuffixNum: mgr.cpInfo.latestFileChunkSuffixNum + 1, 226 latestFileChunksize: 0, 227 lastBlockNumber: mgr.cpInfo.lastBlockNumber} 228 229 nextFileWriter, err := newBlockfileWriter( 230 deriveBlockfilePath(mgr.rootDir, cpInfo.latestFileChunkSuffixNum)) 231 232 if err != nil { 233 panic(fmt.Sprintf("Could not open writer to next file: %s", err)) 234 } 235 mgr.currentFileWriter.close() 236 err = mgr.saveCurrentInfo(cpInfo, true) 237 if err != nil { 238 panic(fmt.Sprintf("Could not save next block file info to db: %s", err)) 239 } 240 mgr.currentFileWriter = nextFileWriter 241 mgr.updateCheckpoint(cpInfo) 242 } 243 244 func (mgr *blockfileMgr) addBlock(block *common.Block) error { 245 if block.Header.Number != mgr.getBlockchainInfo().Height { 246 return fmt.Errorf("Block number should have been %d but was %d", mgr.getBlockchainInfo().Height, block.Header.Number) 247 } 248 blockBytes, info, err := serializeBlock(block) 249 if err != nil { 250 return fmt.Errorf("Error while serializing block: %s", err) 251 } 252 blockHash := block.Header.Hash() 253 //Get the location / offset where each transaction starts in the block and where the block ends 254 txOffsets := info.txOffsets 255 currentOffset := mgr.cpInfo.latestFileChunksize 256 if err != nil { 257 return fmt.Errorf("Error while serializing block: %s", err) 258 } 259 blockBytesLen := len(blockBytes) 260 blockBytesEncodedLen := proto.EncodeVarint(uint64(blockBytesLen)) 261 totalBytesToAppend := blockBytesLen + len(blockBytesEncodedLen) 262 263 //Determine if we need to start a new file since the size of this block 264 //exceeds the amount of space left in the current file 265 if currentOffset+totalBytesToAppend > mgr.conf.maxBlockfileSize { 266 mgr.moveToNextFile() 267 currentOffset = 0 268 } 269 //append blockBytesEncodedLen to the file 270 err = mgr.currentFileWriter.append(blockBytesEncodedLen, false) 271 if err == nil { 272 //append the actual block bytes to the file 273 err = mgr.currentFileWriter.append(blockBytes, true) 274 } 275 if err != nil { 276 truncateErr := mgr.currentFileWriter.truncateFile(mgr.cpInfo.latestFileChunksize) 277 if truncateErr != nil { 278 panic(fmt.Sprintf("Could not truncate current file to known size after an error during block append: %s", err)) 279 } 280 return fmt.Errorf("Error while appending block to file: %s", err) 281 } 282 283 //Update the checkpoint info with the results of adding the new block 284 currentCPInfo := mgr.cpInfo 285 newCPInfo := &checkpointInfo{ 286 latestFileChunkSuffixNum: currentCPInfo.latestFileChunkSuffixNum, 287 latestFileChunksize: currentCPInfo.latestFileChunksize + totalBytesToAppend, 288 isChainEmpty: false, 289 lastBlockNumber: block.Header.Number} 290 //save the checkpoint information in the database 291 if err = mgr.saveCurrentInfo(newCPInfo, false); err != nil { 292 truncateErr := mgr.currentFileWriter.truncateFile(currentCPInfo.latestFileChunksize) 293 if truncateErr != nil { 294 panic(fmt.Sprintf("Error in truncating current file to known size after an error in saving checkpoint info: %s", err)) 295 } 296 return fmt.Errorf("Error while saving current file info to db: %s", err) 297 } 298 299 //Index block file location pointer updated with file suffex and offset for the new block 300 blockFLP := &fileLocPointer{fileSuffixNum: newCPInfo.latestFileChunkSuffixNum} 301 blockFLP.offset = currentOffset 302 // shift the txoffset because we prepend length of bytes before block bytes 303 for _, txOffset := range txOffsets { 304 txOffset.loc.offset += len(blockBytesEncodedLen) 305 } 306 //save the index in the database 307 mgr.index.indexBlock(&blockIdxInfo{ 308 blockNum: block.Header.Number, blockHash: blockHash, 309 flp: blockFLP, txOffsets: txOffsets, metadata: block.Metadata}) 310 311 //update the checkpoint info (for storage) and the blockchain info (for APIs) in the manager 312 mgr.updateCheckpoint(newCPInfo) 313 mgr.updateBlockchainInfo(blockHash, block) 314 return nil 315 } 316 317 func (mgr *blockfileMgr) syncIndex() error { 318 var lastBlockIndexed uint64 319 var indexEmpty bool 320 var err error 321 //from the database, get the last block that was indexed 322 if lastBlockIndexed, err = mgr.index.getLastBlockIndexed(); err != nil { 323 if err != errIndexEmpty { 324 return err 325 } 326 indexEmpty = true 327 } 328 //initialize index to file number:zero, offset:zero and blockNum:0 329 startFileNum := 0 330 startOffset := 0 331 blockNum := uint64(0) 332 skipFirstBlock := false 333 //get the last file that blocks were added to using the checkpoint info 334 endFileNum := mgr.cpInfo.latestFileChunkSuffixNum 335 //if the index stored in the db has value, update the index information with those values 336 if !indexEmpty { 337 var flp *fileLocPointer 338 if flp, err = mgr.index.getBlockLocByBlockNum(lastBlockIndexed); err != nil { 339 return err 340 } 341 startFileNum = flp.fileSuffixNum 342 startOffset = flp.locPointer.offset 343 blockNum = lastBlockIndexed 344 skipFirstBlock = true 345 } 346 347 //open a blockstream to the file location that was stored in the index 348 var stream *blockStream 349 if stream, err = newBlockStream(mgr.rootDir, startFileNum, int64(startOffset), endFileNum); err != nil { 350 return err 351 } 352 var blockBytes []byte 353 var blockPlacementInfo *blockPlacementInfo 354 355 if skipFirstBlock { 356 if blockBytes, _, err = stream.nextBlockBytesAndPlacementInfo(); err != nil { 357 return err 358 } 359 if blockBytes == nil { 360 return fmt.Errorf("block bytes for block num = [%d] should not be nil here. The indexes for the block are already present", 361 lastBlockIndexed) 362 } 363 } 364 365 //Should be at the last block already, but go ahead and loop looking for next blockBytes. 366 //If there is another block, add it to the index. 367 //This will ensure block indexes are correct, for example if peer had crashed before indexes got updated. 368 for { 369 if blockBytes, blockPlacementInfo, err = stream.nextBlockBytesAndPlacementInfo(); err != nil { 370 return err 371 } 372 if blockBytes == nil { 373 break 374 } 375 info, err := extractSerializedBlockInfo(blockBytes) 376 if err != nil { 377 return err 378 } 379 380 //The blockStartOffset will get applied to the txOffsets prior to indexing within indexBlock(), 381 //therefore just shift by the difference between blockBytesOffset and blockStartOffset 382 numBytesToShift := int(blockPlacementInfo.blockBytesOffset - blockPlacementInfo.blockStartOffset) 383 for _, offset := range info.txOffsets { 384 offset.loc.offset += numBytesToShift 385 } 386 387 //Update the blockIndexInfo with what was actually stored in file system 388 blockIdxInfo := &blockIdxInfo{} 389 blockIdxInfo.blockHash = info.blockHeader.Hash() 390 blockIdxInfo.blockNum = info.blockHeader.Number 391 blockIdxInfo.flp = &fileLocPointer{fileSuffixNum: blockPlacementInfo.fileNum, 392 locPointer: locPointer{offset: int(blockPlacementInfo.blockStartOffset)}} 393 blockIdxInfo.txOffsets = info.txOffsets 394 blockIdxInfo.metadata = info.metadata 395 396 logger.Debugf("syncIndex() indexing block [%d]", blockIdxInfo.blockNum) 397 if err = mgr.index.indexBlock(blockIdxInfo); err != nil { 398 return err 399 } 400 blockNum++ 401 } 402 return nil 403 } 404 405 func (mgr *blockfileMgr) getBlockchainInfo() *common.BlockchainInfo { 406 return mgr.bcInfo.Load().(*common.BlockchainInfo) 407 } 408 409 func (mgr *blockfileMgr) updateCheckpoint(cpInfo *checkpointInfo) { 410 mgr.cpInfoCond.L.Lock() 411 defer mgr.cpInfoCond.L.Unlock() 412 mgr.cpInfo = cpInfo 413 logger.Debugf("Broadcasting about update checkpointInfo: %s", cpInfo) 414 mgr.cpInfoCond.Broadcast() 415 } 416 417 func (mgr *blockfileMgr) updateBlockchainInfo(latestBlockHash []byte, latestBlock *common.Block) { 418 currentBCInfo := mgr.getBlockchainInfo() 419 newBCInfo := &common.BlockchainInfo{ 420 Height: currentBCInfo.Height + 1, 421 CurrentBlockHash: latestBlockHash, 422 PreviousBlockHash: latestBlock.Header.PreviousHash} 423 424 mgr.bcInfo.Store(newBCInfo) 425 } 426 427 func (mgr *blockfileMgr) retrieveBlockByHash(blockHash []byte) (*common.Block, error) { 428 logger.Debugf("retrieveBlockByHash() - blockHash = [%#v]", blockHash) 429 loc, err := mgr.index.getBlockLocByHash(blockHash) 430 if err != nil { 431 return nil, err 432 } 433 return mgr.fetchBlock(loc) 434 } 435 436 func (mgr *blockfileMgr) retrieveBlockByNumber(blockNum uint64) (*common.Block, error) { 437 logger.Debugf("retrieveBlockByNumber() - blockNum = [%d]", blockNum) 438 439 // interpret math.MaxUint64 as a request for last block 440 if blockNum == math.MaxUint64 { 441 blockNum = mgr.getBlockchainInfo().Height - 1 442 } 443 444 loc, err := mgr.index.getBlockLocByBlockNum(blockNum) 445 if err != nil { 446 return nil, err 447 } 448 return mgr.fetchBlock(loc) 449 } 450 451 func (mgr *blockfileMgr) retrieveBlockByTxID(txID string) (*common.Block, error) { 452 logger.Debugf("retrieveBlockByTxID() - txID = [%s]", txID) 453 454 loc, err := mgr.index.getBlockLocByTxID(txID) 455 456 if err != nil { 457 return nil, err 458 } 459 return mgr.fetchBlock(loc) 460 } 461 462 func (mgr *blockfileMgr) retrieveTxValidationCodeByTxID(txID string) (peer.TxValidationCode, error) { 463 logger.Debugf("retrieveTxValidationCodeByTxID() - txID = [%s]", txID) 464 return mgr.index.getTxValidationCodeByTxID(txID) 465 } 466 467 func (mgr *blockfileMgr) retrieveBlockHeaderByNumber(blockNum uint64) (*common.BlockHeader, error) { 468 logger.Debugf("retrieveBlockHeaderByNumber() - blockNum = [%d]", blockNum) 469 loc, err := mgr.index.getBlockLocByBlockNum(blockNum) 470 if err != nil { 471 return nil, err 472 } 473 blockBytes, err := mgr.fetchBlockBytes(loc) 474 if err != nil { 475 return nil, err 476 } 477 info, err := extractSerializedBlockInfo(blockBytes) 478 if err != nil { 479 return nil, err 480 } 481 return info.blockHeader, nil 482 } 483 484 func (mgr *blockfileMgr) retrieveBlocks(startNum uint64) (*blocksItr, error) { 485 return newBlockItr(mgr, startNum), nil 486 } 487 488 func (mgr *blockfileMgr) retrieveTransactionByID(txID string) (*common.Envelope, error) { 489 logger.Debugf("retrieveTransactionByID() - txId = [%s]", txID) 490 loc, err := mgr.index.getTxLoc(txID) 491 if err != nil { 492 return nil, err 493 } 494 return mgr.fetchTransactionEnvelope(loc) 495 } 496 497 func (mgr *blockfileMgr) retrieveTransactionByBlockNumTranNum(blockNum uint64, tranNum uint64) (*common.Envelope, error) { 498 logger.Debugf("retrieveTransactionByBlockNumTranNum() - blockNum = [%d], tranNum = [%d]", blockNum, tranNum) 499 loc, err := mgr.index.getTXLocByBlockNumTranNum(blockNum, tranNum) 500 if err != nil { 501 return nil, err 502 } 503 return mgr.fetchTransactionEnvelope(loc) 504 } 505 506 func (mgr *blockfileMgr) fetchBlock(lp *fileLocPointer) (*common.Block, error) { 507 blockBytes, err := mgr.fetchBlockBytes(lp) 508 if err != nil { 509 return nil, err 510 } 511 block, err := deserializeBlock(blockBytes) 512 if err != nil { 513 return nil, err 514 } 515 return block, nil 516 } 517 518 func (mgr *blockfileMgr) fetchTransactionEnvelope(lp *fileLocPointer) (*common.Envelope, error) { 519 logger.Debugf("Entering fetchTransactionEnvelope() %v\n", lp) 520 var err error 521 var txEnvelopeBytes []byte 522 if txEnvelopeBytes, err = mgr.fetchRawBytes(lp); err != nil { 523 return nil, err 524 } 525 _, n := proto.DecodeVarint(txEnvelopeBytes) 526 return putil.GetEnvelopeFromBlock(txEnvelopeBytes[n:]) 527 } 528 529 func (mgr *blockfileMgr) fetchBlockBytes(lp *fileLocPointer) ([]byte, error) { 530 stream, err := newBlockfileStream(mgr.rootDir, lp.fileSuffixNum, int64(lp.offset)) 531 if err != nil { 532 return nil, err 533 } 534 defer stream.close() 535 b, err := stream.nextBlockBytes() 536 if err != nil { 537 return nil, err 538 } 539 return b, nil 540 } 541 542 func (mgr *blockfileMgr) fetchRawBytes(lp *fileLocPointer) ([]byte, error) { 543 filePath := deriveBlockfilePath(mgr.rootDir, lp.fileSuffixNum) 544 reader, err := newBlockfileReader(filePath) 545 if err != nil { 546 return nil, err 547 } 548 defer reader.close() 549 b, err := reader.read(lp.offset, lp.bytesLength) 550 if err != nil { 551 return nil, err 552 } 553 return b, nil 554 } 555 556 //Get the current checkpoint information that is stored in the database 557 func (mgr *blockfileMgr) loadCurrentInfo() (*checkpointInfo, error) { 558 var b []byte 559 var err error 560 if b, err = mgr.db.Get(blkMgrInfoKey); b == nil || err != nil { 561 return nil, err 562 } 563 i := &checkpointInfo{} 564 if err = i.unmarshal(b); err != nil { 565 return nil, err 566 } 567 logger.Debugf("loaded checkpointInfo:%s", i) 568 return i, nil 569 } 570 571 func (mgr *blockfileMgr) saveCurrentInfo(i *checkpointInfo, sync bool) error { 572 b, err := i.marshal() 573 if err != nil { 574 return err 575 } 576 if err = mgr.db.Put(blkMgrInfoKey, b, sync); err != nil { 577 return err 578 } 579 return nil 580 } 581 582 // scanForLastCompleteBlock scan a given block file and detects the last offset in the file 583 // after which there may lie a block partially written (towards the end of the file in a crash scenario). 584 func scanForLastCompleteBlock(rootDir string, fileNum int, startingOffset int64) (int64, int, error) { 585 //scan the passed file number suffix starting from the passed offset to find the last completed block 586 numBlocks := 0 587 blockStream, errOpen := newBlockfileStream(rootDir, fileNum, startingOffset) 588 if errOpen != nil { 589 return 0, 0, errOpen 590 } 591 defer blockStream.close() 592 var errRead error 593 var blockBytes []byte 594 for { 595 blockBytes, errRead = blockStream.nextBlockBytes() 596 if blockBytes == nil || errRead != nil { 597 break 598 } 599 numBlocks++ 600 } 601 if errRead == ErrUnexpectedEndOfBlockfile { 602 logger.Debugf(`Error:%s 603 The error may happen if a crash has happened during block appending. 604 Resetting error to nil and returning current offset as a last complete block's end offset`, errRead) 605 errRead = nil 606 } 607 logger.Debugf("scanForLastCompleteBlock(): last complete block ends at offset=[%d]", blockStream.currentOffset) 608 return blockStream.currentOffset, numBlocks, errRead 609 } 610 611 // checkpointInfo 612 type checkpointInfo struct { 613 latestFileChunkSuffixNum int 614 latestFileChunksize int 615 isChainEmpty bool 616 lastBlockNumber uint64 617 } 618 619 func (i *checkpointInfo) marshal() ([]byte, error) { 620 buffer := proto.NewBuffer([]byte{}) 621 var err error 622 if err = buffer.EncodeVarint(uint64(i.latestFileChunkSuffixNum)); err != nil { 623 return nil, err 624 } 625 if err = buffer.EncodeVarint(uint64(i.latestFileChunksize)); err != nil { 626 return nil, err 627 } 628 if err = buffer.EncodeVarint(i.lastBlockNumber); err != nil { 629 return nil, err 630 } 631 var chainEmptyMarker uint64 632 if i.isChainEmpty { 633 chainEmptyMarker = 1 634 } 635 if err = buffer.EncodeVarint(chainEmptyMarker); err != nil { 636 return nil, err 637 } 638 return buffer.Bytes(), nil 639 } 640 641 func (i *checkpointInfo) unmarshal(b []byte) error { 642 buffer := proto.NewBuffer(b) 643 var val uint64 644 var chainEmptyMarker uint64 645 var err error 646 647 if val, err = buffer.DecodeVarint(); err != nil { 648 return err 649 } 650 i.latestFileChunkSuffixNum = int(val) 651 652 if val, err = buffer.DecodeVarint(); err != nil { 653 return err 654 } 655 i.latestFileChunksize = int(val) 656 657 if val, err = buffer.DecodeVarint(); err != nil { 658 return err 659 } 660 i.lastBlockNumber = val 661 if chainEmptyMarker, err = buffer.DecodeVarint(); err != nil { 662 return err 663 } 664 i.isChainEmpty = chainEmptyMarker == 1 665 return nil 666 } 667 668 func (i *checkpointInfo) String() string { 669 return fmt.Sprintf("latestFileChunkSuffixNum=[%d], latestFileChunksize=[%d], isChainEmpty=[%t], lastBlockNumber=[%d]", 670 i.latestFileChunkSuffixNum, i.latestFileChunksize, i.isChainEmpty, i.lastBlockNumber) 671 }