func voteBlockScript(parentBlock *wire.MsgBlock) []byte {
	var data [40]byte
	parentHash := parentBlock.BlockHash()
	copy(data[:], parentHash[:])
	binary.LittleEndian.PutUint32(data[32:], parentBlock.Header.Height)
	binary.LittleEndian.PutUint32(data[36:], parentBlock.Header.KeyHeight)
	script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).
		AddData(data[:]).Script()
	if err != nil {
		panic(err)
	}
	return script
}
voteBlockScript returns a standard provably-pruneable OP_RETURN script
suitable for use in a vote tx (ssgen) given the block to vote on.
package chaingen
import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
	"math/big"
	"runtime"
	"sort"
	"time"
	"github.com/HcashOrg/hcashd/blockchain"
	"github.com/HcashOrg/hcashd/chaincfg"
	"github.com/HcashOrg/hcashd/chaincfg/chainhash"
	"github.com/HcashOrg/hcashd/txscript"
	"github.com/HcashOrg/hcashd/wire"
	"github.com/HcashOrg/hcashutil"
)
var (
	hash256prngSeedConst = []byte{0x24, 0x3f, 0x6a, 0x88, 0x85, 0xa3, 0x08,
		0xd3}
	opTrueScript = []byte{txscript.OP_TRUE}
	opTrueRedeemScript = []byte{txscript.OP_DATA_1, txscript.OP_TRUE}
	coinbaseSigScript = []byte{txscript.OP_0, txscript.OP_0}
)
const (
	voteBitYes = 0x01
)
type SpendableOut struct {
	prevOut     wire.OutPoint
	blockHeight uint32
	blockIndex  uint32
	amount      hcashutil.Amount
}
func (s *SpendableOut) PrevOut() wire.OutPoint {
	return s.prevOut
}
func (s *SpendableOut) BlockHeight() uint32 {
	return s.blockHeight
}
func (s *SpendableOut) BlockIndex() uint32 {
	return s.blockIndex
}
func (s *SpendableOut) Amount() hcashutil.Amount {
	return s.amount
}
func MakeSpendableOutForTx(tx *wire.MsgTx, blockHeight, txIndex, txOutIndex uint32) SpendableOut {
	return SpendableOut{
		prevOut: wire.OutPoint{
			Hash:  *tx.CachedTxHash(),
			Index: txOutIndex,
			Tree:  wire.TxTreeRegular,
		},
		blockHeight: blockHeight,
		blockIndex:  txIndex,
		amount:      hcashutil.Amount(tx.TxOut[txOutIndex].Value),
	}
}
func MakeSpendableOut(block *wire.MsgBlock, txIndex, txOutIndex uint32) SpendableOut {
	tx := block.Transactions[txIndex]
	return MakeSpendableOutForTx(tx, block.Header.Height, txIndex, txOutIndex)
}
type stakeTicket struct {
	tx          *wire.MsgTx
	blockHeight uint32
	blockIndex  uint32
}
type stakeTicketSorter []*stakeTicket
func (t stakeTicketSorter) Len() int { return len(t) }
func (t stakeTicketSorter) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t stakeTicketSorter) Less(i, j int) bool {
	iHash := t[i].tx.CachedTxHash()[:]
	jHash := t[j].tx.CachedTxHash()[:]
	return bytes.Compare(iHash, jHash) < 0
}
type Generator struct {
	params           *chaincfg.Params
	tip              *wire.MsgBlock
	tipName          string
	blocks           map[chainhash.Hash]*wire.MsgBlock
	blocksByName     map[string]*wire.MsgBlock
	p2shOpTrueAddr   hcashutil.Address
	p2shOpTrueScript []byte
	spendableOuts     [][]SpendableOut
	prevCollectedHash chainhash.Hash
	originalParents map[chainhash.Hash]chainhash.Hash
	immatureTickets []*stakeTicket
	liveTickets     []*stakeTicket
	wonTickets      map[chainhash.Hash][]*stakeTicket
	expiredTickets  []*stakeTicket
}
func MakeGenerator(params *chaincfg.Params) (Generator, error) {
	p2shOpTrueAddr, err := hcashutil.NewAddressScriptHash(opTrueScript, params)
	if err != nil {
		return Generator{}, err
	}
	p2shOpTrueScript, err := txscript.PayToAddrScript(p2shOpTrueAddr)
	if err != nil {
		return Generator{}, err
	}
	genesis := params.GenesisBlock
	genesisHash := genesis.BlockHash()
	return Generator{
		params:           params,
		tip:              genesis,
		tipName:          "genesis",
		blocks:           map[chainhash.Hash]*wire.MsgBlock{genesisHash: genesis},
		blocksByName:     map[string]*wire.MsgBlock{"genesis": genesis},
		p2shOpTrueAddr:   p2shOpTrueAddr,
		p2shOpTrueScript: p2shOpTrueScript,
		originalParents:  make(map[chainhash.Hash]chainhash.Hash),
		wonTickets:       make(map[chainhash.Hash][]*stakeTicket),
	}, nil
}
func (g *Generator) Params() *chaincfg.Params {
	return g.params
}
func (g *Generator) Tip() *wire.MsgBlock {
	return g.tip
}
func (g *Generator) TipName() string {
	return g.tipName
}
func (g *Generator) BlockByName(blockName string) *wire.MsgBlock {
	block, ok := g.blocksByName[blockName]
	if !ok {
		panic(fmt.Sprintf("block name %s does not exist", blockName))
	}
	return block
}
func (g *Generator) BlockByHash(hash *chainhash.Hash) *wire.MsgBlock {
	block, ok := g.blocks[*hash]
	if !ok {
		panic(fmt.Sprintf("block with hash %s does not exist", hash))
	}
	return block
}
func opReturnScript(data []byte) []byte {
	builder := txscript.NewScriptBuilder()
	script, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script()
	if err != nil {
		panic(err)
	}
	return script
}
func UniqueOpReturnScript() []byte {
	rand, err := wire.RandomUint64()
	if err != nil {
		panic(err)
	}
	data := make([]byte, 8, 8)
	binary.LittleEndian.PutUint64(data[0:8], rand)
	return opReturnScript(data)
}
func (g *Generator) calcFullSubsidy(blockHeight uint32) hcashutil.Amount {
	iterations := int64(blockHeight) / g.params.SubsidyReductionInterval
	subsidy := g.params.BaseSubsidy
	for i := int64(0); i < iterations; i++ {
		subsidy *= g.params.MulSubsidy
		subsidy /= g.params.DivSubsidy
	}
	return hcashutil.Amount(subsidy)
}
func (g *Generator) calcPoWSubsidy(fullSubsidy hcashutil.Amount, blockHeight uint32, numVotes uint16) hcashutil.Amount {
	powProportion := hcashutil.Amount(g.params.WorkRewardProportion)
	totalProportions := hcashutil.Amount(g.params.TotalSubsidyProportions())
	powSubsidy := (fullSubsidy * powProportion) / totalProportions
	if int64(blockHeight) < g.params.StakeValidationHeight {
		return powSubsidy
	}
	ticketsPerBlock := hcashutil.Amount(g.params.TicketsPerBlock)
	return (powSubsidy * hcashutil.Amount(numVotes)) / ticketsPerBlock
}
func (g *Generator) calcPoSSubsidy(heightVotedOn uint32) hcashutil.Amount {
	if int64(heightVotedOn+2) < g.params.StakeValidationHeight {
		return 0
	}
	fullSubsidy := g.calcFullSubsidy(heightVotedOn)
	posProportion := hcashutil.Amount(g.params.StakeRewardProportion)
	totalProportions := hcashutil.Amount(g.params.TotalSubsidyProportions())
	return (fullSubsidy * posProportion) / totalProportions
}
func (g *Generator) calcDevSubsidy(fullSubsidy hcashutil.Amount, blockHeight uint32, numVotes uint16) hcashutil.Amount {
	devProportion := hcashutil.Amount(g.params.BlockTaxProportion)
	totalProportions := hcashutil.Amount(g.params.TotalSubsidyProportions())
	devSubsidy := (fullSubsidy * devProportion) / totalProportions
	if int64(blockHeight) < g.params.StakeValidationHeight {
		return devSubsidy
	}
	ticketsPerBlock := hcashutil.Amount(g.params.TicketsPerBlock)
	return (devSubsidy * hcashutil.Amount(numVotes)) / ticketsPerBlock
}
func standardCoinbaseOpReturnScript(blockHeight uint32) []byte {
	rand, err := wire.RandomUint64()
	if err != nil {
		panic(err)
	}
	data := make([]byte, 36, 36)
	binary.LittleEndian.PutUint32(data[0:4], blockHeight)
	binary.LittleEndian.PutUint64(data[28:36], rand)
	return opReturnScript(data)
}
func (g *Generator) addExtraCoinbaseTxOutputs(tx *wire.MsgTx, blockHeight uint32, devSubsidy, powSubsidy hcashutil.Amount) {
	tx.AddTxOut(&wire.TxOut{
		Value:    int64(devSubsidy),
		Version:  g.params.OrganizationPkScriptVersion,
		PkScript: g.params.OrganizationPkScript,
	})
	tx.AddTxOut(wire.NewTxOut(0, standardCoinbaseOpReturnScript(blockHeight)))
	const numPoWOutputs = 6
	amount := powSubsidy / numPoWOutputs
	for i := 0; i < numPoWOutputs; i++ {
		if i == numPoWOutputs-1 {
			amount = powSubsidy - amount*(numPoWOutputs-1)
		}
		tx.AddTxOut(wire.NewTxOut(int64(amount), g.p2shOpTrueScript))
	}
}
func (g *Generator) addCoinbaseTxOutputs(tx *wire.MsgTx, opReturnPkScript []byte) {
	tx.AddTxOut(&wire.TxOut{
		Value:    int64(0),
		Version:  g.params.OrganizationPkScriptVersion,
		PkScript: opReturnPkScript,
	})
	tx.AddTxOut(wire.NewTxOut(int64(0), g.p2shOpTrueScript))
}
func (g *Generator) CreateCoinbaseTx(opReturnPkScript []byte) *wire.MsgTx {
	tx := wire.NewMsgTx()
	tx.AddTxIn(&wire.TxIn{
		PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
			wire.MaxPrevOutIndex, wire.TxTreeRegular),
		Sequence:        wire.MaxTxInSequenceNum,
		ValueIn:         int64(0),
		BlockHeight:     wire.NullBlockHeight,
		BlockIndex:      wire.NullBlockIndex,
		SignatureScript: coinbaseSigScript,
	})
	g.addCoinbaseTxOutputs(tx, opReturnPkScript)
	return tx
}
func (g *Generator) CreateExtraCoinbaseTx(blockHeight uint32, numVotes uint16) *wire.MsgTx {
	height := blockHeight
	if blockHeight >= 0 {
		height = height - 1
	}
	fullSubsidy := g.calcFullSubsidy(height)
	devSubsidy := g.calcDevSubsidy(fullSubsidy, height, numVotes)
	powSubsidy := g.calcPoWSubsidy(fullSubsidy, height, numVotes)
	tx := wire.NewMsgTx()
	tx.AddTxIn(&wire.TxIn{
		PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
			wire.MaxPrevOutIndex, wire.TxTreeRegular),
		Sequence:        wire.MaxTxInSequenceNum,
		ValueIn:         int64(devSubsidy + powSubsidy),
		BlockHeight:     wire.NullBlockHeight,
		BlockIndex:      wire.NullBlockIndex,
		SignatureScript: coinbaseSigScript,
	})
	g.addExtraCoinbaseTxOutputs(tx, blockHeight, devSubsidy, powSubsidy)
	return tx
}
func purchaseCommitmentScript(addr hcashutil.Address, amount, voteFeeLimit, revocationFeeLimit hcashutil.Amount) []byte {
	limits := uint16(0)
	if voteFeeLimit != 0 {
		exp := uint16(math.Ceil(math.Log2(float64(voteFeeLimit))))
		limits |= (exp | 0x40)
	}
	if revocationFeeLimit != 0 {
		exp := uint16(math.Ceil(math.Log2(float64(revocationFeeLimit))))
		limits |= ((exp | 0x40) << 8)
	}
	var data [30]byte
	copy(data[:], addr.ScriptAddress())
	binary.LittleEndian.PutUint64(data[20:], uint64(amount))
	data[27] |= 1 << 7
	binary.LittleEndian.PutUint16(data[28:], limits)
	script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).
		AddData(data[:]).Script()
	if err != nil {
		panic(err)
	}
	return script
}
func (g *Generator) createTicketPurchaseTx(spend *SpendableOut, ticketPrice, fee hcashutil.Amount) *wire.MsgTx {
	pkScript, err := txscript.PayToSStx(g.p2shOpTrueAddr)
	if err != nil {
		panic(err)
	}
	commitScript := purchaseCommitmentScript(g.p2shOpTrueAddr,
		ticketPrice+fee, 0, ticketPrice)
	change := spend.amount - ticketPrice - fee
	changeScript, err := txscript.PayToSStxChange(g.p2shOpTrueAddr)
	if err != nil {
		panic(err)
	}
	tx := wire.NewMsgTx()
	tx.AddTxIn(&wire.TxIn{
		PreviousOutPoint: spend.prevOut,
		Sequence:         wire.MaxTxInSequenceNum,
		ValueIn:          int64(spend.amount),
		BlockHeight:      spend.blockHeight,
		BlockIndex:       spend.blockIndex,
		SignatureScript:  opTrueRedeemScript,
	})
	tx.AddTxOut(wire.NewTxOut(int64(ticketPrice), pkScript))
	tx.AddTxOut(wire.NewTxOut(0, commitScript))
	tx.AddTxOut(wire.NewTxOut(int64(change), changeScript))
	return tx
}
func isTicketPurchaseTx(tx *wire.MsgTx) bool {
	if len(tx.TxOut) == 0 {
		return false
	}
	txOut := tx.TxOut[0]
	scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript)
	return scriptClass == txscript.StakeSubmissionTy
}
func isVoteTx(tx *wire.MsgTx) bool {
	if len(tx.TxOut) < 3 {
		return false
	}
	txOut := tx.TxOut[2]
	scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript)
	return scriptClass == txscript.StakeGenTy
}
func isRevocationTx(tx *wire.MsgTx) bool {
	if len(tx.TxOut) == 0 {
		return false
	}
	txOut := tx.TxOut[0]
	scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript)
	return scriptClass == txscript.StakeRevocationTy
}