2019-09-14 07:28:43 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
2021-07-18 13:32:10 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
2019-09-14 07:28:43 +00:00
|
|
|
"github.com/syndtr/goleveldb/leveldb/util"
|
2020-03-25 14:07:53 +00:00
|
|
|
"go.etcd.io/bbolt"
|
2019-09-14 07:28:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// BoltDBOptions configuration for boltdb.
|
|
|
|
type BoltDBOptions struct {
|
|
|
|
FilePath string `yaml:"FilePath"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bucket represents bucket used in boltdb to store all the data.
|
|
|
|
var Bucket = []byte("DB")
|
|
|
|
|
|
|
|
// BoltDBStore it is the storage implementation for storing and retrieving
|
|
|
|
// blockchain data.
|
|
|
|
type BoltDBStore struct {
|
|
|
|
db *bbolt.DB
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewBoltDBStore returns a new ready to use BoltDB storage with created bucket.
|
2019-09-16 15:52:47 +00:00
|
|
|
func NewBoltDBStore(cfg BoltDBOptions) (*BoltDBStore, error) {
|
2019-09-14 07:28:43 +00:00
|
|
|
var opts *bbolt.Options // should be exposed via BoltDBOptions if anything needed
|
|
|
|
fileMode := os.FileMode(0600) // should be exposed via BoltDBOptions if anything needed
|
|
|
|
fileName := cfg.FilePath
|
2019-11-06 14:12:33 +00:00
|
|
|
if err := io.MakeDirForFile(fileName, "BoltDB"); err != nil {
|
|
|
|
return nil, err
|
2019-09-14 07:28:43 +00:00
|
|
|
}
|
|
|
|
db, err := bbolt.Open(fileName, fileMode, opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
_, err = tx.CreateBucketIfNotExists(Bucket)
|
|
|
|
if err != nil {
|
2020-08-06 16:09:57 +00:00
|
|
|
return fmt.Errorf("could not create root bucket: %w", err)
|
2019-09-14 07:28:43 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return &BoltDBStore{db: db}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put implements the Store interface.
|
|
|
|
func (s *BoltDBStore) Put(key, value []byte) error {
|
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
b := tx.Bucket(Bucket)
|
|
|
|
err := b.Put(key, value)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get implements the Store interface.
|
|
|
|
func (s *BoltDBStore) Get(key []byte) (val []byte, err error) {
|
|
|
|
err = s.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
b := tx.Bucket(Bucket)
|
|
|
|
val = b.Get(key)
|
2020-10-13 15:57:04 +00:00
|
|
|
// Value from Get is only valid for the lifetime of transaction, #1482
|
|
|
|
if val != nil {
|
2021-07-18 13:32:10 +00:00
|
|
|
val = slice.Copy(val)
|
2020-10-13 15:57:04 +00:00
|
|
|
}
|
2019-09-14 07:28:43 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if val == nil {
|
|
|
|
err = ErrKeyNotFound
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-07 14:08:09 +00:00
|
|
|
// Delete implements the Store interface.
|
|
|
|
func (s *BoltDBStore) Delete(key []byte) error {
|
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
b := tx.Bucket(Bucket)
|
|
|
|
return b.Delete(key)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-14 07:28:43 +00:00
|
|
|
// PutBatch implements the Store interface.
|
|
|
|
func (s *BoltDBStore) PutBatch(batch Batch) error {
|
2021-08-12 10:35:09 +00:00
|
|
|
memBatch := batch.(*MemoryBatch)
|
|
|
|
return s.PutChangeSet(memBatch.mem, memBatch.del)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PutChangeSet implements the Store interface.
|
|
|
|
func (s *BoltDBStore) PutChangeSet(puts map[string][]byte, dels map[string]bool) error {
|
2019-09-14 07:28:43 +00:00
|
|
|
return s.db.Batch(func(tx *bbolt.Tx) error {
|
|
|
|
b := tx.Bucket(Bucket)
|
2021-08-12 10:35:09 +00:00
|
|
|
for k, v := range puts {
|
2019-10-07 14:05:53 +00:00
|
|
|
err := b.Put([]byte(k), v)
|
2019-09-14 07:28:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2021-08-12 10:35:09 +00:00
|
|
|
for k := range dels {
|
2019-10-07 14:08:09 +00:00
|
|
|
err := b.Delete([]byte(k))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 07:28:43 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Seek implements the Store interface.
|
2022-01-17 17:41:51 +00:00
|
|
|
func (s *BoltDBStore) Seek(rng SeekRange, f func(k, v []byte) bool) {
|
2021-12-16 13:55:50 +00:00
|
|
|
start := make([]byte, len(rng.Prefix)+len(rng.Start))
|
|
|
|
copy(start, rng.Prefix)
|
|
|
|
copy(start[len(rng.Prefix):], rng.Start)
|
2021-12-28 13:01:44 +00:00
|
|
|
if rng.Backwards {
|
|
|
|
s.seekBackwards(rng.Prefix, start, f)
|
|
|
|
} else {
|
|
|
|
s.seek(rng.Prefix, start, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-17 17:41:51 +00:00
|
|
|
func (s *BoltDBStore) seek(key []byte, start []byte, f func(k, v []byte) bool) {
|
2021-12-28 13:01:44 +00:00
|
|
|
prefix := util.BytesPrefix(key)
|
2021-12-16 13:55:50 +00:00
|
|
|
prefix.Start = start
|
2019-09-14 07:28:43 +00:00
|
|
|
err := s.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
c := tx.Bucket(Bucket).Cursor()
|
2021-12-16 13:55:50 +00:00
|
|
|
for k, v := c.Seek(prefix.Start); k != nil && (len(prefix.Limit) == 0 || bytes.Compare(k, prefix.Limit) <= 0); k, v = c.Next() {
|
2022-01-17 17:41:51 +00:00
|
|
|
if !f(k, v) {
|
|
|
|
break
|
|
|
|
}
|
2019-09-14 07:28:43 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
2020-01-09 07:38:09 +00:00
|
|
|
panic(err)
|
2019-09-14 07:28:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-17 17:41:51 +00:00
|
|
|
func (s *BoltDBStore) seekBackwards(key []byte, start []byte, f func(k, v []byte) bool) {
|
2021-12-28 13:01:44 +00:00
|
|
|
err := s.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
c := tx.Bucket(Bucket).Cursor()
|
|
|
|
// Move cursor to the first kv pair which is followed by the pair matching the specified prefix.
|
|
|
|
if len(start) == 0 {
|
|
|
|
lastKey, _ := c.Last()
|
|
|
|
start = lastKey
|
|
|
|
}
|
|
|
|
rng := util.BytesPrefix(start) // in fact, we only need limit based on start slice to iterate backwards starting from this limit
|
|
|
|
c.Seek(rng.Limit)
|
|
|
|
for k, v := c.Prev(); k != nil && bytes.HasPrefix(k, key); k, v = c.Prev() {
|
2022-01-17 17:41:51 +00:00
|
|
|
if !f(k, v) {
|
|
|
|
break
|
|
|
|
}
|
2021-12-28 13:01:44 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-14 07:28:43 +00:00
|
|
|
// Batch implements the Batch interface and returns a boltdb
|
|
|
|
// compatible Batch.
|
|
|
|
func (s *BoltDBStore) Batch() Batch {
|
2019-09-26 15:39:53 +00:00
|
|
|
return newMemoryBatch()
|
2019-09-14 07:28:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close releases all db resources.
|
|
|
|
func (s *BoltDBStore) Close() error {
|
|
|
|
return s.db.Close()
|
|
|
|
}
|