240 lines
5.3 KiB
Go
240 lines
5.3 KiB
Go
package lmdb
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"os"
|
|
|
|
"github.com/Secured-Finance/dione/blockchain/database"
|
|
types2 "github.com/Secured-Finance/dione/blockchain/types"
|
|
"github.com/fxamacker/cbor/v2"
|
|
"github.com/ledgerwatch/lmdb-go/lmdb"
|
|
)
|
|
|
|
const (
|
|
DefaultBlockDataPrefix = "blockdata_"
|
|
DefaultBlockHeaderPrefix = "header_"
|
|
DefaultMetadataIndexName = "metadata"
|
|
LatestBlockHeightKey = "latest_block_height"
|
|
)
|
|
|
|
type Database struct {
|
|
dbEnv *lmdb.Env
|
|
db lmdb.DBI
|
|
metadataIndex *Index
|
|
heightIndex *Index
|
|
}
|
|
|
|
func NewDatabase(path string) (*Database, error) {
|
|
db := &Database{}
|
|
|
|
// configure lmdb env
|
|
env, err := lmdb.NewEnv()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = env.SetMaxDBs(1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = env.SetMapSize(100 * 1024 * 1024 * 1024) // 100 GB
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = os.MkdirAll(path, 0755)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = env.Open(path, 0, 0755)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
db.dbEnv = env
|
|
|
|
var dbi lmdb.DBI
|
|
err = env.Update(func(txn *lmdb.Txn) error {
|
|
dbi, err = txn.OpenDBI("blocks", lmdb.Create)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
db.db = dbi
|
|
|
|
// create index instances
|
|
metadataIndex := NewIndex(DefaultMetadataIndexName, env, dbi)
|
|
heightIndex := NewIndex("height", env, dbi)
|
|
db.metadataIndex = metadataIndex
|
|
db.heightIndex = heightIndex
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func (d *Database) StoreBlock(block *types2.Block) error {
|
|
err := d.dbEnv.Update(func(txn *lmdb.Txn) error {
|
|
data, err := cbor.Marshal(block.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
headerData, err := cbor.Marshal(block.Header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
blockHash := hex.EncodeToString(block.Header.Hash)
|
|
err = txn.Put(d.db, []byte(DefaultBlockDataPrefix+blockHash), data, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = txn.Put(d.db, []byte(DefaultBlockHeaderPrefix+blockHash), headerData, 0) // store header separately for easy fetching
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// update index "height -> block hash"
|
|
heightBytes := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(heightBytes, block.Header.Height)
|
|
err = d.heightIndex.PutBytes(heightBytes, block.Header.Hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Database) HasBlock(blockHash []byte) (bool, error) {
|
|
var blockExists bool
|
|
err := d.dbEnv.View(func(txn *lmdb.Txn) error {
|
|
h := hex.EncodeToString(blockHash)
|
|
_, err := txn.Get(d.db, []byte(DefaultBlockHeaderPrefix+h)) // try to fetch block header
|
|
if err != nil {
|
|
if lmdb.IsNotFound(err) {
|
|
blockExists = false
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
blockExists = true
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return blockExists, nil
|
|
}
|
|
|
|
func (d *Database) FetchBlockData(blockHash []byte) ([]*types2.Transaction, error) {
|
|
var data []*types2.Transaction
|
|
err := d.dbEnv.View(func(txn *lmdb.Txn) error {
|
|
h := hex.EncodeToString(blockHash)
|
|
blockData, err := txn.Get(d.db, []byte(DefaultBlockDataPrefix+h))
|
|
if err != nil {
|
|
if lmdb.IsNotFound(err) {
|
|
return database.ErrBlockNotFound
|
|
}
|
|
return err
|
|
}
|
|
err = cbor.Unmarshal(blockData, &data)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (d *Database) FetchBlockHeader(blockHash []byte) (*types2.BlockHeader, error) {
|
|
var blockHeader types2.BlockHeader
|
|
err := d.dbEnv.View(func(txn *lmdb.Txn) error {
|
|
h := hex.EncodeToString(blockHash)
|
|
data, err := txn.Get(d.db, []byte(DefaultBlockHeaderPrefix+h))
|
|
if err != nil {
|
|
if lmdb.IsNotFound(err) {
|
|
return database.ErrBlockNotFound
|
|
}
|
|
return err
|
|
}
|
|
err = cbor.Unmarshal(data, &blockHeader)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &blockHeader, nil
|
|
}
|
|
|
|
func (d *Database) FetchBlock(blockHash []byte) (*types2.Block, error) {
|
|
var block types2.Block
|
|
header, err := d.FetchBlockHeader(blockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block.Header = header
|
|
|
|
data, err := d.FetchBlockData(blockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block.Data = data
|
|
|
|
return &block, nil
|
|
}
|
|
|
|
func (d *Database) FetchBlockByHeight(height uint64) (*types2.Block, error) {
|
|
var heightBytes = make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(heightBytes, height)
|
|
blockHash, err := d.heightIndex.GetBytes(heightBytes)
|
|
if err != nil {
|
|
if err == ErrIndexKeyNotFound {
|
|
return nil, database.ErrBlockNotFound
|
|
}
|
|
}
|
|
block, err := d.FetchBlock(blockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return block, nil
|
|
}
|
|
|
|
func (d *Database) FetchBlockHeaderByHeight(height uint64) (*types2.BlockHeader, error) {
|
|
var heightBytes = make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(heightBytes, height)
|
|
blockHash, err := d.heightIndex.GetBytes(heightBytes)
|
|
if err != nil {
|
|
if err == ErrIndexKeyNotFound {
|
|
return nil, database.ErrBlockNotFound
|
|
}
|
|
}
|
|
blockHeader, err := d.FetchBlockHeader(blockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return blockHeader, nil
|
|
}
|
|
|
|
func (d *Database) GetLatestBlockHeight() (uint64, error) {
|
|
height, err := d.metadataIndex.GetUint64([]byte(LatestBlockHeightKey))
|
|
if err != nil {
|
|
if err == ErrIndexKeyNotFound {
|
|
return 0, database.ErrLatestHeightNil
|
|
}
|
|
return 0, err
|
|
}
|
|
return height, nil
|
|
}
|
|
|
|
func (d *Database) SetLatestBlockHeight(height uint64) error {
|
|
err := d.metadataIndex.PutUint64([]byte(LatestBlockHeightKey), height)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|