github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/filedao/filedao_legacy.go (about) 1 // Copyright (c) 2020 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package filedao 7 8 import ( 9 "context" 10 "os" 11 "sync" 12 "sync/atomic" 13 14 "github.com/iotexproject/go-pkgs/cache" 15 "github.com/iotexproject/go-pkgs/hash" 16 "github.com/iotexproject/iotex-proto/golang/iotextypes" 17 "github.com/pkg/errors" 18 "go.uber.org/zap" 19 "google.golang.org/protobuf/proto" 20 21 "github.com/iotexproject/iotex-core/action" 22 "github.com/iotexproject/iotex-core/blockchain/block" 23 "github.com/iotexproject/iotex-core/db" 24 "github.com/iotexproject/iotex-core/db/batch" 25 "github.com/iotexproject/iotex-core/pkg/compress" 26 "github.com/iotexproject/iotex-core/pkg/enc" 27 "github.com/iotexproject/iotex-core/pkg/lifecycle" 28 "github.com/iotexproject/iotex-core/pkg/log" 29 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 30 ) 31 32 const ( 33 _blockNS = "blk" 34 _blockHeaderNS = "bhr" 35 _blockBodyNS = "bbd" 36 _blockFooterNS = "bfr" 37 _receiptsNS = "rpt" 38 ) 39 40 var ( 41 _heightPrefix = []byte("he.") 42 _heightToFileBucket = []byte("h2f") 43 // patternLen = len("00000000.db") 44 // suffixLen = len(".db") 45 ) 46 47 type ( 48 // fileDAOLegacy handles chain db file before file split activation at v1.1.2 49 fileDAOLegacy struct { 50 compressBlock bool 51 lifecycle lifecycle.Lifecycle 52 cfg db.Config 53 mutex sync.RWMutex // for create new db file 54 topIndex atomic.Value 55 htf db.RangeIndex 56 kvStore db.KVStore 57 kvStores cache.LRUCache //store like map[index]db.KVStore,index from 1...N 58 deser *block.Deserializer 59 } 60 ) 61 62 // newFileDAOLegacy creates a new legacy file 63 func newFileDAOLegacy(cfg db.Config, deser *block.Deserializer) (FileDAO, error) { 64 return &fileDAOLegacy{ 65 compressBlock: cfg.CompressLegacy, 66 cfg: cfg, 67 kvStore: db.NewBoltDB(cfg), 68 kvStores: cache.NewThreadSafeLruCache(0), 69 deser: deser, 70 }, nil 71 } 72 73 func (fd *fileDAOLegacy) Start(ctx context.Context) error { 74 if err := fd.kvStore.Start(ctx); err != nil { 75 return err 76 } 77 78 // set init height value and transaction log flag 79 if _, err := fd.kvStore.Get(_blockNS, _topHeightKey); err != nil && 80 errors.Cause(err) == db.ErrNotExist { 81 zero8bytes := make([]byte, 8) 82 if err := fd.kvStore.Put(_blockNS, _topHeightKey, zero8bytes); err != nil { 83 return errors.Wrap(err, "failed to write initial value for top height") 84 } 85 if err := fd.kvStore.Put(_systemLogNS, zero8bytes, []byte(_systemLogNS)); err != nil { 86 return errors.Wrap(err, "failed to write initial value for transaction log") 87 } 88 } 89 90 // loop thru all legacy files 91 base := fd.cfg.DbPath 92 _, files := checkAuxFiles(base, FileLegacyAuxiliary) 93 var maxN uint64 94 for _, file := range files { 95 index, ok := isAuxFile(file, base) 96 if !ok { 97 continue 98 } 99 if _, _, err := fd.openDB(index); err != nil { 100 return err 101 } 102 if uint64(index) > maxN { 103 maxN = uint64(index) 104 } 105 } 106 if maxN == 0 { 107 maxN = 1 108 } 109 fd.topIndex.Store(maxN) 110 return nil 111 } 112 113 func (fd *fileDAOLegacy) Stop(ctx context.Context) error { 114 if err := fd.lifecycle.OnStop(ctx); err != nil { 115 return err 116 } 117 return fd.kvStore.Stop(ctx) 118 } 119 120 func (fd *fileDAOLegacy) Height() (uint64, error) { 121 value, err := getValueMustBe8Bytes(fd.kvStore, _blockNS, _topHeightKey) 122 if err != nil { 123 return 0, errors.Wrap(err, "failed to get top height") 124 } 125 return enc.MachineEndian.Uint64(value), nil 126 } 127 128 func (fd *fileDAOLegacy) GetBlockHash(height uint64) (hash.Hash256, error) { 129 if height == 0 { 130 return block.GenesisHash(), nil 131 } 132 h := hash.ZeroHash256 133 value, err := fd.kvStore.Get(_blockHashHeightMappingNS, heightKey(height)) 134 if err != nil { 135 return h, errors.Wrap(err, "failed to get block hash") 136 } 137 if len(h) != len(value) { 138 return h, errors.Wrapf(err, "blockhash is broken with length = %d", len(value)) 139 } 140 copy(h[:], value) 141 return h, nil 142 } 143 144 func (fd *fileDAOLegacy) GetBlockHeight(h hash.Hash256) (uint64, error) { 145 if h == block.GenesisHash() { 146 return 0, nil 147 } 148 value, err := getValueMustBe8Bytes(fd.kvStore, _blockHashHeightMappingNS, hashKey(h)) 149 if err != nil { 150 return 0, errors.Wrap(err, "failed to get block height") 151 } 152 return enc.MachineEndian.Uint64(value), nil 153 } 154 155 func (fd *fileDAOLegacy) GetBlock(h hash.Hash256) (*block.Block, error) { 156 if h == block.GenesisHash() { 157 return block.GenesisBlock(), nil 158 } 159 header, err := fd.Header(h) 160 if err != nil { 161 return nil, errors.Wrapf(err, "failed to get block header %x", h) 162 } 163 body, err := fd.body(h) 164 if err != nil { 165 return nil, errors.Wrapf(err, "failed to get block body %x", h) 166 } 167 footer, err := fd.footer(h) 168 if err != nil { 169 return nil, errors.Wrapf(err, "failed to get block footer %x", h) 170 } 171 return &block.Block{ 172 Header: *header, 173 Body: *body, 174 Footer: *footer, 175 }, nil 176 } 177 178 func (fd *fileDAOLegacy) GetBlockByHeight(height uint64) (*block.Block, error) { 179 hash, err := fd.GetBlockHash(height) 180 if err != nil { 181 return nil, err 182 } 183 return fd.GetBlock(hash) 184 } 185 186 func (fd *fileDAOLegacy) HeaderByHeight(height uint64) (*block.Header, error) { 187 hash, err := fd.GetBlockHash(height) 188 if err != nil { 189 return nil, err 190 } 191 return fd.Header(hash) 192 } 193 194 func (fd *fileDAOLegacy) FooterByHeight(height uint64) (*block.Footer, error) { 195 hash, err := fd.GetBlockHash(height) 196 if err != nil { 197 return nil, err 198 } 199 return fd.footer(hash) 200 } 201 202 func (fd *fileDAOLegacy) GetReceipts(height uint64) ([]*action.Receipt, error) { 203 kvStore, _, err := fd.getDBFromHeight(height) 204 if err != nil { 205 return nil, err 206 } 207 value, err := kvStore.Get(_receiptsNS, byteutil.Uint64ToBytes(height)) 208 if err != nil { 209 return nil, errors.Wrapf(err, "failed to get receipts of block %d", height) 210 } 211 if len(value) == 0 { 212 return nil, errors.Wrap(ErrDataCorruption, "block receipts missing") 213 } 214 receiptsPb := &iotextypes.Receipts{} 215 if err := proto.Unmarshal(value, receiptsPb); err != nil { 216 return nil, errors.Wrap(err, "failed to unmarshal block receipts") 217 } 218 219 var blockReceipts []*action.Receipt 220 for _, receiptPb := range receiptsPb.Receipts { 221 receipt := &action.Receipt{} 222 receipt.ConvertFromReceiptPb(receiptPb) 223 blockReceipts = append(blockReceipts, receipt) 224 } 225 return blockReceipts, nil 226 } 227 228 func (fd *fileDAOLegacy) Header(h hash.Hash256) (*block.Header, error) { 229 value, err := fd.getBlockValue(_blockHeaderNS, h) 230 if err != nil { 231 return nil, errors.Wrapf(err, "failed to get block header %x", h) 232 } 233 if fd.compressBlock { 234 value, err = compress.DecompGzip(value) 235 if err != nil { 236 return nil, errors.Wrapf(err, "error when decompressing a block header %x", h) 237 } 238 } 239 if len(value) == 0 { 240 return nil, errors.Wrapf(ErrDataCorruption, "block header %x is missing", h) 241 } 242 243 header := &block.Header{} 244 if err := header.Deserialize(value); err != nil { 245 return nil, errors.Wrapf(err, "failed to deserialize block header %x", h) 246 } 247 return header, nil 248 } 249 250 func (fd *fileDAOLegacy) body(h hash.Hash256) (*block.Body, error) { 251 value, err := fd.getBlockValue(_blockBodyNS, h) 252 if err != nil { 253 return nil, errors.Wrapf(err, "failed to get block body %x", h) 254 } 255 if fd.compressBlock { 256 value, err = compress.DecompGzip(value) 257 if err != nil { 258 return nil, errors.Wrapf(err, "error when decompressing a block body %x", h) 259 } 260 } 261 262 if len(value) == 0 { 263 // block body could be empty 264 return &block.Body{}, nil 265 } 266 return fd.deser.DeserializeBody(value) 267 } 268 269 func (fd *fileDAOLegacy) footer(h hash.Hash256) (*block.Footer, error) { 270 value, err := fd.getBlockValue(_blockFooterNS, h) 271 if err != nil { 272 return nil, errors.Wrapf(err, "failed to get block footer %x", h) 273 } 274 if fd.compressBlock { 275 value, err = compress.DecompGzip(value) 276 if err != nil { 277 return nil, errors.Wrapf(err, "error when decompressing a block footer %x", h) 278 } 279 } 280 if len(value) == 0 { 281 return nil, errors.Wrapf(ErrDataCorruption, "block footer %x is missing", h) 282 } 283 284 footer := &block.Footer{} 285 if err := footer.Deserialize(value); err != nil { 286 return nil, errors.Wrapf(err, "failed to deserialize block footer %x", h) 287 } 288 return footer, nil 289 } 290 291 func (fd *fileDAOLegacy) ContainsTransactionLog() bool { 292 sys, err := fd.kvStore.Get(_systemLogNS, make([]byte, 8)) 293 return err == nil && string(sys) == _systemLogNS 294 } 295 296 func (fd *fileDAOLegacy) TransactionLogs(height uint64) (*iotextypes.TransactionLogs, error) { 297 if !fd.ContainsTransactionLog() { 298 return nil, ErrNotSupported 299 } 300 301 kvStore, _, err := fd.getDBFromHeight(height) 302 if err != nil { 303 return nil, err 304 } 305 logsBytes, err := kvStore.Get(_systemLogNS, heightKey(height)) 306 if err != nil { 307 return nil, errors.Wrap(err, "failed to get transaction log") 308 } 309 return block.DeserializeSystemLogPb(logsBytes) 310 } 311 312 func (fd *fileDAOLegacy) PutBlock(ctx context.Context, blk *block.Block) error { 313 serHeader, err := blk.Header.Serialize() 314 if err != nil { 315 return errors.Wrap(err, "failed to serialize block header") 316 } 317 serBody, err := blk.Body.Serialize() 318 if err != nil { 319 return errors.Wrap(err, "failed to serialize block body") 320 } 321 serFooter, err := blk.Footer.Serialize() 322 if err != nil { 323 return errors.Wrap(err, "failed to serialize block footer") 324 } 325 if fd.compressBlock { 326 serHeader, err = compress.CompGzip(serHeader) 327 if err != nil { 328 return errors.Wrapf(err, "error when compressing a block header") 329 } 330 serBody, err = compress.CompGzip(serBody) 331 if err != nil { 332 return errors.Wrapf(err, "error when compressing a block body") 333 } 334 serFooter, err = compress.CompGzip(serFooter) 335 if err != nil { 336 return errors.Wrapf(err, "error when compressing a block footer") 337 } 338 } 339 batchForBlock := batch.NewBatch() 340 hash := blk.HashBlock() 341 batchForBlock.Put(_blockHeaderNS, hash[:], serHeader, "failed to put block header") 342 batchForBlock.Put(_blockBodyNS, hash[:], serBody, "failed to put block body") 343 batchForBlock.Put(_blockFooterNS, hash[:], serFooter, "failed to put block footer") 344 blkHeight := blk.Height() 345 heightKey := heightKey(blkHeight) 346 if fd.ContainsTransactionLog() { 347 if sysLog := blk.TransactionLog(); sysLog != nil { 348 batchForBlock.Put(_systemLogNS, heightKey, sysLog.Serialize(), "failed to put transaction log") 349 } 350 } 351 kv, _, err := fd.getTopDB(blkHeight) 352 if err != nil { 353 return err 354 } 355 // write receipts 356 if blk.Receipts != nil { 357 receipts := iotextypes.Receipts{} 358 for _, r := range blk.Receipts { 359 receipts.Receipts = append(receipts.Receipts, r.ConvertToReceiptPb()) 360 } 361 if receiptsBytes, err := proto.Marshal(&receipts); err == nil { 362 batchForBlock.Put(_receiptsNS, byteutil.Uint64ToBytes(blkHeight), receiptsBytes, "failed to put receipts") 363 } else { 364 log.L().Error("failed to serialize receipits for block", zap.Uint64("height", blkHeight)) 365 } 366 } 367 if err := kv.WriteBatch(batchForBlock); err != nil { 368 return err 369 } 370 371 b := batch.NewBatch() 372 heightValue := byteutil.Uint64ToBytes(blkHeight) 373 hashKey := hashKey(hash) 374 b.Put(_blockHashHeightMappingNS, hashKey, heightValue, "failed to put hash -> height mapping") 375 b.Put(_blockHashHeightMappingNS, heightKey, hash[:], "failed to put height -> hash mapping") 376 tipHeight, err := fd.kvStore.Get(_blockNS, _topHeightKey) 377 if err != nil { 378 return errors.Wrap(err, "failed to get top height") 379 } 380 if blkHeight > enc.MachineEndian.Uint64(tipHeight) { 381 b.Put(_blockNS, _topHeightKey, heightValue, "failed to put top height") 382 b.Put(_blockNS, _topHashKey, hash[:], "failed to put top hash") 383 } 384 return fd.kvStore.WriteBatch(b) 385 } 386 387 func (fd *fileDAOLegacy) DeleteTipBlock() error { 388 // First obtain tip height from db 389 height, err := fd.Height() 390 if err != nil { 391 return errors.Wrap(err, "failed to get tip height") 392 } 393 if height == 0 { 394 // should not delete genesis block 395 return errors.New("cannot delete genesis block") 396 } 397 // Obtain tip block hash 398 hash, err := fd.getTipHash() 399 if err != nil { 400 return errors.Wrap(err, "failed to get tip block hash") 401 } 402 403 b := batch.NewBatch() 404 batchForBlock := batch.NewBatch() 405 whichDB, _, err := fd.getDBFromHeight(height) 406 if err != nil { 407 return err 408 } 409 // Delete hash -> block mapping 410 batchForBlock.Delete(_blockHeaderNS, hash[:], "failed to delete block header") 411 batchForBlock.Delete(_blockBodyNS, hash[:], "failed to delete block body") 412 batchForBlock.Delete(_blockFooterNS, hash[:], "failed to delete block footer") 413 // delete receipt 414 batchForBlock.Delete(_receiptsNS, byteutil.Uint64ToBytes(height), "failed to delete receipt") 415 // Delete hash -> height mapping 416 hashKey := hashKey(hash) 417 b.Delete(_blockHashHeightMappingNS, hashKey, "failed to delete hash -> height mapping") 418 // Delete height -> hash mapping 419 heightKey := heightKey(height) 420 b.Delete(_blockHashHeightMappingNS, heightKey, "failed to delete height -> hash mapping") 421 422 // Update tip height 423 b.Put(_blockNS, _topHeightKey, byteutil.Uint64ToBytes(height-1), "failed to put top height") 424 // Update tip hash 425 hash2, err := fd.GetBlockHash(height - 1) 426 if err != nil { 427 return errors.Wrap(err, "failed to get tip block hash") 428 } 429 b.Put(_blockNS, _topHashKey, hash2[:], "failed to put top hash") 430 431 if err := fd.kvStore.WriteBatch(b); err != nil { 432 return err 433 } 434 return whichDB.WriteBatch(batchForBlock) 435 } 436 437 // getTipHash returns the blockchain tip hash 438 func (fd *fileDAOLegacy) getTipHash() (hash.Hash256, error) { 439 value, err := fd.kvStore.Get(_blockNS, _topHashKey) 440 if err != nil { 441 return hash.ZeroHash256, errors.Wrap(err, "failed to get tip hash") 442 } 443 return hash.BytesToHash256(value), nil 444 } 445 446 func (fd *fileDAOLegacy) indexFile(height uint64, index []byte) error { 447 fd.mutex.Lock() 448 defer fd.mutex.Unlock() 449 450 if fd.htf == nil { 451 htf, err := db.NewRangeIndex(fd.kvStore, _heightToFileBucket, make([]byte, 8)) 452 if err != nil { 453 return err 454 } 455 fd.htf = htf 456 } 457 return fd.htf.Insert(height, index) 458 } 459 460 // getFileIndex return the db filename 461 func (fd *fileDAOLegacy) getFileIndex(height uint64) ([]byte, error) { 462 fd.mutex.RLock() 463 defer fd.mutex.RUnlock() 464 465 if fd.htf == nil { 466 htf, err := db.NewRangeIndex(fd.kvStore, _heightToFileBucket, make([]byte, 8)) 467 if err != nil { 468 return nil, err 469 } 470 fd.htf = htf 471 } 472 return fd.htf.Get(height) 473 } 474 475 func (fd *fileDAOLegacy) getTopDB(blkHeight uint64) (kvStore db.KVStore, index uint64, err error) { 476 if fd.cfg.SplitDBSizeMB == 0 || blkHeight <= fd.cfg.SplitDBHeight { 477 return fd.kvStore, 0, nil 478 } 479 topIndex := fd.topIndex.Load().(uint64) 480 dat, err := os.Stat(kthAuxFileName(fd.cfg.DbPath, topIndex)) 481 if err != nil { 482 if !os.IsNotExist(err) { 483 // something wrong getting FileInfo 484 return 485 } 486 // index the height --> file index mapping 487 if err = fd.indexFile(blkHeight, byteutil.Uint64ToBytesBigEndian(topIndex)); err != nil { 488 return 489 } 490 // db file does not exist, create it 491 return fd.openDB(topIndex) 492 } 493 // other errors except file does not exist 494 if err != nil { 495 return 496 } 497 // file exists,but need create new db 498 if uint64(dat.Size()) > fd.cfg.SplitDBSize() { 499 kvStore, index, _ = fd.openDB(topIndex + 1) 500 fd.topIndex.Store(index) 501 // index the height --> file index mapping 502 _ = fd.indexFile(blkHeight, byteutil.Uint64ToBytesBigEndian(index)) 503 return 504 } 505 // db exist,need load from kvStores 506 kv, ok := fd.kvStores.Get(topIndex) 507 if ok { 508 kvStore, ok = kv.(db.KVStore) 509 if !ok { 510 err = errors.New("db convert error") 511 } 512 index = topIndex 513 return 514 } 515 // file exists,but not opened 516 return fd.openDB(topIndex) 517 } 518 519 // getDBFromHash returns db of this block stored 520 func (fd *fileDAOLegacy) getDBFromHash(h hash.Hash256) (db.KVStore, uint64, error) { 521 height, err := fd.GetBlockHeight(h) 522 if err != nil { 523 return nil, 0, err 524 } 525 return fd.getDBFromHeight(height) 526 } 527 528 func (fd *fileDAOLegacy) getDBFromHeight(blkHeight uint64) (kvStore db.KVStore, index uint64, err error) { 529 if fd.cfg.SplitDBSizeMB == 0 { 530 return fd.kvStore, 0, nil 531 } 532 if blkHeight <= fd.cfg.SplitDBHeight { 533 return fd.kvStore, 0, nil 534 } 535 // get file index 536 value, err := fd.getFileIndex(blkHeight) 537 if err != nil { 538 return 539 } 540 return fd.getDBFromIndex(byteutil.BytesToUint64BigEndian(value)) 541 } 542 543 func (fd *fileDAOLegacy) getDBFromIndex(idx uint64) (kvStore db.KVStore, index uint64, err error) { 544 if idx == 0 { 545 return fd.kvStore, 0, nil 546 } 547 kv, ok := fd.kvStores.Get(idx) 548 if ok { 549 kvStore, ok = kv.(db.KVStore) 550 if !ok { 551 err = errors.New("db convert error") 552 } 553 index = idx 554 return 555 } 556 // if user rm some db files manully,then call this method will create new file 557 return fd.openDB(idx) 558 } 559 560 // getBlockValue get block's data from db,if this db failed,it will try the previous one 561 func (fd *fileDAOLegacy) getBlockValue(ns string, h hash.Hash256) ([]byte, error) { 562 whichDB, index, err := fd.getDBFromHash(h) 563 if err != nil { 564 return nil, err 565 } 566 value, err := whichDB.Get(ns, h[:]) 567 if errors.Cause(err) == db.ErrNotExist { 568 idx := index - 1 569 if index == 0 { 570 idx = 0 571 } 572 db, _, err := fd.getDBFromIndex(idx) 573 if err != nil { 574 return nil, err 575 } 576 value, err = db.Get(ns, h[:]) 577 if err != nil { 578 return nil, err 579 } 580 } 581 return value, err 582 } 583 584 // openDB open file if exists, or create new file 585 func (fd *fileDAOLegacy) openDB(idx uint64) (kvStore db.KVStore, index uint64, err error) { 586 if idx == 0 { 587 return fd.kvStore, 0, nil 588 } 589 cfg := fd.cfg 590 cfg.DbPath = kthAuxFileName(cfg.DbPath, idx) 591 592 fd.mutex.Lock() 593 defer fd.mutex.Unlock() 594 // open or create this db file 595 var newFile bool 596 _, err = os.Stat(cfg.DbPath) 597 if err != nil { 598 if !os.IsNotExist(err) { 599 // something wrong getting FileInfo 600 return 601 } 602 newFile = true 603 } 604 605 kvStore = db.NewBoltDB(cfg) 606 fd.kvStores.Add(idx, kvStore) 607 err = kvStore.Start(context.Background()) 608 if err != nil { 609 return 610 } 611 612 if newFile { 613 if err = kvStore.Put(_systemLogNS, make([]byte, 8), []byte(_systemLogNS)); err != nil { 614 return 615 } 616 } 617 fd.lifecycle.Add(kvStore) 618 index = idx 619 return 620 } 621 622 func heightKey(height uint64) []byte { 623 return append(_heightPrefix, byteutil.Uint64ToBytes(height)...) 624 }