func (s *simulator) calcDevSubsidy(fullSubsidy dcrutil.Amount, blockHeight int32, numVotes uint16) dcrutil.Amount {
	devProportion := dcrutil.Amount(s.params.BlockTaxProportion)
	totalProportions := dcrutil.Amount(s.params.TotalSubsidyProportions())
	devSubsidy := (fullSubsidy * devProportion) / totalProportions
	if int64(blockHeight) < s.params.StakeValidationHeight {
		return devSubsidy
	}
	ticketsPerBlock := dcrutil.Amount(s.params.TicketsPerBlock)
	return (devSubsidy * dcrutil.Amount(numVotes)) / ticketsPerBlock
}
calcDevSubsidy returns the dev org subsidy portion from a given full subsidy.
package main
import (
	"bytes"
	"encoding/binary"
	"fmt"
	"html/template"
	"math"
	"math/big"
	"math/rand"
	"os"
	"os/exec"
	"runtime"
	"sort"
	"strconv"
	"time"
	"github.com/davecgh/dcrstakesim/internal/tickettreap"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/chaincfg/v2"
	"github.com/decred/dcrd/dcrutil/v2"
)
var (
	hash256prngSeedConst = []byte{0x24, 0x3f, 0x6a, 0x88, 0x85, 0xa3, 0x08,
		0xd3}
)
func init() {
	rand.Seed(time.Now().Unix())
}
func openBrowser(url string) bool {
	var cmds [][]string
	if exe := os.Getenv("BROWSER"); exe != "" {
		cmds = append(cmds, []string{exe})
	}
	switch runtime.GOOS {
	case "darwin":
		cmds = append(cmds, []string{"/usr/bin/open"})
	case "windows":
		cmds = append(cmds, []string{"cmd", "/c", "start"})
	default:
		cmds = append(cmds, []string{"xdg-open"})
	}
	cmds = append(cmds, []string{"chrome"}, []string{"google-chrome"},
		[]string{"firefox"})
	for _, args := range cmds {
		cmd := exec.Command(args[0], append(args[1:], url)...)
		if cmd.Start() == nil {
			return true
		}
	}
	return false
}
type hash256prng struct {
	seed       chainhash.Hash  
	idx        uint64          
	cachedHash chainhash.Hash  
	hashOffset int             
}
func newHash256PRNG(seed []byte) *hash256prng {
	seedHash := chainhash.HashFunc(append(seed, hash256prngSeedConst...))
	return &hash256prng{
		seed:       seedHash,
		idx:        0,
		cachedHash: seedHash,
	}
}
func (hp *hash256prng) Hash256Rand() uint32 {
	offset := hp.hashOffset * 4
	r := binary.BigEndian.Uint32(hp.cachedHash[offset : offset+4])
	hp.hashOffset++
	if hp.hashOffset > 7 {
		data := make([]byte, len(hp.seed)+4)
		copy(data, hp.seed[:])
		binary.BigEndian.PutUint32(data[len(hp.seed):], uint32(hp.idx))
		hp.cachedHash = chainhash.HashH(data)
		hp.idx++
		hp.hashOffset = 0
	}
	if hp.idx > math.MaxUint32 {
		hp.seed = chainhash.HashH(hp.seed[:])
		hp.cachedHash = hp.seed
		hp.idx = 0
	}
	return r
}
func (hp *hash256prng) uniformRandom(upperBound uint32) uint32 {
	if upperBound < 2 {
		return 0
	}
	min := ((math.MaxUint32 - (upperBound * 2)) + 1) % upperBound
	if upperBound > 0x80000000 {
		min = 1 + ^upperBound
	}
	r := hp.Hash256Rand()
	for r < min {
		r = hp.Hash256Rand()
	}
	return r % upperBound
}
type stakeTicket struct {
	hash        chainhash.Hash
	blockHeight int32
	price       dcrutil.Amount
	winHeight   int32
}
func newStakeTicket(hash *chainhash.Hash, purchaseHeight int32, price int64) *stakeTicket {
	return &stakeTicket{
		hash:        *hash,
		blockHeight: purchaseHeight,
		price:       dcrutil.Amount(price),
		winHeight:   -1,
	}
}
func stakeTicketHash(purchaseHeight int32, ticketNum uint8) chainhash.Hash {
	var b [8]byte
	binary.LittleEndian.PutUint32(b[:], uint32(purchaseHeight))
	binary.LittleEndian.PutUint32(b[4:], uint32(ticketNum))
	return chainhash.HashH(b[:])
}
type uint32Sorter []uint32
func (s uint32Sorter) Len() int {
	return len(s)
}
func (s uint32Sorter) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s uint32Sorter) Less(i, j int) bool {
	return s[i] < s[j]
}
func winningTickets(voteBlock *blockNode, liveTickets *tickettreap.Immutable, numVotes uint16) ([]*stakeTicket, error) {
	numLiveTickets := uint32(liveTickets.Len())
	if numLiveTickets > math.MaxUint32 {
		return nil, fmt.Errorf("live ticket pool has %d tickets which "+
			"is more than the max allowed of %d", numLiveTickets,
			math.MaxUint32)
	}
	if uint32(numVotes) > numLiveTickets {
		return nil, fmt.Errorf("live ticket pool has %d tickets, "+
			"while %d are needed to vote", numLiveTickets, numVotes)
	}
	prng := newHash256PRNG(voteBlock.header)
	winningOffsets := make([]uint32, 0, numVotes)
	usedOffsets := make(map[uint32]struct{})
	for uint16(len(winningOffsets)) < numVotes {
		ticketIndex := prng.uniformRandom(numLiveTickets)
		if _, exists := usedOffsets[ticketIndex]; !exists {
			usedOffsets[ticketIndex] = struct{}{}
			winningOffsets = append(winningOffsets, ticketIndex)
		}
	}
	sort.Sort(uint32Sorter(winningOffsets))
	winners := make([]*stakeTicket, 0, numVotes)
	var poolIdx, winnerIdx uint32
	liveTickets.ForEach(func(key tickettreap.Key, val *tickettreap.Value) bool {
		if poolIdx == winningOffsets[winnerIdx] {
			ticketHash := (*chainhash.Hash)(&key)
			ticket := newStakeTicket(ticketHash, val.PurchaseHeight,
				val.PurchasePrice)
			ticket.winHeight = voteBlock.height + 1
			winners = append(winners, ticket)
			if uint16(len(winners)) == numVotes {
				return false
			}
			winnerIdx++
		}
		poolIdx++
		return true
	})
	return winners, nil
}
type blockNode struct {
	parent *blockNode
	height int32
	header []byte
	next   *blockNode
	ticketPrice     int64           
	regularSubsidy  dcrutil.Amount  
	poolSize        uint32          
	totalSupply     dcrutil.Amount  
	spendableSupply dcrutil.Amount  
	stakedCoins dcrutil.Amount
	numVoters      uint16
	ticketsAdded   []*stakeTicket
	ticketsVoted   []*stakeTicket
	ticketsRevoked []*stakeTicket
}
func newBlockNode(parent *blockNode, ticketsAdded, ticketsVoted, ticketsRevoked []*stakeTicket) *blockNode {
	node := &blockNode{
		parent:         parent,
		ticketsAdded:   ticketsAdded,
		ticketsVoted:   ticketsVoted,
		ticketsRevoked: ticketsRevoked,
	}
	if parent != nil {
		parent.next = node
		node.height = parent.height + 1
	}
	return node
}
type simulator struct {
	params  *chaincfg.Params
	verbose bool
	root *blockNode
	tip  *blockNode
	immatureTickets  []*stakeTicket
	liveTickets      *tickettreap.Immutable
	expireHeights    map[int32][]*stakeTicket
	expiredTickets   []*stakeTicket
	missedTickets    []*stakeTicket
	unrevokedTickets []*stakeTicket
	wonTickets       []*stakeTicket
	maturingSupply map[int32]dcrutil.Amount
	nextTicketPriceFunc func() int64
	demandFunc          func(int32, int64) float64
}
func (s *simulator) calcFullSubsidy(blockHeight int32) dcrutil.Amount {
	iterations := int64(blockHeight) / s.params.SubsidyReductionInterval
	subsidy := s.params.BaseSubsidy
	for i := int64(0); i < iterations; i++ {
		subsidy *= s.params.MulSubsidy
		subsidy /= s.params.DivSubsidy
	}
	return dcrutil.Amount(subsidy)
}
func (s *simulator) calcPoWSubsidy(fullSubsidy dcrutil.Amount, blockHeight int32, numVotes uint16) dcrutil.Amount {
	powProportion := dcrutil.Amount(s.params.WorkRewardProportion)
	totalProportions := dcrutil.Amount(s.params.TotalSubsidyProportions())
	powSubsidy := (fullSubsidy * powProportion) / totalProportions
	if int64(blockHeight) < s.params.StakeValidationHeight {
		return powSubsidy
	}
	ticketsPerBlock := dcrutil.Amount(s.params.TicketsPerBlock)
	return (powSubsidy * dcrutil.Amount(numVotes)) / ticketsPerBlock
}
func (s *simulator) calcPoSSubsidy(heightVotedOn int32) dcrutil.Amount {
	if int64(heightVotedOn+1) < s.params.StakeValidationHeight {
		return 0
	}
	fullSubsidy := s.calcFullSubsidy(heightVotedOn)
	posProportion := dcrutil.Amount(s.params.StakeRewardProportion)
	totalProportions := dcrutil.Amount(s.params.TotalSubsidyProportions())
	return (fullSubsidy * posProportion) / totalProportions
}