github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/filedao/filedao.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 "fmt" 11 "sync" 12 13 "github.com/pkg/errors" 14 "go.uber.org/zap" 15 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/pkg/log" 23 ) 24 25 const ( 26 _blockHashHeightMappingNS = "h2h" 27 _systemLogNS = "syl" 28 ) 29 30 var ( 31 _topHeightKey = []byte("th") 32 _topHashKey = []byte("ts") 33 _hashPrefix = []byte("ha.") 34 ) 35 36 // vars 37 var ( 38 ErrFileNotExist = errors.New("file does not exist") 39 ErrFileCantAccess = errors.New("cannot access file") 40 ErrFileInvalid = errors.New("file format is not valid") 41 ErrNotSupported = errors.New("feature not supported") 42 ErrAlreadyExist = errors.New("block already exist") 43 ErrInvalidTipHeight = errors.New("invalid tip height") 44 ErrDataCorruption = errors.New("data is corrupted") 45 ) 46 47 type ( 48 // BaseFileDAO represents the basic data access object 49 BaseFileDAO interface { 50 Start(ctx context.Context) error 51 Stop(ctx context.Context) error 52 Height() (uint64, error) 53 GetBlockHash(uint64) (hash.Hash256, error) 54 GetBlockHeight(hash.Hash256) (uint64, error) 55 GetBlock(hash.Hash256) (*block.Block, error) 56 GetBlockByHeight(uint64) (*block.Block, error) 57 GetReceipts(uint64) ([]*action.Receipt, error) 58 ContainsTransactionLog() bool 59 TransactionLogs(uint64) (*iotextypes.TransactionLogs, error) 60 PutBlock(context.Context, *block.Block) error 61 DeleteTipBlock() error 62 } 63 64 // FileDAO represents the data access object for managing block db file 65 FileDAO interface { 66 BaseFileDAO 67 Header(hash.Hash256) (*block.Header, error) 68 HeaderByHeight(uint64) (*block.Header, error) 69 FooterByHeight(uint64) (*block.Footer, error) 70 } 71 72 // fileDAO implements FileDAO 73 fileDAO struct { 74 lock sync.Mutex 75 topIndex uint64 76 splitHeight uint64 77 cfg db.Config 78 currFd BaseFileDAO 79 legacyFd FileDAO 80 v2Fd *FileV2Manager // a collection of v2 db files 81 blockDeserializer *block.Deserializer 82 } 83 ) 84 85 // NewFileDAO creates an instance of FileDAO 86 func NewFileDAO(cfg db.Config, deser *block.Deserializer) (FileDAO, error) { 87 header, err := readFileHeader(cfg.DbPath, FileAll) 88 if err != nil { 89 if err != ErrFileNotExist { 90 return nil, err 91 } 92 // start new chain db using v2 format 93 if err := createNewV2File(1, cfg, deser); err != nil { 94 return nil, err 95 } 96 header = &FileHeader{Version: FileV2} 97 } 98 99 switch header.Version { 100 case FileLegacyAuxiliary: 101 // default chain db file is legacy format, but not master, the master file has been corrupted 102 return nil, ErrFileInvalid 103 case FileLegacyMaster: 104 // master file is legacy format 105 return CreateFileDAO(true, cfg, deser) 106 case FileV2: 107 // master file is v2 format 108 return CreateFileDAO(false, cfg, deser) 109 default: 110 panic(fmt.Errorf("corrupted file version: %s", header.Version)) 111 } 112 } 113 114 // NewFileDAOInMemForTest creates an in-memory FileDAO for testing 115 func NewFileDAOInMemForTest() (FileDAO, error) { 116 return newTestInMemFd() 117 } 118 119 func (fd *fileDAO) Start(ctx context.Context) error { 120 if fd.legacyFd != nil { 121 if err := fd.legacyFd.Start(ctx); err != nil { 122 return err 123 } 124 } 125 if fd.v2Fd != nil { 126 if err := fd.v2Fd.Start(ctx); err != nil { 127 return err 128 } 129 } 130 131 if fd.v2Fd != nil { 132 fd.currFd, fd.splitHeight = fd.v2Fd.TopFd() 133 } else { 134 fd.currFd = fd.legacyFd 135 } 136 return nil 137 } 138 139 func (fd *fileDAO) Stop(ctx context.Context) error { 140 if fd.legacyFd != nil { 141 if err := fd.legacyFd.Stop(ctx); err != nil { 142 return err 143 } 144 } 145 if fd.v2Fd != nil { 146 return fd.v2Fd.Stop(ctx) 147 } 148 return nil 149 } 150 151 func (fd *fileDAO) Height() (uint64, error) { 152 return fd.currFd.Height() 153 } 154 155 func (fd *fileDAO) GetBlockHash(height uint64) (hash.Hash256, error) { 156 if fd.v2Fd != nil { 157 if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil { 158 return v2.GetBlockHash(height) 159 } 160 } 161 162 if fd.legacyFd != nil { 163 return fd.legacyFd.GetBlockHash(height) 164 } 165 return hash.ZeroHash256, ErrNotSupported 166 } 167 168 func (fd *fileDAO) GetBlockHeight(hash hash.Hash256) (uint64, error) { 169 var ( 170 height uint64 171 err error 172 ) 173 if fd.v2Fd != nil { 174 if height, err = fd.v2Fd.GetBlockHeight(hash); err == nil { 175 return height, nil 176 } 177 } 178 179 if fd.legacyFd != nil { 180 return fd.legacyFd.GetBlockHeight(hash) 181 } 182 return 0, err 183 } 184 185 func (fd *fileDAO) GetBlock(hash hash.Hash256) (*block.Block, error) { 186 var ( 187 blk *block.Block 188 err error 189 ) 190 if fd.v2Fd != nil { 191 if blk, err = fd.v2Fd.GetBlock(hash); err == nil { 192 return blk, nil 193 } 194 } 195 196 if fd.legacyFd != nil { 197 return fd.legacyFd.GetBlock(hash) 198 } 199 return nil, err 200 } 201 202 func (fd *fileDAO) GetBlockByHeight(height uint64) (*block.Block, error) { 203 if fd.v2Fd != nil { 204 if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil { 205 return v2.GetBlockByHeight(height) 206 } 207 } 208 209 if fd.legacyFd != nil { 210 return fd.legacyFd.GetBlockByHeight(height) 211 } 212 return nil, ErrNotSupported 213 } 214 215 func (fd *fileDAO) Header(hash hash.Hash256) (*block.Header, error) { 216 var ( 217 blk *block.Block 218 err error 219 ) 220 if fd.v2Fd != nil { 221 if blk, err = fd.v2Fd.GetBlock(hash); err == nil { 222 return &blk.Header, nil 223 } 224 } 225 226 if fd.legacyFd != nil { 227 return fd.legacyFd.Header(hash) 228 } 229 return nil, err 230 } 231 232 func (fd *fileDAO) HeaderByHeight(height uint64) (*block.Header, error) { 233 if fd.v2Fd != nil { 234 if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil { 235 blk, err := v2.GetBlockByHeight(height) 236 if err != nil { 237 return nil, err 238 } 239 return &blk.Header, nil 240 } 241 } 242 243 if fd.legacyFd != nil { 244 return fd.legacyFd.HeaderByHeight(height) 245 } 246 return nil, ErrNotSupported 247 } 248 249 func (fd *fileDAO) FooterByHeight(height uint64) (*block.Footer, error) { 250 if fd.v2Fd != nil { 251 if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil { 252 blk, err := v2.GetBlockByHeight(height) 253 if err != nil { 254 return nil, err 255 } 256 return &blk.Footer, nil 257 } 258 } 259 260 if fd.legacyFd != nil { 261 return fd.legacyFd.FooterByHeight(height) 262 } 263 return nil, ErrNotSupported 264 } 265 266 func (fd *fileDAO) GetReceipts(height uint64) ([]*action.Receipt, error) { 267 if fd.v2Fd != nil { 268 if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil { 269 return v2.GetReceipts(height) 270 } 271 } 272 273 if fd.legacyFd != nil { 274 return fd.legacyFd.GetReceipts(height) 275 } 276 return nil, ErrNotSupported 277 } 278 279 func (fd *fileDAO) ContainsTransactionLog() bool { 280 // TODO: change to ContainsTransactionLog(uint64) 281 return fd.currFd.ContainsTransactionLog() 282 } 283 284 func (fd *fileDAO) TransactionLogs(height uint64) (*iotextypes.TransactionLogs, error) { 285 if fd.v2Fd != nil { 286 if v2 := fd.v2Fd.FileDAOByHeight(height); v2 != nil { 287 return v2.TransactionLogs(height) 288 } 289 } 290 291 if fd.legacyFd != nil { 292 return fd.legacyFd.TransactionLogs(height) 293 } 294 return nil, ErrNotSupported 295 } 296 297 func (fd *fileDAO) PutBlock(ctx context.Context, blk *block.Block) error { 298 // bail out if block already exists 299 h := blk.HashBlock() 300 if _, err := fd.GetBlockHeight(h); err == nil { 301 log.L().Error("Block already exists.", zap.Uint64("height", blk.Height()), log.Hex("hash", h[:])) 302 return ErrAlreadyExist 303 } 304 305 // check if we need to split DB 306 if fd.cfg.V2BlocksToSplitDB > 0 { 307 if err := fd.prepNextDbFile(blk.Height()); err != nil { 308 return err 309 } 310 } 311 return fd.currFd.PutBlock(ctx, blk) 312 } 313 314 func (fd *fileDAO) prepNextDbFile(height uint64) error { 315 tip, err := fd.currFd.Height() 316 if err != nil { 317 return err 318 } 319 if height != tip+1 { 320 return ErrInvalidTipHeight 321 } 322 323 fd.lock.Lock() 324 defer fd.lock.Unlock() 325 326 if height > fd.splitHeight && height-fd.splitHeight >= fd.cfg.V2BlocksToSplitDB { 327 return fd.addNewV2File(height) 328 } 329 return nil 330 } 331 332 func (fd *fileDAO) addNewV2File(height uint64) error { 333 // create a new v2 file 334 cfg := fd.cfg 335 cfg.DbPath = kthAuxFileName(cfg.DbPath, fd.topIndex+1) 336 v2, err := newFileDAOv2(height, cfg, fd.blockDeserializer) 337 if err != nil { 338 return err 339 } 340 341 defer func() { 342 if err == nil { 343 fd.currFd = v2 344 fd.topIndex++ 345 fd.splitHeight = height 346 } 347 }() 348 349 // add the new v2 file to existing v2 manager 350 ctx := context.Background() 351 if fd.v2Fd != nil { 352 if err = fd.v2Fd.AddFileDAO(v2, height); err != nil { 353 return err 354 } 355 err = v2.Start(ctx) 356 return err 357 } 358 359 // create v2 manager 360 fd.v2Fd, _ = newFileV2Manager([]*fileDAOv2{v2}) 361 err = fd.v2Fd.Start(ctx) 362 return err 363 } 364 365 func (fd *fileDAO) DeleteTipBlock() error { 366 return fd.currFd.DeleteTipBlock() 367 } 368 369 // CreateFileDAO creates FileDAO according to master file 370 func CreateFileDAO(legacy bool, cfg db.Config, deser *block.Deserializer) (FileDAO, error) { 371 fd := fileDAO{splitHeight: 1, cfg: cfg, blockDeserializer: deser} 372 fds := []*fileDAOv2{} 373 v2Top, v2Files := checkAuxFiles(cfg.DbPath, FileV2) 374 if legacy { 375 legacyFd, err := newFileDAOLegacy(cfg, deser) 376 if err != nil { 377 return nil, err 378 } 379 fd.legacyFd = legacyFd 380 fd.topIndex, _ = checkAuxFiles(cfg.DbPath, FileLegacyAuxiliary) 381 382 // legacy master file with no v2 files, early exit 383 if len(v2Files) == 0 { 384 return &fd, nil 385 } 386 } else { 387 // v2 master file 388 fds = append(fds, openFileDAOv2(cfg, deser)) 389 } 390 391 // populate v2 files into v2 manager 392 if len(v2Files) > 0 { 393 for _, name := range v2Files { 394 cfg.DbPath = name 395 fds = append(fds, openFileDAOv2(cfg, deser)) 396 } 397 398 // v2 file's top index overrides v1's top 399 fd.topIndex = v2Top 400 } 401 v2Fd, err := newFileV2Manager(fds) 402 if err != nil { 403 return nil, err 404 } 405 fd.v2Fd = v2Fd 406 return &fd, nil 407 } 408 409 // createNewV2File creates a new v2 chain db file 410 func createNewV2File(start uint64, cfg db.Config, deser *block.Deserializer) error { 411 v2, err := newFileDAOv2(start, cfg, deser) 412 if err != nil { 413 return err 414 } 415 416 // calling Start() will write the header 417 ctx := context.Background() 418 if err := v2.Start(ctx); err != nil { 419 return err 420 } 421 return v2.Stop(ctx) 422 }