dione/blockchain/blockchain.go

260 lines
5.8 KiB
Go
Raw Normal View History

2021-06-04 21:18:06 +00:00
package blockchain
2021-05-14 20:32:49 +00:00
import (
"encoding/binary"
2021-05-14 20:32:49 +00:00
"encoding/hex"
"errors"
2021-07-11 00:32:58 +00:00
"os"
2021-05-14 20:32:49 +00:00
2021-06-04 21:18:06 +00:00
"github.com/Secured-Finance/dione/blockchain/utils"
types2 "github.com/Secured-Finance/dione/blockchain/types"
2021-05-14 20:32:49 +00:00
"github.com/fxamacker/cbor/v2"
2021-05-14 20:32:49 +00:00
"github.com/ledgerwatch/lmdb-go/lmdb"
)
const (
DefaultBlockDataPrefix = "blockdata_"
2021-05-14 20:32:49 +00:00
DefaultBlockHeaderPrefix = "header_"
DefaultMetadataIndexName = "metadata"
LatestBlockHeightKey = "latest_block_height"
2021-05-14 20:32:49 +00:00
)
var (
ErrBlockNotFound = errors.New("block isn't found")
ErrLatestHeightNil = errors.New("latest block height is nil")
2021-05-14 20:32:49 +00:00
)
2021-06-04 21:18:06 +00:00
type BlockChain struct {
dbEnv *lmdb.Env
db lmdb.DBI
2021-06-04 21:18:06 +00:00
metadataIndex *utils.Index
heightIndex *utils.Index
2021-05-14 20:32:49 +00:00
}
2021-06-04 21:18:06 +00:00
func NewBlockChain(path string) (*BlockChain, error) {
chain := &BlockChain{}
2021-05-14 20:32:49 +00:00
// configure lmdb env
env, err := lmdb.NewEnv()
if err != nil {
return nil, err
}
2021-07-11 00:32:58 +00:00
err = env.SetMaxDBs(1)
if err != nil {
return nil, err
}
2021-05-14 20:32:49 +00:00
err = env.SetMapSize(100 * 1024 * 1024 * 1024) // 100 GB
if err != nil {
return nil, err
}
2021-07-11 00:32:58 +00:00
err = os.MkdirAll(path, 0755)
if err != nil {
return nil, err
}
err = env.Open(path, 0, 0755)
2021-05-14 20:32:49 +00:00
if err != nil {
return nil, err
}
2021-06-04 21:18:06 +00:00
chain.dbEnv = env
2021-05-14 20:32:49 +00:00
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
}
2021-06-04 21:18:06 +00:00
chain.db = dbi
2021-05-14 20:32:49 +00:00
// create index instances
2021-06-04 21:18:06 +00:00
metadataIndex := utils.NewIndex(DefaultMetadataIndexName, env, dbi)
heightIndex := utils.NewIndex("height", env, dbi)
chain.metadataIndex = metadataIndex
chain.heightIndex = heightIndex
2021-06-04 21:18:06 +00:00
return chain, nil
2021-05-14 20:32:49 +00:00
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) setLatestBlockHeight(height uint64) error {
return bp.metadataIndex.PutUint64([]byte(LatestBlockHeightKey), height)
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) GetLatestBlockHeight() (uint64, error) {
height, err := bp.metadataIndex.GetUint64([]byte(LatestBlockHeightKey))
if err != nil {
2021-06-04 21:18:06 +00:00
if err == utils.ErrIndexKeyNotFound {
return 0, ErrLatestHeightNil
}
return 0, err
}
return height, nil
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) StoreBlock(block *types2.Block) error {
err := bp.dbEnv.Update(func(txn *lmdb.Txn) error {
data, err := cbor.Marshal(block.Data)
2021-05-14 20:32:49 +00:00
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(bp.db, []byte(DefaultBlockDataPrefix+blockHash), data, 0)
2021-05-14 20:32:49 +00:00
if err != nil {
return err
}
err = txn.Put(bp.db, []byte(DefaultBlockHeaderPrefix+blockHash), headerData, 0) // store header separately for easy fetching
return err
})
if err != nil {
return err
}
// update index "height -> block hash"
2021-07-11 00:32:58 +00:00
heightBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(heightBytes, block.Header.Height)
err = bp.heightIndex.PutBytes(heightBytes, block.Header.Hash)
if err != nil {
return err
}
// update latest block height
height, err := bp.GetLatestBlockHeight()
if err != nil && err != ErrLatestHeightNil {
return err
}
if err == ErrLatestHeightNil {
if err = bp.setLatestBlockHeight(block.Header.Height); err != nil {
return err
}
} else {
if block.Header.Height > height {
if err = bp.setLatestBlockHeight(block.Header.Height); err != nil {
return err
}
}
}
return nil
2021-05-14 20:32:49 +00:00
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) HasBlock(blockHash []byte) (bool, error) {
2021-05-14 20:32:49 +00:00
var blockExists bool
err := bp.dbEnv.View(func(txn *lmdb.Txn) error {
h := hex.EncodeToString(blockHash)
_, err := txn.Get(bp.db, []byte(DefaultBlockHeaderPrefix+h)) // try to fetch block header
2021-05-14 20:32:49 +00:00
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
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) FetchBlockData(blockHash []byte) ([]*types2.Transaction, error) {
var data []*types2.Transaction
2021-05-14 20:32:49 +00:00
err := bp.dbEnv.View(func(txn *lmdb.Txn) error {
h := hex.EncodeToString(blockHash)
blockData, err := txn.Get(bp.db, []byte(DefaultBlockDataPrefix+h))
2021-05-14 20:32:49 +00:00
if err != nil {
if lmdb.IsNotFound(err) {
return ErrBlockNotFound
}
return err
}
err = cbor.Unmarshal(blockData, data)
2021-05-14 20:32:49 +00:00
return err
})
if err != nil {
return nil, err
}
return data, nil
2021-05-14 20:32:49 +00:00
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) FetchBlockHeader(blockHash []byte) (*types2.BlockHeader, error) {
var blockHeader types2.BlockHeader
2021-05-14 20:32:49 +00:00
err := bp.dbEnv.View(func(txn *lmdb.Txn) error {
h := hex.EncodeToString(blockHash)
data, err := txn.Get(bp.db, []byte(DefaultBlockHeaderPrefix+h))
2021-05-14 20:32:49 +00:00
if err != nil {
if lmdb.IsNotFound(err) {
return ErrBlockNotFound
}
return err
}
err = cbor.Unmarshal(data, &blockHeader)
return err
})
if err != nil {
return nil, err
}
return &blockHeader, nil
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) FetchBlock(blockHash []byte) (*types2.Block, error) {
var block types2.Block
header, err := bp.FetchBlockHeader(blockHash)
if err != nil {
return nil, err
}
block.Header = header
data, err := bp.FetchBlockData(blockHash)
if err != nil {
return nil, err
}
block.Data = data
return &block, nil
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) FetchBlockByHeight(height uint64) (*types2.Block, error) {
var heightBytes []byte
binary.LittleEndian.PutUint64(heightBytes, height)
blockHash, err := bp.heightIndex.GetBytes(heightBytes)
if err != nil {
2021-06-04 21:18:06 +00:00
if err == utils.ErrIndexKeyNotFound {
return nil, ErrBlockNotFound
}
}
block, err := bp.FetchBlock(blockHash)
if err != nil {
return nil, err
}
return block, nil
}
2021-06-04 21:18:06 +00:00
func (bp *BlockChain) FetchBlockHeaderByHeight(height uint64) (*types2.BlockHeader, error) {
var heightBytes []byte
binary.LittleEndian.PutUint64(heightBytes, height)
blockHash, err := bp.heightIndex.GetBytes(heightBytes)
if err != nil {
2021-06-04 21:18:06 +00:00
if err == utils.ErrIndexKeyNotFound {
return nil, ErrBlockNotFound
}
}
blockHeader, err := bp.FetchBlockHeader(blockHash)
if err != nil {
return nil, err
}
return blockHeader, nil
}