github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/filedao/filedao_v2.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 "sync/atomic" 11 "unsafe" 12 13 "github.com/pkg/errors" 14 15 "github.com/iotexproject/go-pkgs/cache" 16 "github.com/iotexproject/go-pkgs/hash" 17 "github.com/iotexproject/iotex-proto/golang/iotextypes" 18 19 "github.com/iotexproject/iotex-core/action" 20 "github.com/iotexproject/iotex-core/blockchain/block" 21 "github.com/iotexproject/iotex-core/db" 22 "github.com/iotexproject/iotex-core/db/batch" 23 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 24 ) 25 26 // namespace for hash, block, and header storage 27 const ( 28 _hashDataNS = "hsh" 29 _blockDataNS = "bdn" 30 _headerDataNs = "hdr" 31 ) 32 33 var ( 34 _fileHeaderKey = []byte("fh") 35 ) 36 37 type ( 38 // fileDAOv2 handles chain db file after file split activation at v1.1.2 39 fileDAOv2 struct { 40 filename string 41 header *FileHeader 42 tip *FileTip 43 blkBuffer *stagingBuffer 44 blkStorePbCache cache.LRUCache 45 blkCache cache.LRUCache 46 receiptCache cache.LRUCache 47 kvStore db.KVStore 48 batch batch.KVStoreBatch 49 hashStore db.CountingIndex // store block hash 50 blkStore db.CountingIndex // store raw blocks 51 sysStore db.CountingIndex // store transaction log 52 deser *block.Deserializer 53 } 54 ) 55 56 // newFileDAOv2 creates a new v2 file 57 func newFileDAOv2(bottom uint64, cfg db.Config, deser *block.Deserializer) (*fileDAOv2, error) { 58 if bottom == 0 { 59 return nil, ErrNotSupported 60 } 61 62 fd := fileDAOv2{ 63 filename: cfg.DbPath, 64 header: &FileHeader{ 65 Version: FileV2, 66 Compressor: cfg.Compressor, 67 BlockStoreSize: uint64(cfg.BlockStoreBatchSize), 68 Start: bottom, 69 }, 70 tip: &FileTip{ 71 Height: bottom - 1, 72 }, 73 blkStorePbCache: cache.NewThreadSafeLruCache(16), 74 blkCache: cache.NewThreadSafeLruCache(256), 75 receiptCache: cache.NewThreadSafeLruCache(256), 76 kvStore: db.NewBoltDB(cfg), 77 batch: batch.NewBatch(), 78 deser: deser, 79 } 80 return &fd, nil 81 } 82 83 // openFileDAOv2 opens an existing v2 file 84 func openFileDAOv2(cfg db.Config, deser *block.Deserializer) *fileDAOv2 { 85 return &fileDAOv2{ 86 filename: cfg.DbPath, 87 blkStorePbCache: cache.NewThreadSafeLruCache(16), 88 blkCache: cache.NewThreadSafeLruCache(256), 89 receiptCache: cache.NewThreadSafeLruCache(256), 90 kvStore: db.NewBoltDB(cfg), 91 batch: batch.NewBatch(), 92 deser: deser, 93 } 94 } 95 96 func (fd *fileDAOv2) Start(ctx context.Context) error { 97 if err := fd.kvStore.Start(ctx); err != nil { 98 return err 99 } 100 101 // check file header 102 header, err := ReadHeaderV2(fd.kvStore) 103 if err != nil { 104 if errors.Cause(err) != db.ErrBucketNotExist && errors.Cause(err) != db.ErrNotExist { 105 return errors.Wrap(err, "failed to get file header") 106 } 107 // write file header and tip 108 if err = WriteHeaderV2(fd.kvStore, fd.header); err != nil { 109 return err 110 } 111 if err = WriteTip(fd.kvStore, _headerDataNs, _topHeightKey, fd.tip); err != nil { 112 return err 113 } 114 } else { 115 fd.header = header 116 // read file tip 117 if fd.tip, err = ReadTip(fd.kvStore, _headerDataNs, _topHeightKey); err != nil { 118 return err 119 } 120 } 121 122 // create counting index for hash, blk, and transaction log 123 if fd.hashStore, err = db.NewCountingIndexNX(fd.kvStore, []byte(_hashDataNS)); err != nil { 124 return err 125 } 126 if fd.blkStore, err = db.NewCountingIndexNX(fd.kvStore, []byte(_blockDataNS)); err != nil { 127 return err 128 } 129 if fd.sysStore, err = db.NewCountingIndexNX(fd.kvStore, []byte(_systemLogNS)); err != nil { 130 return err 131 } 132 133 // populate staging buffer 134 if fd.blkBuffer, err = fd.populateStagingBuffer(); err != nil { 135 return err 136 } 137 return nil 138 } 139 140 func (fd *fileDAOv2) Stop(ctx context.Context) error { 141 return fd.kvStore.Stop(ctx) 142 } 143 144 func (fd *fileDAOv2) Height() (uint64, error) { 145 tip := fd.loadTip() 146 return tip.Height, nil 147 } 148 149 func (fd *fileDAOv2) Bottom() (uint64, error) { 150 return fd.header.Start, nil 151 } 152 153 func (fd *fileDAOv2) ContainsHeight(height uint64) bool { 154 return fd.header.Start <= height && height <= fd.loadTip().Height 155 } 156 157 func (fd *fileDAOv2) GetBlockHash(height uint64) (hash.Hash256, error) { 158 if height == 0 { 159 return block.GenesisHash(), nil 160 } 161 if !fd.ContainsHeight(height) { 162 return hash.ZeroHash256, db.ErrNotExist 163 } 164 h, err := fd.hashStore.Get(height - fd.header.Start) 165 if err != nil { 166 return hash.ZeroHash256, errors.Wrap(err, "failed to get block hash") 167 } 168 return hash.BytesToHash256(h), nil 169 } 170 171 func (fd *fileDAOv2) GetBlockHeight(h hash.Hash256) (uint64, error) { 172 if h == block.GenesisHash() { 173 return 0, nil 174 } 175 value, err := getValueMustBe8Bytes(fd.kvStore, _blockHashHeightMappingNS, hashKey(h)) 176 if err != nil { 177 return 0, errors.Wrap(err, "failed to get block height") 178 } 179 return byteutil.BytesToUint64BigEndian(value), nil 180 } 181 182 func (fd *fileDAOv2) GetBlock(h hash.Hash256) (*block.Block, error) { 183 height, err := fd.GetBlockHeight(h) 184 if err != nil { 185 return nil, errors.Wrap(err, "failed to get block") 186 } 187 return fd.GetBlockByHeight(height) 188 } 189 190 func (fd *fileDAOv2) GetBlockByHeight(height uint64) (*block.Block, error) { 191 if height == 0 { 192 return block.GenesisBlock(), nil 193 } 194 blk, err := fd.getBlock(height) 195 if err != nil { 196 return nil, errors.Wrapf(err, "failed to get block at height %d", height) 197 } 198 return blk, nil 199 } 200 201 func (fd *fileDAOv2) GetReceipts(height uint64) ([]*action.Receipt, error) { 202 receipts, err := fd.getReceipt(height) 203 if err != nil { 204 return nil, errors.Wrapf(err, "failed to get receipts at height %d", height) 205 } 206 return receipts, nil 207 } 208 209 func (fd *fileDAOv2) ContainsTransactionLog() bool { 210 return true 211 } 212 213 func (fd *fileDAOv2) TransactionLogs(height uint64) (*iotextypes.TransactionLogs, error) { 214 if !fd.ContainsHeight(height) { 215 return nil, ErrNotSupported 216 } 217 218 value, err := fd.sysStore.Get(height - fd.header.Start) 219 if err != nil { 220 return nil, errors.Wrapf(err, "failed to get transaction log at height %d", height) 221 } 222 value, err = decompBytes(value, fd.header.Compressor) 223 if err != nil { 224 return nil, errors.Wrapf(err, "failed to get transaction log at height %d", height) 225 } 226 return block.DeserializeSystemLogPb(value) 227 } 228 229 func (fd *fileDAOv2) PutBlock(_ context.Context, blk *block.Block) error { 230 tip := fd.loadTip() 231 if blk.Height() != tip.Height+1 { 232 return ErrInvalidTipHeight 233 } 234 235 // write tip hash and hash-height mapping 236 if err := fd.putTipHashHeightMapping(blk); err != nil { 237 return errors.Wrap(err, "failed to write hash-height mapping") 238 } 239 240 // write block data 241 if err := fd.putBlock(blk); err != nil { 242 return errors.Wrap(err, "failed to write block") 243 } 244 245 // write receipt and transaction log 246 if err := fd.putTransactionLog(blk); err != nil { 247 return errors.Wrap(err, "failed to write receipt") 248 } 249 250 if err := fd.kvStore.WriteBatch(fd.batch); err != nil { 251 return errors.Wrapf(err, "failed to put block at height %d", blk.Height()) 252 } 253 fd.batch.Clear() 254 // update file tip 255 tip = &FileTip{Height: blk.Height(), Hash: blk.HashBlock()} 256 fd.storeTip(tip) 257 return nil 258 } 259 260 func (fd *fileDAOv2) DeleteTipBlock() error { 261 tip := fd.loadTip() 262 height := tip.Height 263 264 if !fd.ContainsHeight(height) { 265 // cannot delete block that does not exist 266 return ErrNotSupported 267 } 268 269 // delete hash 270 if err := fd.hashStore.Revert(1); err != nil { 271 return err 272 } 273 // delete tip of block storage, if new tip height < lowest block stored in it 274 if height-1 < fd.lowestBlockOfStoreTip() { 275 if err := fd.blkStore.Revert(1); err != nil { 276 return err 277 } 278 } 279 // delete transaction log 280 if err := fd.sysStore.Revert(1); err != nil { 281 return err 282 } 283 284 // delete hash -> height mapping 285 fd.batch.Delete(_blockHashHeightMappingNS, hashKey(tip.Hash), "failed to delete hash -> height mapping") 286 287 // update file tip 288 var ( 289 h = hash.ZeroHash256 290 err error 291 ) 292 if height > fd.header.Start { 293 h, err = fd.GetBlockHash(height - 1) 294 if err != nil { 295 return err 296 } 297 } 298 tip = &FileTip{Height: height - 1, Hash: h} 299 ser, err := tip.Serialize() 300 if err != nil { 301 return err 302 } 303 fd.batch.Put(_headerDataNs, _topHeightKey, ser, "failed to put file tip") 304 305 if err := fd.kvStore.WriteBatch(fd.batch); err != nil { 306 return err 307 } 308 fd.batch.Clear() 309 fd.storeTip(tip) 310 return nil 311 } 312 313 func (fd *fileDAOv2) loadTip() *FileTip { 314 p := (*unsafe.Pointer)(unsafe.Pointer(&fd.tip)) 315 return (*FileTip)(atomic.LoadPointer(p)) 316 } 317 318 func (fd *fileDAOv2) storeTip(tip *FileTip) { 319 p := (*unsafe.Pointer)(unsafe.Pointer(&fd.tip)) 320 atomic.StorePointer(p, unsafe.Pointer(tip)) 321 }