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"
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2021-08-12 10:35:09 +00:00
|
|
|
// PutChangeSet implements the Store interface.
|
storage: use two maps for MemoryStore
Simple and dumb as it is, this allows to separate contract storage from other
things and dramatically improve Seek() time over storage (even though it's
still unordered!) which in turn improves block processing speed.
LevelDB LevelDB (KeepOnlyLatest) BoltDB BoltDB (KeepOnlyLatest)
Master real 16m27,936s real 10m9,440s real 16m39,369s real 8m1,227s
user 20m12,619s user 26m13,925s user 18m9,162s user 18m5,846s
sys 2m56,377s sys 1m32,051s sys 9m52,576s sys 2m9,455s
2 maps real 10m49,495s real 8m53,342s real 11m46,204s real 5m56,043s
user 14m19,922s user 24m6,225s user 13m25,691s user 15m4,694s
sys 1m53,021s sys 1m23,006s sys 4m31,735s sys 2m8,714s
neo-bench performance is mostly unaffected, ~0.5% for 1-1 test and 4% for
10K-10K test both fall within regular test error range.
2022-02-15 16:07:59 +00:00
|
|
|
func (s *BoltDBStore) PutChangeSet(puts map[string][]byte, stores map[string][]byte) error {
|
2022-01-29 08:54:25 +00:00
|
|
|
var err error
|
|
|
|
|
2022-02-11 13:48:35 +00:00
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
2019-09-14 07:28:43 +00:00
|
|
|
b := tx.Bucket(Bucket)
|
storage: use two maps for MemoryStore
Simple and dumb as it is, this allows to separate contract storage from other
things and dramatically improve Seek() time over storage (even though it's
still unordered!) which in turn improves block processing speed.
LevelDB LevelDB (KeepOnlyLatest) BoltDB BoltDB (KeepOnlyLatest)
Master real 16m27,936s real 10m9,440s real 16m39,369s real 8m1,227s
user 20m12,619s user 26m13,925s user 18m9,162s user 18m5,846s
sys 2m56,377s sys 1m32,051s sys 9m52,576s sys 2m9,455s
2 maps real 10m49,495s real 8m53,342s real 11m46,204s real 5m56,043s
user 14m19,922s user 24m6,225s user 13m25,691s user 15m4,694s
sys 1m53,021s sys 1m23,006s sys 4m31,735s sys 2m8,714s
neo-bench performance is mostly unaffected, ~0.5% for 1-1 test and 4% for
10K-10K test both fall within regular test error range.
2022-02-15 16:07:59 +00:00
|
|
|
for _, m := range []map[string][]byte{puts, stores} {
|
|
|
|
for k, v := range m {
|
|
|
|
if v != nil {
|
|
|
|
err = b.Put([]byte(k), v)
|
|
|
|
} else {
|
|
|
|
err = b.Delete([]byte(k))
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-07 14:08:09 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-14 07:28:43 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-02-11 17:35:45 +00:00
|
|
|
// SeekGC implements the Store interface.
|
|
|
|
func (s *BoltDBStore) SeekGC(rng SeekRange, keep func(k, v []byte) bool) error {
|
|
|
|
return boltSeek(s.db.Update, rng, func(c *bbolt.Cursor, k, v []byte) (bool, error) {
|
|
|
|
if !keep(k, v) {
|
|
|
|
if err := c.Delete(); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-14 07:28:43 +00:00
|
|
|
// 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) {
|
2022-02-11 17:35:45 +00:00
|
|
|
err := boltSeek(s.db.View, rng, func(_ *bbolt.Cursor, k, v []byte) (bool, error) {
|
|
|
|
return f(k, v), nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func boltSeek(txopener func(func(*bbolt.Tx) error) error, rng SeekRange, f func(c *bbolt.Cursor, k, v []byte) (bool, error)) error {
|
2022-02-11 15:00:45 +00:00
|
|
|
rang := seekRangeToPrefixes(rng)
|
2022-02-11 17:35:45 +00:00
|
|
|
return txopener(func(tx *bbolt.Tx) error {
|
2022-02-11 15:00:45 +00:00
|
|
|
var (
|
|
|
|
k, v []byte
|
|
|
|
next func() ([]byte, []byte)
|
|
|
|
)
|
|
|
|
|
2019-09-14 07:28:43 +00:00
|
|
|
c := tx.Bucket(Bucket).Cursor()
|
2022-02-11 15:00:45 +00:00
|
|
|
|
|
|
|
if !rng.Backwards {
|
|
|
|
k, v = c.Seek(rang.Start)
|
|
|
|
next = c.Next
|
|
|
|
} else {
|
|
|
|
if len(rang.Limit) == 0 {
|
|
|
|
lastKey, _ := c.Last()
|
|
|
|
k, v = c.Seek(lastKey)
|
|
|
|
} else {
|
|
|
|
c.Seek(rang.Limit)
|
|
|
|
k, v = c.Prev()
|
2022-01-17 17:41:51 +00:00
|
|
|
}
|
2022-02-11 15:00:45 +00:00
|
|
|
next = c.Prev
|
2019-09-14 07:28:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-11 15:00:45 +00:00
|
|
|
for ; k != nil && bytes.HasPrefix(k, rng.Prefix) && (len(rang.Limit) == 0 || bytes.Compare(k, rang.Limit) <= 0); k, v = next() {
|
2022-02-11 17:35:45 +00:00
|
|
|
cont, err := f(c, k, v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !cont {
|
2022-01-17 17:41:51 +00:00
|
|
|
break
|
|
|
|
}
|
2021-12-28 13:01:44 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-14 07:28:43 +00:00
|
|
|
// Close releases all db resources.
|
|
|
|
func (s *BoltDBStore) Close() error {
|
|
|
|
return s.db.Close()
|
|
|
|
}
|