func DbPutBlockUndoData(dbTx database.Tx, height uint32, utds []UndoTicketData) error {
	meta := dbTx.Metadata()
	bucket := meta.Bucket(dbnamespace.StakeBlockUndoDataBucketName)
	k := make([]byte, 4)
	dbnamespace.ByteOrder.PutUint32(k, height)
	v := serializeBlockUndoData(utds)
	return bucket.Put(k[:], v[:])
}
DbPutBlockUndoData inserts block undo data into the database for a given height.
package ticketdb
import (
	"fmt"
	"time"
	"github.com/HcashOrg/hcashd/blockchain/stake/internal/dbnamespace"
	"github.com/HcashOrg/hcashd/blockchain/stake/internal/tickettreap"
	"github.com/HcashOrg/hcashd/chaincfg/chainhash"
	"github.com/HcashOrg/hcashd/database"
)
const (
	upgradeStartedBit = 0x80000000
	currentDatabaseVersion = 1
)
const databaseInfoSize = 8
type DatabaseInfo struct {
	Version        uint32
	Date           time.Time
	UpgradeStarted bool
}
func serializeDatabaseInfo(dbi *DatabaseInfo) []byte {
	version := dbi.Version
	if dbi.UpgradeStarted {
		version |= upgradeStartedBit
	}
	val := make([]byte, databaseInfoSize)
	versionBytes := make([]byte, 4)
	dbnamespace.ByteOrder.PutUint32(versionBytes, version)
	copy(val[0:4], versionBytes)
	timestampBytes := make([]byte, 4)
	dbnamespace.ByteOrder.PutUint32(timestampBytes, uint32(dbi.Date.Unix()))
	copy(val[4:8], timestampBytes)
	return val
}
func DbPutDatabaseInfo(dbTx database.Tx, dbi *DatabaseInfo) error {
	meta := dbTx.Metadata()
	subsidyBucket := meta.Bucket(dbnamespace.StakeDbInfoBucketName)
	val := serializeDatabaseInfo(dbi)
	return subsidyBucket.Put(dbnamespace.StakeDbInfoBucketName, val[:])
}
func deserializeDatabaseInfo(dbInfoBytes []byte) (*DatabaseInfo, error) {
	if len(dbInfoBytes) < databaseInfoSize {
		return nil, ticketDBError(ErrDatabaseInfoShortRead,
			"short read when deserializing best chain state data")
	}
	rawVersion := dbnamespace.ByteOrder.Uint32(dbInfoBytes[0:4])
	upgradeStarted := (upgradeStartedBit & rawVersion) > 0
	version := rawVersion &^ upgradeStartedBit
	ts := dbnamespace.ByteOrder.Uint32(dbInfoBytes[4:8])
	return &DatabaseInfo{
		Version:        version,
		Date:           time.Unix(int64(ts), 0),
		UpgradeStarted: upgradeStarted,
	}, nil
}
func DbFetchDatabaseInfo(dbTx database.Tx) (*DatabaseInfo, error) {
	meta := dbTx.Metadata()
	bucket := meta.Bucket(dbnamespace.StakeDbInfoBucketName)
	if bucket == nil {
		return nil, nil
	}
	dbInfoBytes := bucket.Get(dbnamespace.StakeDbInfoBucketName)
	if dbInfoBytes == nil {
		return nil, ticketDBError(ErrMissingKey, "missing key for database info")
	}
	return deserializeDatabaseInfo(dbInfoBytes)
}
var minimumBestChainStateSize = chainhash.HashSize + 4 + 4 + 4 + 8 + 8 + 2
type BestChainState struct {
	Hash        chainhash.Hash
	Height      uint32
	KeyHeight	uint32
	Live        uint32
	Missed      uint64
	Revoked     uint64
	PerBlock    uint16
	NextWinners []chainhash.Hash
}
func serializeBestChainState(state BestChainState) []byte {
	serializedData := make([]byte, minimumBestChainStateSize)
	offset := 0
	copy(serializedData[offset:offset+chainhash.HashSize], state.Hash[:])
	offset += chainhash.HashSize
	dbnamespace.ByteOrder.PutUint32(serializedData[offset:], state.Height)
	offset += 4
	dbnamespace.ByteOrder.PutUint32(serializedData[offset:], state.KeyHeight)
	offset += 4
	dbnamespace.ByteOrder.PutUint32(serializedData[offset:], state.Live)
	offset += 4
	dbnamespace.ByteOrder.PutUint64(serializedData[offset:], state.Missed)
	offset += 8
	dbnamespace.ByteOrder.PutUint64(serializedData[offset:], state.Revoked)
	offset += 8
	dbnamespace.ByteOrder.PutUint16(serializedData[offset:], state.PerBlock)
	offset += 2
	ticketBuffer := make([]byte, chainhash.HashSize*int(state.PerBlock))
	serializedData = append(serializedData, ticketBuffer...)
	for i := range state.NextWinners {
		copy(serializedData[offset:offset+chainhash.HashSize],
			state.NextWinners[i][:])
		offset += chainhash.HashSize
	}
	return serializedData[:]
}
func deserializeBestChainState(serializedData []byte) (BestChainState, error) {
	if len(serializedData) < minimumBestChainStateSize {
		return BestChainState{}, ticketDBError(ErrChainStateShortRead,
			"short read when deserializing best chain state data")
	}
	state := BestChainState{}
	offset := 0
	copy(state.Hash[:], serializedData[offset:offset+chainhash.HashSize])
	offset += chainhash.HashSize
	state.Height = dbnamespace.ByteOrder.Uint32(serializedData[offset : offset+4])
	offset += 4
	state.KeyHeight = dbnamespace.ByteOrder.Uint32(serializedData[offset: offset + 4])
	offset += 4
	state.Live = dbnamespace.ByteOrder.Uint32(
		serializedData[offset : offset+4])
	offset += 4
	state.Missed = dbnamespace.ByteOrder.Uint64(
		serializedData[offset : offset+8])
	offset += 8
	state.Revoked = dbnamespace.ByteOrder.Uint64(
		serializedData[offset : offset+8])
	offset += 8
	state.PerBlock = dbnamespace.ByteOrder.Uint16(
		serializedData[offset : offset+2])
	offset += 2
	state.NextWinners = make([]chainhash.Hash, int(state.PerBlock))
	for i := 0; i < int(state.PerBlock); i++ {
		copy(state.NextWinners[i][:],
			serializedData[offset:offset+chainhash.HashSize])
		offset += chainhash.HashSize
	}
	return state, nil
}
func DbFetchBestState(dbTx database.Tx) (BestChainState, error) {
	meta := dbTx.Metadata()
	v := meta.Get(dbnamespace.StakeChainStateKeyName)
	if v == nil {
		return BestChainState{}, ticketDBError(ErrMissingKey,
			"missing key for chain state data")
	}
	return deserializeBestChainState(v)
}
func DbPutBestState(dbTx database.Tx, bcs BestChainState) error {
	serializedData := serializeBestChainState(bcs)
	return dbTx.Metadata().Put(dbnamespace.StakeChainStateKeyName, serializedData)
}
type UndoTicketData struct {
	TicketHash   chainhash.Hash
	TicketHeight uint32
	TicketKeyHeight uint32
	Missed       bool
	Revoked      bool
	Spent        bool
	Expired      bool
}
const undoTicketDataSize = 37 + 4
func undoBitFlagsToByte(missed, revoked, spent, expired bool) byte {
	var b byte
	if missed {
		b |= 1 << 0
	}
	if revoked {
		b |= 1 << 1
	}
	if spent {
		b |= 1 << 2
	}
	if expired {
		b |= 1 << 3
	}
	return b
}
func undoBitFlagsFromByte(b byte) (bool, bool, bool, bool) {
	missed := b&(1<<0) > 0
	revoked := b&(1<<1) > 0
	spent := b&(1<<2) > 0
	expired := b&(1<<3) > 0
	return missed, revoked, spent, expired
}
func serializeBlockUndoData(utds []UndoTicketData) []byte {
	b := make([]byte, len(utds)*undoTicketDataSize)
	offset := 0
	for _, utd := range utds {
		copy(b[offset:offset+chainhash.HashSize], utd.TicketHash[:])
		offset += chainhash.HashSize
		dbnamespace.ByteOrder.PutUint32(b[offset:offset+4], utd.TicketHeight)
		offset += 4
		dbnamespace.ByteOrder.PutUint32(b[offset:offset+4], utd.TicketKeyHeight)
		offset += 4
		b[offset] = undoBitFlagsToByte(utd.Missed, utd.Revoked, utd.Spent,
			utd.Expired)
		offset++
	}
	return b
}
func deserializeBlockUndoData(b []byte) ([]UndoTicketData, error) {
	if b != nil && len(b) == 0 {
		return make([]UndoTicketData, 0), nil
	}
	if len(b) < undoTicketDataSize {
		return nil, ticketDBError(ErrUndoDataShortRead, "short read when "+
			"deserializing block undo data")
	}
	if len(b) % undoTicketDataSize != 0 {
		return nil, ticketDBError(ErrUndoDataCorrupt, "corrupt data found "+
			"when deserializing block undo data")
	}
	entries := len(b) / undoTicketDataSize
	utds := make([]UndoTicketData, entries)
	offset := 0
	for i := 0; i < entries; i++ {
		hash, err := chainhash.NewHash(
			b[offset : offset+chainhash.HashSize])
		if err != nil {
			return nil, ticketDBError(ErrUndoDataCorrupt, "corrupt hash found "+
				"when deserializing block undo data")
		}
		offset += chainhash.HashSize
		height := dbnamespace.ByteOrder.Uint32(b[offset : offset+4])
		offset += 4
		keyHeight := dbnamespace.ByteOrder.Uint32(b[offset : offset+4])
		offset += 4
		missed, revoked, spent, expired := undoBitFlagsFromByte(b[offset])
		offset++
		utds[i] = UndoTicketData{
			TicketHash:   *hash,
			TicketHeight: height,
			TicketKeyHeight: keyHeight,
			Missed:       missed,
			Revoked:      revoked,
			Spent:        spent,
			Expired:      expired,
		}
	}
	return utds, nil
}
func DbFetchBlockUndoData(dbTx database.Tx, height uint32) ([]UndoTicketData, error) {
	meta := dbTx.Metadata()
	bucket := meta.Bucket(dbnamespace.StakeBlockUndoDataBucketName)
	k := make([]byte, 4)
	dbnamespace.ByteOrder.PutUint32(k, height)
	v := bucket.Get(k)
	if v == nil {
		return nil, ticketDBError(ErrMissingKey,
			fmt.Sprintf("missing key %v for block undo data", height))
	}
	return deserializeBlockUndoData(v)
}