func (g *testGenerator) assertTipBlockSize(expected int) {
	serializeSize := g.last.SerializeSize()
	if serializeSize != expected {
		panic(fmt.Sprintf("block size of block %q (height %d) is %d "+
			"instead of expected %d", g.lastName, g.lastHeight,
			serializeSize, expected))
	}
}
assertTipBlockSize panics if the if the current tip block associated with the
generator does not have the specified size when serialized.
package dagfullblocktests
import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"github.com/soteria-dag/soterd/blockdag"
	"math"
	"runtime"
	"time"
	"github.com/soteria-dag/soterd/chaincfg"
	"github.com/soteria-dag/soterd/chaincfg/chainhash"
	"github.com/soteria-dag/soterd/mining/cuckoo"
	"github.com/soteria-dag/soterd/soterec"
	"github.com/soteria-dag/soterd/soterutil"
	"github.com/soteria-dag/soterd/txscript"
	"github.com/soteria-dag/soterd/wire"
)
const (
	maxBlockSigOps       = 20000
	maxBlockSize         = 1000520
	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 blockdag.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
}
type ExpectedTips struct {  
	tips []*ExpectedTip
}
var _ TestInstance = ExpectedTips{}
func (b ExpectedTips) 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
	solver       cuckoo.Solver
	last         *wire.MsgBlock
	lastName     string
	lastHeight   int32
	tipNames     []string
	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()
	var solver cuckoo.Solver
	orig := cuckoo.NewCuckooSolver(cuckoo.DefaultEdgeBits, cuckoo.DefaultSipHash)
	solver = orig
	return testGenerator{
		params:       params,
		solver:       solver,
		blocks:       map[chainhash.Hash]*wire.MsgBlock{genesisHash: genesis},
		blocksByName: map[string]*wire.MsgBlock{"genesis": genesis},
		blockHeights: map[string]int32{"genesis": 0},
		last:         genesis,
		lastName:     "genesis",
		lastHeight:   0,
		tipNames:     []string{"genesis"},
		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:    blockdag.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 := blockdag.BuildMerkleTreeStore(utilTxns, false)
	return *merkles[len(merkles)-1]
}
func solveBlock(solver cuckoo.Solver, block *wire.MsgBlock) bool {
	var targetDifficulty = blockdag.CompactToBig(block.Header.Bits)
	var startNonce uint32
	if block.Header.Nonce == 0 {
		startNonce = 1
	} else {
		startNonce = block.Header.Nonce
	}
	for i := startNonce; i <= uint32(math.MaxUint32); i++ {
		block.Header.Nonce = i
		encoded, err := block.Header.Bytes()
		if err != nil {
			continue
		}
		solutions, err := solver.Solve(encoded, block.Header.Nonce)
		if err != nil {
			continue
		}
		if len(solutions) == 0 {
			continue
		}
		for _, cycleNonces := range solutions {
			err := solver.Verify(encoded, block.Header.Nonce, cycleNonces)
			if err != nil {
				continue
			}
			proofDifficulty := blockdag.ProofDifficulty(cycleNonces)
			if proofDifficulty.Cmp(targetDifficulty) >= 0 {
				block.Verification.CycleNonces = cycleNonces
				block.Verification.Size = int32(len(cycleNonces))
				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, parentNames []string, mungers ...func(*wire.MsgBlock)) *wire.MsgBlock {
	if parentNames == nil {
		parentNames = g.tipNames
	}
	maxHeight := g.lastHeight
	for _, tip := range parentNames {
		tipHeight := g.blockHeights[tip]
		if tipHeight > maxHeight {
			maxHeight = tipHeight
		}
	}
	nextHeight := maxHeight + 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.last.Header.Timestamp.Add(time.Second)
	}
	var tipHashes = make([]*chainhash.Hash, len(parentNames))
	var parents = make([]*wire.MsgBlock, len(parentNames))
	for i, parentName := range parentNames {
		parents[i] = g.blocksByName[parentName]
	}
	for i, tip := range parents {
		hash := tip.BlockHash()
		tipHashes[i] = &hash
	}
	prevHash := blockdag.GenerateTipsHash(tipHashes)
	parentInfo := make([]*wire.Parent, len(tipHashes))
	for i, parent := range parents {
		parentInfo[i] = &wire.Parent{
			Hash: parent.BlockHash(),
		}
	}
	block := wire.MsgBlock{
		Header: wire.BlockHeader{
			Version:    1,
			PrevBlock:  *prevHash,
			MerkleRoot: calcMerkleRoot(txns),
			Bits:       g.params.PowLimitBits,
			Timestamp:  ts,
			Nonce:      0,  
		},
		Parents: wire.ParentSubHeader {
			Version:  1,
			Size:     int32(len(parentInfo)),
			Parents:  parentInfo,
		},
		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(g.solver, &block) {
		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.last = &block
	g.lastName = blockName
	g.lastHeight = nextHeight
	newTips := make([]string,0)
	for _, tip := range g.tipNames {
		includeTip := true
		for _, parent := range parentNames {
			if parent == tip {
				includeTip = false
				break
			}
		}
		if includeTip {
			newTips = append(newTips, tip)
		}
	}
	newTips = append(newTips, blockName)
	g.tipNames = newTips
	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) setLast(blockName string) {
	g.last = g.blocksByName[blockName]
	g.lastName = blockName
	g.lastHeight = g.blockHeights[blockName]
}
func (g *testGenerator) setTips(tipNames []string) {
	g.tipNames = tipNames
}
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.last, 0, 0))
	g.prevCollectedHash = g.last.BlockHash()
}
func (g *testGenerator) saveSpendableCoinbaseOuts() {
	curTipName := g.lastName
	defer g.setLast(curTipName)
	tipBlocks := make([]*wire.MsgBlock, len(g.tipNames))
	for i, tipName := range g.tipNames {
		tipBlocks[i] = g.blocksByName[tipName]
	}
	var collectBlocks []*wire.MsgBlock
	for b := tipBlocks; b != nil; {  
		foundHash := false
		nextBlocks := make([]*wire.MsgBlock,0)
		for _, block := range b {
			if block.BlockHash() == g.prevCollectedHash {
				foundHash = true
			} else {
				collectBlocks = append(collectBlocks, block)
				for _, parent := range block.Parents.Parents {
					parentBlock := g.blocks[parent.Hash]
					nextBlocks = append(nextBlocks, parentBlock)
				}
			}
		}
		if foundHash {
			break
		}
		if len(nextBlocks) > 0 {
			b = nextBlocks
		} else {
			b = nil
		}
	}
	for i := range collectBlocks {
		g.last = 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.last)
	if numSigOps != expected {
		panic(fmt.Sprintf("generated number of sigops for block %q "+
			"(height %d) is %d instead of expected %d", g.lastName,
			g.lastHeight, numSigOps, expected))
	}
}