|
// Copyright 2017-2018 DERO Project. All rights reserved.
|
|
// Use of this source code in any form is governed by RESEARCH license.
|
|
// license can be found in the LICENSE file.
|
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
|
//
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
// 32 bit and other systems will use this
|
|
|
|
// +build !amd64 goleveldb
|
|
|
|
package storage
|
|
|
|
import "os"
|
|
import "fmt"
|
|
import "sync"
|
|
import "path/filepath"
|
|
import "encoding/binary"
|
|
|
|
import "github.com/romana/rlog"
|
|
import "github.com/syndtr/goleveldb/leveldb"
|
|
import "github.com/syndtr/goleveldb/leveldb/opt"
|
|
import log "github.com/sirupsen/logrus"
|
|
|
|
import "github.com/deroproject/derosuite/globals"
|
|
|
|
type BoltStore struct {
|
|
DB *leveldb.DB
|
|
tx *leveldb.Transaction
|
|
sync.Mutex // lock this struct
|
|
}
|
|
|
|
var Bolt_backend *BoltStore = &BoltStore{} // global variable
|
|
var logger *log.Entry
|
|
|
|
func (b *BoltStore) Init(params map[string]interface{}) (err error) {
|
|
logger = globals.Logger.WithFields(log.Fields{"com": "STORE"})
|
|
current_path := filepath.Join(os.TempDir(), "derod_leveldb_database.db")
|
|
logger.Infof("Initializing leveldb store at path %s", current_path)
|
|
|
|
// Open the my.db data file in your current directory.
|
|
// It will be created if it doesn't exist.
|
|
options := opt.Options{
|
|
CompactionL0Trigger: 32, // default value is 4
|
|
CompactionTableSize: 64 * 1024 * 1024, // default is 2 Mib
|
|
WriteBuffer: 16 * 1024 * 1024, // default is 4 Mib
|
|
|
|
}
|
|
b.DB, err = leveldb.OpenFile(current_path, &options)
|
|
if err != nil {
|
|
logger.Fatalf("Cannot open boltdb store err %s\n", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *BoltStore) Shutdown() (err error) {
|
|
logger.Infof("Shutting boltdb store")
|
|
if b.DB != nil {
|
|
|
|
b.DB.Close()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// get a new writable tx,
|
|
// we will manage the writable txs manually
|
|
// since a block may cause changes to a number of fields which must be reflected atomically
|
|
// this function is always triggered while the atomic lock is taken
|
|
// this is done avoid a race condition in returning the tx and using it
|
|
func (b *BoltStore) get_new_writable_tx() (tx *leveldb.Transaction) {
|
|
if b.tx != nil {
|
|
tx = b.tx // use existing pending tx
|
|
} else { // create new pending tx
|
|
|
|
tx, err := b.DB.OpenTransaction() // begin a new writable tx
|
|
if err != nil {
|
|
logger.Warnf("Error while creating new writable tx, err %s", err)
|
|
} else {
|
|
b.tx = tx
|
|
rlog.Tracef(1, "Beginning new writable TX")
|
|
|
|
}
|
|
}
|
|
return b.tx
|
|
}
|
|
|
|
// Commit the pending transaction to disk
|
|
func (b *BoltStore) Commit() {
|
|
b.Lock()
|
|
if b.tx != nil {
|
|
rlog.Tracef(1, "Committing writable TX")
|
|
err := b.tx.Commit()
|
|
if err != nil {
|
|
logger.Warnf("Error while commit tx, err %s", err)
|
|
}
|
|
b.tx = nil
|
|
} else {
|
|
logger.Warnf("Trying to Commit a NULL transaction, NOT possible")
|
|
}
|
|
b.Unlock()
|
|
}
|
|
|
|
// Roll back existing changes to disk
|
|
func (b *BoltStore) Rollback() {
|
|
b.Lock()
|
|
if b.tx != nil {
|
|
rlog.Tracef(1, "Rollbacking writable TX")
|
|
b.tx.Discard()
|
|
b.tx = nil
|
|
} else {
|
|
//logger.Warnf("Trying to Rollback a NULL transaction, NOT possible")
|
|
}
|
|
b.Unlock()
|
|
}
|
|
|
|
// sync the DB to disk
|
|
func (b *BoltStore) Sync() {
|
|
b.Lock()
|
|
if b.DB != nil {
|
|
//b.DB.Sync() // sync the DB
|
|
}
|
|
b.Unlock()
|
|
}
|
|
|
|
func (b *BoltStore) StoreObject(universe_name []byte, galaxy_name []byte, solar_name []byte, key []byte, data []byte) (err error) {
|
|
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
|
|
rlog.Tracef(10, "Storing object %s %s %x data len %d\n", string(universe_name), string(galaxy_name), key, len(data))
|
|
// open universe bucket
|
|
tx := b.get_new_writable_tx()
|
|
|
|
keyname := append(universe_name, append(galaxy_name, append(solar_name, key...)...)...)
|
|
// now lets update the object attribute
|
|
err = tx.Put(keyname, data, nil)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
func (b *BoltStore) LoadObject(universe []byte, bucket_name []byte, solar_bucket []byte, key []byte) (data []byte, err error) {
|
|
rlog.Tracef(10, "Loading object %s %s %x\n", string(universe), string(bucket_name), key)
|
|
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
|
|
var tx *leveldb.Transaction
|
|
if b.tx != nil {
|
|
tx = b.tx
|
|
} else {
|
|
tx, err = b.DB.OpenTransaction() // create writable tx and discard it
|
|
defer tx.Discard() // tx must be rolled back always, never commit
|
|
}
|
|
// open universe bucket
|
|
{
|
|
keyname := append(universe, append(bucket_name, append(solar_bucket, key...)...)...)
|
|
|
|
// now lets find the object
|
|
data, err = tx.Get(keyname, nil)
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// this function stores a uint64
|
|
// this will automcatically use the lock
|
|
func (b *BoltStore) StoreUint64(universe_bucket []byte, galaxy_bucket []byte, solar_bucket []byte, key []byte, data uint64) error {
|
|
return b.StoreObject(universe_bucket, galaxy_bucket, solar_bucket, key, itob(data))
|
|
|
|
}
|
|
|
|
// this function loads the data as 64 byte integer
|
|
func (b *BoltStore) LoadUint64(universe_bucket []byte, galaxy_bucket []byte, solar_bucket []byte, key []byte) (uint64, error) {
|
|
object_data, err := b.LoadObject(universe_bucket, galaxy_bucket, solar_bucket, key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if len(object_data) == 0 {
|
|
return 0, fmt.Errorf("No value stored here, we should look more")
|
|
}
|
|
|
|
if len(object_data) != 8 {
|
|
panic("Database corruption, invalid data ")
|
|
}
|
|
|
|
value := binary.BigEndian.Uint64(object_data)
|
|
return value, nil
|
|
}
|
|
|
|
// itob returns an 8-byte big endian representation of v.
|
|
func itob(v uint64) []byte {
|
|
b := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(b, uint64(v))
|
|
return b
|
|
}
|