github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/storage/boltdb_store.go (about) 1 package storage 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "time" 9 10 "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" 11 "github.com/nspcc-dev/neo-go/pkg/io" 12 "go.etcd.io/bbolt" 13 ) 14 15 // Bucket represents bucket used in boltdb to store all the data. 16 var Bucket = []byte("DB") 17 18 // BoltDBStore it is the storage implementation for storing and retrieving 19 // blockchain data. 20 type BoltDBStore struct { 21 db *bbolt.DB 22 } 23 24 // defaultOpenTimeout is the default timeout for performing flock on a bbolt database. 25 // bbolt does retries every 50ms during this interval. 26 const defaultOpenTimeout = 1 * time.Second 27 28 // NewBoltDBStore returns a new ready to use BoltDB storage with created bucket. 29 func NewBoltDBStore(cfg dbconfig.BoltDBOptions) (*BoltDBStore, error) { 30 cp := *bbolt.DefaultOptions // Do not change bbolt's global variable. 31 opts := &cp 32 fileMode := os.FileMode(0600) // should be exposed via BoltDBOptions if anything needed 33 fileName := cfg.FilePath 34 if cfg.ReadOnly { 35 opts.ReadOnly = true 36 } else { 37 if err := io.MakeDirForFile(fileName, "BoltDB"); err != nil { 38 return nil, err 39 } 40 } 41 opts.Timeout = defaultOpenTimeout 42 43 db, err := bbolt.Open(fileName, fileMode, opts) 44 if err != nil { 45 return nil, fmt.Errorf("failed to open BoltDB instance: %w", err) 46 } 47 if opts.ReadOnly { 48 err = db.View(func(tx *bbolt.Tx) error { 49 b := tx.Bucket(Bucket) 50 if b == nil { 51 return errors.New("root bucket does not exist") 52 } 53 return nil 54 }) 55 } else { 56 err = db.Update(func(tx *bbolt.Tx) error { 57 _, err = tx.CreateBucketIfNotExists(Bucket) 58 if err != nil { 59 return fmt.Errorf("could not create root bucket: %w", err) 60 } 61 return nil 62 }) 63 } 64 if err != nil { 65 closeErr := db.Close() 66 err = fmt.Errorf("failed to initialize BoltDB instance: %w", err) 67 if closeErr != nil { 68 err = fmt.Errorf("%w, failed to close BoltDB instance: %w", err, closeErr) 69 } 70 return nil, err 71 } 72 73 return &BoltDBStore{db: db}, nil 74 } 75 76 // Get implements the Store interface. 77 func (s *BoltDBStore) Get(key []byte) (val []byte, err error) { 78 err = s.db.View(func(tx *bbolt.Tx) error { 79 b := tx.Bucket(Bucket) 80 // Value from Get is only valid for the lifetime of transaction, #1482 81 val = bytes.Clone(b.Get(key)) 82 return nil 83 }) 84 if val == nil { 85 err = ErrKeyNotFound 86 } 87 return 88 } 89 90 // PutChangeSet implements the Store interface. 91 func (s *BoltDBStore) PutChangeSet(puts map[string][]byte, stores map[string][]byte) error { 92 var err error 93 94 return s.db.Update(func(tx *bbolt.Tx) error { 95 b := tx.Bucket(Bucket) 96 for _, m := range []map[string][]byte{puts, stores} { 97 for k, v := range m { 98 if v != nil { 99 err = b.Put([]byte(k), v) 100 } else { 101 err = b.Delete([]byte(k)) 102 } 103 if err != nil { 104 return err 105 } 106 } 107 } 108 return nil 109 }) 110 } 111 112 // SeekGC implements the Store interface. 113 func (s *BoltDBStore) SeekGC(rng SeekRange, keep func(k, v []byte) bool) error { 114 return boltSeek(s.db.Update, rng, func(c *bbolt.Cursor, k, v []byte) (bool, error) { 115 if !keep(k, v) { 116 if err := c.Delete(); err != nil { 117 return false, err 118 } 119 } 120 return true, nil 121 }) 122 } 123 124 // Seek implements the Store interface. 125 func (s *BoltDBStore) Seek(rng SeekRange, f func(k, v []byte) bool) { 126 err := boltSeek(s.db.View, rng, func(_ *bbolt.Cursor, k, v []byte) (bool, error) { 127 return f(k, v), nil 128 }) 129 if err != nil { 130 panic(err) 131 } 132 } 133 134 func boltSeek(txopener func(func(*bbolt.Tx) error) error, rng SeekRange, f func(c *bbolt.Cursor, k, v []byte) (bool, error)) error { 135 rang := seekRangeToPrefixes(rng) 136 return txopener(func(tx *bbolt.Tx) error { 137 var ( 138 k, v []byte 139 next func() ([]byte, []byte) 140 ) 141 142 c := tx.Bucket(Bucket).Cursor() 143 144 if !rng.Backwards { 145 k, v = c.Seek(rang.Start) 146 next = c.Next 147 } else { 148 if len(rang.Limit) == 0 { 149 lastKey, _ := c.Last() 150 k, v = c.Seek(lastKey) 151 } else { 152 c.Seek(rang.Limit) 153 k, v = c.Prev() 154 } 155 next = c.Prev 156 } 157 158 for ; k != nil && bytes.HasPrefix(k, rng.Prefix) && (len(rang.Limit) == 0 || bytes.Compare(k, rang.Limit) <= 0); k, v = next() { 159 cont, err := f(c, k, v) 160 if err != nil { 161 return err 162 } 163 if !cont { 164 break 165 } 166 } 167 return nil 168 }) 169 } 170 171 // Close releases all db resources. 172 func (s *BoltDBStore) Close() error { 173 return s.db.Close() 174 }