func (g *testGenerator) assertTipBlockSize(expected int) {
	serializeSize := g.tip.SerializeSize()
	if serializeSize != expected {
		panic(fmt.Sprintf("block size of block %q (height %d) is %d "+
			"instead of expected %d", g.tipName, g.tipHeight,
			serializeSize, expected))
	}
}
assertTipBlockSize panics if the if the current tip block associated with the
generator does not have the specified size when serialized.
package fullblocktests
import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"runtime"
	"time"
	"github.com/soteria-dag/soterd/blockchain"
	"github.com/soteria-dag/soterd/soterec"
	"github.com/soteria-dag/soterd/chaincfg"
	"github.com/soteria-dag/soterd/chaincfg/chainhash"
	"github.com/soteria-dag/soterd/txscript"
	"github.com/soteria-dag/soterd/wire"
	"github.com/soteria-dag/soterd/soterutil"
)
const (
	maxBlockSigOps       = 20000
	maxBlockSize         = 1000000
	minCoinbaseScriptLen = 2
	maxCoinbaseScriptLen = 100
	medianTimeBlocks     = 11
	maxScriptElementSize = 520
	numLargeReorgBlocks = 1088
)
var (
	opTrueScript = []byte{txscript.OP_TRUE}
	lowFee = soterutil.Amount(1)
)
type TestInstance interface {
	FullBlockTestInstance()
}
type AcceptedBlock struct {
	Name        string
	Block       *wire.MsgBlock
	Height      int32
	IsMainChain bool
	IsOrphan    bool
}
var _ TestInstance = AcceptedBlock{}
func (b AcceptedBlock) FullBlockTestInstance() {}
type RejectedBlock struct {
	Name       string
	Block      *wire.MsgBlock
	Height     int32
	RejectCode blockchain.ErrorCode
}
var _ TestInstance = RejectedBlock{}
func (b RejectedBlock) FullBlockTestInstance() {}
type OrphanOrRejectedBlock struct {
	Name   string
	Block  *wire.MsgBlock
	Height int32
}
var _ TestInstance = OrphanOrRejectedBlock{}
func (b OrphanOrRejectedBlock) FullBlockTestInstance() {}
type ExpectedTip struct {
	Name   string
	Block  *wire.MsgBlock
	Height int32
}
var _ TestInstance = ExpectedTip{}
func (b ExpectedTip) FullBlockTestInstance() {}
type RejectedNonCanonicalBlock struct {
	Name     string
	RawBlock []byte
	Height   int32
}
func (b RejectedNonCanonicalBlock) FullBlockTestInstance() {}
type spendableOut struct {
	prevOut wire.OutPoint
	amount  soterutil.Amount
}
func makeSpendableOutForTx(tx *wire.MsgTx, txOutIndex uint32) spendableOut {
	return spendableOut{
		prevOut: wire.OutPoint{
			Hash:  tx.TxHash(),
			Index: txOutIndex,
		},
		amount: soterutil.Amount(tx.TxOut[txOutIndex].Value),
	}
}
func makeSpendableOut(block *wire.MsgBlock, txIndex, txOutIndex uint32) spendableOut {
	return makeSpendableOutForTx(block.Transactions[txIndex], txOutIndex)
}
type testGenerator struct {
	params       *chaincfg.Params
	tip          *wire.MsgBlock
	tipName      string
	tipHeight    int32
	blocks       map[chainhash.Hash]*wire.MsgBlock
	blocksByName map[string]*wire.MsgBlock
	blockHeights map[string]int32
	spendableOuts     []spendableOut
	prevCollectedHash chainhash.Hash
	privKey *soterec.PrivateKey
}
func makeTestGenerator(params *chaincfg.Params) (testGenerator, error) {
	privKey, _ := soterec.PrivKeyFromBytes(soterec.S256(), []byte{0x01})
	genesis := params.GenesisBlock
	genesisHash := genesis.BlockHash()
	return testGenerator{
		params:       params,
		blocks:       map[chainhash.Hash]*wire.MsgBlock{genesisHash: genesis},
		blocksByName: map[string]*wire.MsgBlock{"genesis": genesis},
		blockHeights: map[string]int32{"genesis": 0},
		tip:          genesis,
		tipName:      "genesis",
		tipHeight:    0,
		privKey:      privKey,
	}, nil
}
func payToScriptHashScript(redeemScript []byte) []byte {
	redeemScriptHash := soterutil.Hash160(redeemScript)
	script, err := txscript.NewScriptBuilder().
		AddOp(txscript.OP_HASH160).AddData(redeemScriptHash).
		AddOp(txscript.OP_EQUAL).Script()
	if err != nil {
		panic(err)
	}
	return script
}
func pushDataScript(items ...[]byte) []byte {
	builder := txscript.NewScriptBuilder()
	for _, item := range items {
		builder.AddData(item)
	}
	script, err := builder.Script()
	if err != nil {
		panic(err)
	}
	return script
}
func standardCoinbaseScript(blockHeight int32, extraNonce uint64) ([]byte, error) {
	return txscript.NewScriptBuilder().AddInt64(int64(blockHeight)).
		AddInt64(int64(extraNonce)).Script()
}
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)
	binary.LittleEndian.PutUint64(data[0:8], rand)
	return opReturnScript(data)
}
func (g *testGenerator) createCoinbaseTx(blockHeight int32) *wire.MsgTx {
	extraNonce := uint64(0)
	coinbaseScript, err := standardCoinbaseScript(blockHeight, extraNonce)
	if err != nil {
		panic(err)
	}
	tx := wire.NewMsgTx(1)
	tx.AddTxIn(&wire.TxIn{
		PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
			wire.MaxPrevOutIndex),
		Sequence:        wire.MaxTxInSequenceNum,
		SignatureScript: coinbaseScript,
	})
	tx.AddTxOut(&wire.TxOut{
		Value:    blockchain.CalcBlockSubsidy(blockHeight, g.params),
		PkScript: opTrueScript,
	})
	return tx
}
func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash {
	if len(txns) == 0 {
		return chainhash.Hash{}
	}
	utilTxns := make([]*soterutil.Tx, 0, len(txns))
	for _, tx := range txns {
		utilTxns = append(utilTxns, soterutil.NewTx(tx))
	}
	merkles := blockchain.BuildMerkleTreeStore(utilTxns, false)
	return *merkles[len(merkles)-1]
}
func solveBlock(header *wire.BlockHeader) bool {
	type sbResult struct {
		found bool
		nonce uint32
	}
	targetDifficulty := blockchain.CompactToBig(header.Bits)
	quit := make(chan bool)
	results := make(chan sbResult)
	solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) {
		for i := startNonce; i >= startNonce && i <= stopNonce; i++ {
			select {
			case <-quit:
				return
			default:
				hdr.Nonce = i
				hash := hdr.BlockHash()
				if blockchain.HashToBig(&hash).Cmp(
					targetDifficulty) <= 0 {
					results <- sbResult{true, i}
					return
				}
			}
		}
		results <- sbResult{false, 0}
	}
	startNonce := uint32(1)
	stopNonce := uint32(math.MaxUint32)
	numCores := uint32(runtime.NumCPU())
	noncesPerCore := (stopNonce - startNonce) / numCores
	for i := uint32(0); i < numCores; i++ {
		rangeStart := startNonce + (noncesPerCore * i)
		rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1
		if i == numCores-1 {
			rangeStop = stopNonce
		}
		go solver(*header, rangeStart, rangeStop)
	}
	for i := uint32(0); i < numCores; i++ {
		result := <-results
		if result.found {
			close(quit)
			header.Nonce = result.nonce
			return true
		}
	}
	return false
}
func additionalCoinbase(amount soterutil.Amount) func(*wire.MsgBlock) {
	return func(b *wire.MsgBlock) {
		b.Transactions[0].TxOut[0].Value += int64(amount)
	}
}
func additionalSpendFee(fee soterutil.Amount) func(*wire.MsgBlock) {
	return func(b *wire.MsgBlock) {
		if int64(fee) > b.Transactions[1].TxOut[0].Value {
			panic(fmt.Sprintf("additionalSpendFee: fee of %d "+
				"exceeds available spend transaction value",
				fee))
		}
		b.Transactions[1].TxOut[0].Value -= int64(fee)
	}
}
func replaceSpendScript(pkScript []byte) func(*wire.MsgBlock) {
	return func(b *wire.MsgBlock) {
		b.Transactions[1].TxOut[0].PkScript = pkScript
	}
}
func replaceCoinbaseSigScript(script []byte) func(*wire.MsgBlock) {
	return func(b *wire.MsgBlock) {
		b.Transactions[0].TxIn[0].SignatureScript = script
	}
}
func additionalTx(tx *wire.MsgTx) func(*wire.MsgBlock) {
	return func(b *wire.MsgBlock) {
		b.AddTransaction(tx)
	}
}
func createSpendTx(spend *spendableOut, fee soterutil.Amount) *wire.MsgTx {
	spendTx := wire.NewMsgTx(1)
	spendTx.AddTxIn(&wire.TxIn{
		PreviousOutPoint: spend.prevOut,
		Sequence:         wire.MaxTxInSequenceNum,
		SignatureScript:  nil,
	})
	spendTx.AddTxOut(wire.NewTxOut(int64(spend.amount-fee),
		opTrueScript))
	spendTx.AddTxOut(wire.NewTxOut(0, uniqueOpReturnScript()))
	return spendTx
}
func createSpendTxForTx(tx *wire.MsgTx, fee soterutil.Amount) *wire.MsgTx {
	spend := makeSpendableOutForTx(tx, 0)
	return createSpendTx(&spend, fee)
}
func (g *testGenerator) nextBlock(blockName string, spend *spendableOut, mungers ...func(*wire.MsgBlock)) *wire.MsgBlock {
	nextHeight := g.tipHeight + 1
	coinbaseTx := g.createCoinbaseTx(nextHeight)
	txns := []*wire.MsgTx{coinbaseTx}
	if spend != nil {
		fee := soterutil.Amount(1)
		coinbaseTx.TxOut[0].Value += int64(fee)
		txns = append(txns, createSpendTx(spend, fee))
	}
	var ts time.Time
	if nextHeight == 1 {
		ts = time.Unix(time.Now().Unix(), 0)
	} else {
		ts = g.tip.Header.Timestamp.Add(time.Second)
	}
	block := wire.MsgBlock{
		Header: wire.BlockHeader{
			Version:    1,
			PrevBlock:  g.tip.BlockHash(),
			MerkleRoot: calcMerkleRoot(txns),
			Bits:       g.params.PowLimitBits,
			Timestamp:  ts,
			Nonce:      0,  
		},
		Transactions: txns,
	}
	curMerkleRoot := block.Header.MerkleRoot
	curNonce := block.Header.Nonce
	for _, f := range mungers {
		f(&block)
	}
	if block.Header.MerkleRoot == curMerkleRoot {
		block.Header.MerkleRoot = calcMerkleRoot(block.Transactions)
	}
	if block.Header.Nonce == curNonce && !solveBlock(&block.Header) {
		panic(fmt.Sprintf("Unable to solve block at height %d",
			nextHeight))
	}
	blockHash := block.BlockHash()
	g.blocks[blockHash] = &block
	g.blocksByName[blockName] = &block
	g.blockHeights[blockName] = nextHeight
	g.tip = &block
	g.tipName = blockName
	g.tipHeight = nextHeight
	return &block
}
func (g *testGenerator) updateBlockState(oldBlockName string, oldBlockHash chainhash.Hash, newBlockName string, newBlock *wire.MsgBlock) {
	blockHeight := g.blockHeights[oldBlockName]
	delete(g.blocks, oldBlockHash)
	delete(g.blocksByName, oldBlockName)
	delete(g.blockHeights, oldBlockName)
	newBlockHash := newBlock.BlockHash()
	g.blocks[newBlockHash] = newBlock
	g.blocksByName[newBlockName] = newBlock
	g.blockHeights[newBlockName] = blockHeight
}
func (g *testGenerator) setTip(blockName string) {
	g.tip = g.blocksByName[blockName]
	g.tipName = blockName
	g.tipHeight = g.blockHeights[blockName]
}
func (g *testGenerator) oldestCoinbaseOut() spendableOut {
	op := g.spendableOuts[0]
	g.spendableOuts = g.spendableOuts[1:]
	return op
}
func (g *testGenerator) saveTipCoinbaseOut() {
	g.spendableOuts = append(g.spendableOuts, makeSpendableOut(g.tip, 0, 0))
	g.prevCollectedHash = g.tip.BlockHash()
}
func (g *testGenerator) saveSpendableCoinbaseOuts() {
	curTipName := g.tipName
	defer g.setTip(curTipName)
	var collectBlocks []*wire.MsgBlock
	for b := g.tip; b != nil; b = g.blocks[b.Header.PrevBlock] {
		if b.BlockHash() == g.prevCollectedHash {
			break
		}
		collectBlocks = append(collectBlocks, b)
	}
	for i := range collectBlocks {
		g.tip = collectBlocks[len(collectBlocks)-1-i]
		g.saveTipCoinbaseOut()
	}
}
func nonCanonicalVarInt(val uint32) []byte {
	var rv [9]byte
	rv[0] = 0xff
	binary.LittleEndian.PutUint64(rv[1:], uint64(val))
	return rv[:]
}
func encodeNonCanonicalBlock(b *wire.MsgBlock) []byte {
	var buf bytes.Buffer
	b.Header.SotoEncode(&buf, 0, wire.BaseEncoding)
	buf.Write(nonCanonicalVarInt(uint32(len(b.Transactions))))
	for _, tx := range b.Transactions {
		tx.SotoEncode(&buf, 0, wire.BaseEncoding)
	}
	return buf.Bytes()
}
func cloneBlock(b *wire.MsgBlock) wire.MsgBlock {
	var blockCopy wire.MsgBlock
	blockCopy.Header = b.Header
	for _, tx := range b.Transactions {
		blockCopy.AddTransaction(tx.Copy())
	}
	return blockCopy
}
func repeatOpcode(opcode uint8, numRepeats int) []byte {
	return bytes.Repeat([]byte{opcode}, numRepeats)
}
func assertScriptSigOpsCount(script []byte, expected int) {
	numSigOps := txscript.GetSigOpCount(script)
	if numSigOps != expected {
		_, file, line, _ := runtime.Caller(1)
		panic(fmt.Sprintf("assertion failed at %s:%d: generated number "+
			"of sigops for script is %d instead of expected %d",
			file, line, numSigOps, expected))
	}
}
func countBlockSigOps(block *wire.MsgBlock) int {
	totalSigOps := 0
	for _, tx := range block.Transactions {
		for _, txIn := range tx.TxIn {
			numSigOps := txscript.GetSigOpCount(txIn.SignatureScript)
			totalSigOps += numSigOps
		}
		for _, txOut := range tx.TxOut {
			numSigOps := txscript.GetSigOpCount(txOut.PkScript)
			totalSigOps += numSigOps
		}
	}
	return totalSigOps
}
func (g *testGenerator) assertTipBlockSigOpsCount(expected int) {
	numSigOps := countBlockSigOps(g.tip)
	if numSigOps != expected {
		panic(fmt.Sprintf("generated number of sigops for block %q "+
			"(height %d) is %d instead of expected %d", g.tipName,
			g.tipHeight, numSigOps, expected))
	}
}