func (b *BadgerDB) SetGenesisTime(time uint64, transaction ...interface{}) error {
	genesisTimeBytes := make([]byte, 8)
	binary.BigEndian.PutUint64(genesisTimeBytes[:], time)
	return b.updateInTransaction(func(txn *badger.Txn) error {
		return txn.Set(genesisTimeKey, genesisTimeBytes[:])
	}, transaction...)
}
SetGenesisTime sets the head block for the chain.
package db
import (
	"bytes"
	"encoding/binary"
	"log"
	"runtime"
	"time"
	"github.com/sirupsen/logrus"
	"github.com/libp2p/go-libp2p-core/crypto"
	"github.com/phoreproject/synapse/pb"
	"github.com/prysmaticlabs/go-ssz"
	"github.com/golang/protobuf/proto"
	"github.com/dgraph-io/badger"
	"github.com/phoreproject/synapse/chainhash"
	"github.com/phoreproject/synapse/primitives"
)
var _ Database = (*BadgerDB)(nil)
type BadgerDB struct {
	db               *badger.DB
	attestationCache map[uint32]uint64  
}
func NewBadgerDB(databaseDir string) *BadgerDB {
	opts := badger.DefaultOptions(databaseDir)
	opts = opts.WithLogger(logrus.StandardLogger())
	opts.ValueLogFileSize = 8 << 20
	if runtime.GOOS == "windows" {
		opts.Truncate = true
		opts.ValueLogFileSize = 1 << 20
	}
	opts.NumMemtables = 4
	opts.MaxTableSize = 32 << 20  
	opts.NumCompactors = 1
	db, err := badger.Open(opts)
	if err != nil {
		log.Fatal(err)
	}
	b := &BadgerDB{
		db:               db,
		attestationCache: make(map[uint32]uint64),
	}
	go b.GarbageCollect()
	return b
}
var blockPrefix = []byte("block")
func (b *BadgerDB) Flush() error {
	return b.db.DropAll()
}
func (b *BadgerDB) GetBlockForHash(h chainhash.Hash, transaction ...interface{}) (*primitives.Block, error) {
	key := append(blockPrefix, h[:]...)
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(key)
	if err != nil {
		return nil, err
	}
	blockBytesCopy, err := i.ValueCopy(nil)
	if err != nil {
		return nil, err
	}
	blockProto := new(pb.Block)
	err = proto.Unmarshal(blockBytesCopy, blockProto)
	if err != nil {
		return nil, err
	}
	return primitives.BlockFromProto(blockProto)
}
func (b *BadgerDB) SetBlock(block primitives.Block, transaction ...interface{}) error {
	blockHash, err := ssz.HashTreeRoot(block)
	if err != nil {
		return err
	}
	key := append(blockPrefix, blockHash[:]...)
	blockProto := block.ToProto()
	blockSer, err := proto.Marshal(blockProto)
	if err != nil {
		return err
	}
	return b.updateInTransaction(func(txn *badger.Txn) error {
		return txn.Set(key, blockSer)
	}, transaction...)
}
var attestationPrefix = []byte("att")
func (b *BadgerDB) GetLatestAttestation(validator uint32, transaction ...interface{}) (*primitives.Attestation, error) {
	var validatorBytes [4]byte
	binary.BigEndian.PutUint32(validatorBytes[:], validator)
	key := append(attestationPrefix, validatorBytes[:]...)
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(key)
	if err != nil {
		return nil, err
	}
	attestationBytesCopy, err := i.ValueCopy(nil)
	if err != nil {
		return nil, err
	}
	attProto := new(pb.Attestation)
	err = proto.Unmarshal(attestationBytesCopy, attProto)
	if err != nil {
		return nil, err
	}
	return primitives.AttestationFromProto(attProto)
}
func (b *BadgerDB) SetLatestAttestationsIfNeeded(validators []uint32, attestation primitives.Attestation, transaction ...interface{}) error {
	validatorKeys := make([][]byte, len(validators))
	for i := range validators {
		var validatorBytes [4]byte
		binary.BigEndian.PutUint32(validatorBytes[:], validators[i])
		validatorKeys[i] = append(attestationPrefix, validatorBytes[:]...)
	}
	attProto := attestation.ToProto()
	attSer, err := proto.Marshal(attProto)
	if err != nil {
		return err
	}
	return b.updateInTransaction(func(txn *badger.Txn) error {
		for _, key := range validatorKeys {
			item, err := txn.Get(key[:])
			if err == badger.ErrKeyNotFound {
				err := txn.Set(key, attSer)
				if err != nil {
					return err
				}
				continue
			}
			if err != nil {
				return err
			}
			err = item.Value(func(val []byte) error {
				currentAttProto := new(pb.Attestation)
				err := proto.Unmarshal(val, currentAttProto)
				if err != nil {
					return err
				}
				currentAtt, err := primitives.AttestationFromProto(currentAttProto)
				if err != nil {
					return err
				}
				if currentAtt.Data.Slot >= attestation.Data.Slot {
					return nil
				}
				return txn.Set(key, attSer)
			})
			if err != nil {
				return err
			}
		}
		return nil
	}, transaction...)
}
var headBlockKey = []byte("head_block")
var justifiedHeadKey = []byte("justified_head")
var finalizedHeadKey = []byte("finalized_head")
func (b *BadgerDB) SetHeadBlock(h chainhash.Hash, transaction ...interface{}) error {
	return b.updateInTransaction(func(txn *badger.Txn) error {
		return txn.Set(headBlockKey, h[:])
	}, transaction...)
}
func (b *BadgerDB) GetHeadBlock(transaction ...interface{}) (*chainhash.Hash, error) {
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(headBlockKey)
	if err != nil {
		return nil, err
	}
	blockBytesCopy, err := i.ValueCopy(nil)
	if err != nil {
		return nil, err
	}
	return chainhash.NewHash(blockBytesCopy)
}
func (b *BadgerDB) SetJustifiedHead(h chainhash.Hash, transaction ...interface{}) error {
	return b.updateInTransaction(func(txn *badger.Txn) error {
		return txn.Set(justifiedHeadKey, h[:])
	}, transaction...)
}
func (b *BadgerDB) GetJustifiedHead(transaction ...interface{}) (*chainhash.Hash, error) {
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(justifiedHeadKey)
	if err != nil {
		return nil, err
	}
	blockBytesCopy, err := i.ValueCopy(nil)
	if err != nil {
		return nil, err
	}
	return chainhash.NewHash(blockBytesCopy)
}
func (b *BadgerDB) SetFinalizedHead(h chainhash.Hash, transaction ...interface{}) error {
	return b.updateInTransaction(func(txn *badger.Txn) error {
		return txn.Set(finalizedHeadKey, h[:])
	}, transaction...)
}
func (b *BadgerDB) GetFinalizedHead(transaction ...interface{}) (*chainhash.Hash, error) {
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(finalizedHeadKey)
	if err != nil {
		return nil, err
	}
	blockBytesCopy, err := i.ValueCopy(nil)
	if err != nil {
		return nil, err
	}
	return chainhash.NewHash(blockBytesCopy)
}
var blockNodePrefix = []byte("block_node")
type BlockNodeDiskWithoutChildren struct {
	Hash      chainhash.Hash
	Height    uint64
	Slot      uint64
	Parent    chainhash.Hash
	StateRoot chainhash.Hash
}
func (b *BadgerDB) SetBlockNode(node BlockNodeDisk, transaction ...interface{}) error {
	nodeWithoutChildren := BlockNodeDiskWithoutChildren{
		Hash:      node.Hash,
		Height:    node.Height,
		Slot:      node.Slot,
		Parent:    node.Parent,
		StateRoot: node.StateRoot,
	}
	children := node.Children
	buf := new(bytes.Buffer)
	err := binary.Write(buf, binary.BigEndian, nodeWithoutChildren)
	if err != nil {
		return err
	}
	err = binary.Write(buf, binary.BigEndian, uint32(len(children)))
	if err != nil {
		return err
	}
	err = binary.Write(buf, binary.BigEndian, children)
	if err != nil {
		return err
	}
	key := append(blockNodePrefix, node.Hash[:]...)
	return b.updateInTransaction(func(txn *badger.Txn) error {
		return txn.Set(key, buf.Bytes())
	}, transaction...)
}
func (b *BadgerDB) GetBlockNode(h chainhash.Hash, transaction ...interface{}) (*BlockNodeDisk, error) {
	key := append(blockNodePrefix, h[:]...)
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(key)
	if err != nil {
		return nil, err
	}
	blockNodeBytesCopy, err := i.ValueCopy(nil)
	if err != nil {
		return nil, err
	}
	blockNode := new(BlockNodeDiskWithoutChildren)
	r := bytes.NewReader(blockNodeBytesCopy)
	err = binary.Read(r, binary.BigEndian, blockNode)
	if err != nil {
		return nil, err
	}
	var length uint32
	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return nil, err
	}
	blockNodeChildren := make([]chainhash.Hash, length)
	err = binary.Read(r, binary.BigEndian, blockNodeChildren)
	if err != nil {
		return nil, err
	}
	return &BlockNodeDisk{
		Hash:      blockNode.Hash,
		Height:    blockNode.Height,
		Slot:      blockNode.Slot,
		Parent:    blockNode.Parent,
		StateRoot: blockNode.StateRoot,
		Children:  blockNodeChildren,
	}, nil
}
var finalizedStateKey = []byte("finalized_state")
var justifiedStateKey = []byte("justified_state")
func (b *BadgerDB) GetFinalizedState(transaction ...interface{}) (*primitives.State, error) {
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(finalizedStateKey)
	if err != nil {
		return nil, err
	}
	blockStateBytesCopy, err := i.ValueCopy(nil)
	if err != nil {
		return nil, err
	}
	s := new(pb.State)
	err = proto.Unmarshal(blockStateBytesCopy, s)
	if err != nil {
		return nil, err
	}
	return primitives.StateFromProto(s)
}
func (b *BadgerDB) SetFinalizedState(state primitives.State, transaction ...interface{}) error {
	stateBytes, err := proto.Marshal(state.ToProto())
	if err != nil {
		return err
	}
	return b.updateInTransaction(func(txn *badger.Txn) error {
		return txn.Set(finalizedStateKey, stateBytes)
	}, transaction...)
}
func (b *BadgerDB) GetJustifiedState(transaction ...interface{}) (*primitives.State, error) {
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(justifiedStateKey)
	if err != nil {
		return nil, err
	}
	blockStateBytesCopy, err := i.ValueCopy(nil)
	if err != nil {
		return nil, err
	}
	s := new(pb.State)
	err = proto.Unmarshal(blockStateBytesCopy, s)
	if err != nil {
		return nil, err
	}
	return primitives.StateFromProto(s)
}
func (b *BadgerDB) SetJustifiedState(state primitives.State, transaction ...interface{}) error {
	stateBytes, err := proto.Marshal(state.ToProto())
	if err != nil {
		return err
	}
	return b.updateInTransaction(func(txn *badger.Txn) error {
		return txn.Set(justifiedStateKey, stateBytes)
	}, transaction...)
}
func (b *BadgerDB) Close() error {
	return b.db.Close()
}
func (b *BadgerDB) extractTransaction(transaction ...interface{}) *badger.Txn {
	if transaction != nil && transaction[0] != nil {
		return transaction[0].(*badger.Txn)
	}
	return nil
}
func (b *BadgerDB) updateInTransaction(cb func(txn *badger.Txn) error, transaction ...interface{}) error {
	txn := b.extractTransaction(transaction...)
	if txn != nil {
		return cb(txn)
	}
	return b.db.Update(cb)
}
func (b *BadgerDB) TransactionalUpdate(cb func(transaction interface{}) error) error {
	return b.db.Update(func(txn *badger.Txn) error {
		return cb(txn)
	})
}
var genesisTimeKey = []byte("genesis_time")
func (b *BadgerDB) GetGenesisTime(transaction ...interface{}) (uint64, error) {
	txn := b.extractTransaction(transaction...)
	if txn == nil {
		txn = b.db.NewTransaction(false)
		defer txn.Discard()
	}
	i, err := txn.Get(genesisTimeKey)
	if err != nil {
		return 0, err
	}
	genesisTimeBytes, err := i.ValueCopy(nil)
	if err != nil {
		return 0, err
	}
	return binary.BigEndian.Uint64(genesisTimeBytes), nil
}