github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/pebble/open.go (about) 1 package pebble 2 3 import ( 4 "fmt" 5 6 "errors" 7 8 "github.com/cockroachdb/pebble" 9 "github.com/hashicorp/go-multierror" 10 11 "github.com/onflow/flow-go/storage" 12 "github.com/onflow/flow-go/storage/pebble/registers" 13 ) 14 15 // NewBootstrappedRegistersWithPath initializes a new Registers instance with a pebble db 16 // if the database is not initialized, it close the database and return storage.ErrNotBootstrapped 17 func NewBootstrappedRegistersWithPath(dir string) (*Registers, *pebble.DB, error) { 18 db, err := OpenRegisterPebbleDB(dir) 19 if err != nil { 20 return nil, nil, fmt.Errorf("failed to initialize pebble db: %w", err) 21 } 22 registers, err := NewRegisters(db) 23 if err != nil { 24 if errors.Is(err, storage.ErrNotBootstrapped) { 25 // closing the db if not bootstrapped 26 dbErr := db.Close() 27 if dbErr != nil { 28 err = multierror.Append(err, fmt.Errorf("failed to close db: %w", dbErr)) 29 } 30 } 31 return nil, nil, fmt.Errorf("failed to initialize registers: %w", err) 32 } 33 return registers, db, nil 34 } 35 36 // OpenRegisterPebbleDB opens the database 37 func OpenRegisterPebbleDB(dir string) (*pebble.DB, error) { 38 cache := pebble.NewCache(1 << 20) 39 defer cache.Unref() 40 // currently pebble is only used for registers 41 opts := DefaultPebbleOptions(cache, registers.NewMVCCComparer()) 42 db, err := pebble.Open(dir, opts) 43 if err != nil { 44 return nil, fmt.Errorf("failed to open db: %w", err) 45 } 46 47 return db, nil 48 } 49 50 // ReadHeightsFromBootstrappedDB reads the first and latest height from a bootstrapped register db 51 // If the register db is not bootstrapped, it returns storage.ErrNotBootstrapped 52 // If the register db is corrupted, it returns an error 53 func ReadHeightsFromBootstrappedDB(db *pebble.DB) (firstHeight uint64, latestHeight uint64, err error) { 54 // check height keys and populate cache. These two variables will have been set 55 firstHeight, err = firstStoredHeight(db) 56 if err != nil { 57 if errors.Is(err, storage.ErrNotFound) { 58 return 0, 0, fmt.Errorf("unable to initialize register storage, first height not found in db: %w", storage.ErrNotBootstrapped) 59 } 60 // this means that the DB is either in a corrupted state or has not been initialized 61 return 0, 0, fmt.Errorf("unable to initialize register storage, first height unavailable in db: %w", err) 62 } 63 latestHeight, err = latestStoredHeight(db) 64 if err != nil { 65 // first height is found, but latest height is not found, this means that the DB is in a corrupted state 66 return 0, 0, fmt.Errorf("unable to initialize register storage, latest height unavailable in db: %w", err) 67 } 68 return firstHeight, latestHeight, nil 69 } 70 71 // IsBootstrapped returns true if the db is bootstrapped 72 // otherwise return false 73 // it returns error if the db is corrupted or other exceptions 74 func IsBootstrapped(db *pebble.DB) (bool, error) { 75 _, err1 := firstStoredHeight(db) 76 _, err2 := latestStoredHeight(db) 77 78 if err1 == nil && err2 == nil { 79 return true, nil 80 } 81 82 if errors.Is(err1, storage.ErrNotFound) && errors.Is(err2, storage.ErrNotFound) { 83 return false, nil 84 } 85 86 return false, fmt.Errorf("unable to check if db is bootstrapped %v: %w", err1, err2) 87 } 88 89 func initHeights(db *pebble.DB, firstHeight uint64) error { 90 batch := db.NewBatch() 91 defer batch.Close() 92 // update heights atomically to prevent one getting populated without the other 93 err := batch.Set(firstHeightKey, encodedUint64(firstHeight), nil) 94 if err != nil { 95 return fmt.Errorf("unable to add first height to batch: %w", err) 96 } 97 err = batch.Set(latestHeightKey, encodedUint64(firstHeight), nil) 98 if err != nil { 99 return fmt.Errorf("unable to add latest height to batch: %w", err) 100 } 101 err = batch.Commit(pebble.Sync) 102 if err != nil { 103 return fmt.Errorf("unable to index first and latest heights: %w", err) 104 } 105 return nil 106 }