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  }