2019-09-14 07:28:43 +00:00
package storage
import (
"bytes"
2022-10-03 12:05:44 +00:00
"errors"
2019-09-14 07:28:43 +00:00
"fmt"
"os"
2022-07-08 16:42:06 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
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
)
// 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.
2022-07-08 16:42:06 +00:00
func NewBoltDBStore ( cfg dbconfig . BoltDBOptions ) ( * BoltDBStore , error ) {
2022-10-03 12:05:44 +00:00
cp := * bbolt . DefaultOptions // Do not change bbolt's global variable.
opts := & cp
2019-09-14 07:28:43 +00:00
fileMode := os . FileMode ( 0600 ) // should be exposed via BoltDBOptions if anything needed
fileName := cfg . FilePath
2022-10-03 12:05:44 +00:00
if cfg . ReadOnly {
opts . ReadOnly = true
} else {
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 {
2022-10-03 12:05:44 +00:00
return nil , fmt . Errorf ( "failed to open BoltDB instance: %w" , err )
}
if opts . ReadOnly {
err = db . View ( func ( tx * bbolt . Tx ) error {
b := tx . Bucket ( Bucket )
if b == nil {
return errors . New ( "root bucket does not exist" )
}
return nil
} )
} else {
err = db . Update ( func ( tx * bbolt . Tx ) error {
_ , err = tx . CreateBucketIfNotExists ( Bucket )
if err != nil {
return fmt . Errorf ( "could not create root bucket: %w" , err )
}
return nil
} )
2019-09-14 07:28:43 +00:00
}
2022-10-05 05:13:12 +00:00
if err != nil {
2022-10-10 06:20:11 +00:00
closeErr := db . Close ( )
err = fmt . Errorf ( "failed to initialize BoltDB instance: %w" , err )
if closeErr != nil {
2023-03-15 12:47:38 +00:00
err = fmt . Errorf ( "%w, failed to close BoltDB instance: %v" , err , closeErr ) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors
2022-10-10 06:20:11 +00:00
}
return nil , err
2022-10-05 05:13:12 +00:00
}
2019-09-14 07:28:43 +00:00
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 ( )
}