func checkTransactionsEqual(transA, transB []*wire.MsgTx) error {
	if len(transA) != len(transB) {
		return fmt.Errorf("block transactions count mismatch: %v != %v", len(transA), len(transB))
	}
	txIndexA := make(map[string]int)
	txIndexB := make(map[string]int)
	for i, tx := range transA {
		txHash := tx.TxHash().String()
		txIndexA[txHash] = i
	}
	for i, tx := range transB {
		txHash := tx.TxHash().String()
		txIndexB[txHash] = i
	}
	for hash, _ := range txIndexA {
		_, ok := txIndexB[hash]
		if !ok {
			return fmt.Errorf("block transaction from A missing from or different in B: %v", hash)
		}
	}
	for hash, _ := range txIndexB {
		_, ok := txIndexA[hash]
		if !ok {
			return fmt.Errorf("block transaction from B missing from or different in A: %v", hash)
		}
	}
	return nil
}
checkTransactionsEqual returns an error if the transactions aren't identical.
Since the wire.MsgTx.TxHash() method serializes the object into a hash, we implicitly check for
equivalence by testing for missing transactions in transA and transB
package rpctest
import (
	"bytes"
	"fmt"
	"github.com/soteria-dag/soterd/soterutil"
	"github.com/wcharczuk/go-chart"
	"io/ioutil"
	"reflect"
	"time"
	"github.com/soteria-dag/soterd/chaincfg/chainhash"
	"github.com/soteria-dag/soterd/rpcclient"
	"github.com/soteria-dag/soterd/soterjson"
	"github.com/soteria-dag/soterd/wire"
)
type JoinType uint8
const (
	Blocks JoinType = iota
	Mempools
)
func JoinNodes(nodes []*Harness, joinType JoinType) error {
	switch joinType {
	case Blocks:
		return syncBlocks(nodes)
	case Mempools:
		return syncMempools(nodes)
	}
	return nil
}
func absInt32(x int32) int32 {
	if x < 0 {
		return -x
	} else {
		return x
	}
}
func colorPicker (v int) string {
	color := chart.GetAlternateColor(v)
	return fmt.Sprintf("#%x%x%x", []byte{color.R}, []byte{color.G}, []byte{color.B})
}
func stylePicker (v bool) string {
	if (v) {
		return "filled"
	} 	
	return "filled, dashed"
}
func keys(m map[int32]int) []int32 {
	k := make([]int32, 0)
	for v := range m {
		k = append(k, v)
	}
	return k
}
func meanInterval(values []int32) float64 {
	if len(values) < 2 {
		return float64(0)
	}
	diffs := make([]int32, 0)
	for i := 0; i < (len(values) - 1); i++ {
		d := absInt32(values[i + 1] - values[i])
		diffs = append(diffs, d)
	}
	mean := float64(sumInt32(diffs)) / float64(len(diffs))
	return mean
}
func save(bytes []byte, pattern string) (string, error) {
	t, err := ioutil.TempFile("", pattern)
	if err != nil {
		return "", err
	}
	_, err = t.Write(bytes)
	if err != nil {
		return "", err
	}
	err = t.Close()
	if err != nil {
		return "", err
	}
	return t.Name(), nil
}
func sumInt32(x []int32) int32 {
	total := int32(0)
	for _, v := range x {
		total += v
	}
	return total
}
func CountDAGBlocks(node *Harness) (int, float64, error) {
	count := 0
	mpbHeights := make(map[int32]int)
	tips, err := node.Node.GetDAGTips()
	if err != nil {
		return count, meanInterval(keys(mpbHeights)), err
	}
	for height := int32(0); height <= tips.MaxHeight; height++ {
		hashes, err := node.Node.GetBlockHash(int64(height))
		if err != nil {
			return count, meanInterval(keys(mpbHeights)), err
		}
		for _, hash := range hashes {
			block, err := node.Node.GetBlock(hash)
			if err != nil {
				return count, meanInterval(keys(mpbHeights)), err
			}
			if (*block).Parents.Size > 1 {
				count++
				mpbHeights[height] = 1
			}
		}
	}
	return count, meanInterval(keys(mpbHeights)), nil
}
func DumpDAG(i int, node *Harness) error {
	tips, err := node.Node.GetDAGTips()
	if err != nil {
		return err
	}
	for height := int32(0); height <= tips.MaxHeight; height++ {
		hashes, err := node.Node.GetBlockHash(int64(height))
		if err != nil {
			return err
		}
		for _, hash := range hashes {
			block, err := node.Node.GetBlock(hash)
			if err != nil {
				return err
			}
			fmt.Printf("miner %v\theight %v\tblock %v\tparents %v\n", i, height, hash, block.Parents.ParentHashes())
		}
	}
	return nil
}
func RenderDagsDot(nodes []*Harness, rankdir string) ([]byte, error) {
	var dot bytes.Buffer
	smallHashLen := 7
	blockCreator := make(map[string]int)
	for i, n := range nodes {
		resp, err := n.Node.GetBlockMetrics()
		if err != nil {
			continue
		}
		for _, hash := range resp.BlkHashes {
			blockCreator[hash] = i
		}
	}
	node := nodes[0]
	tips, err := node.Node.GetDAGTips()
	if err != nil {
		return dot.Bytes(), err
	}
	dag := make([][]*wire.MsgBlock, 0)
	blockIndex := make(map[string]*wire.MsgBlock)
	for height := int32(0); height <= tips.MaxHeight; height++ {
		blocks := make([]*wire.MsgBlock, 0)
		hashes, err := node.Node.GetBlockHash(int64(height))
		if err != nil {
			return dot.Bytes(), err
		}
		for _, hash := range hashes {
			block, err := node.Node.GetBlock(hash)
			if err != nil {
				return dot.Bytes(), err
			}
			blockIndex[block.BlockHash().String()] = block
			blocks = append(blocks, block)
		}
		dag = append(dag, blocks)
	}
	dagcoloring, err := node.Node.GetDAGColoring()
	if err != nil {
		return dot.Bytes(), err
	}
	blockcoloring := make(map[string]bool)
	for _, dagNode := range dagcoloring {
		hash := dagNode.Hash
		coloring := dagNode.IsBlue
		blockcoloring[hash] = coloring
	}
	graphIndex := make(map[string]int)
	n := 0
	_, err = fmt.Fprintln(&dot, "digraph dag {")
	if err != nil {
		return dot.Bytes(), err
	}
	_, err = fmt.Fprintln(&dot, "ordering=out;")
	if err != nil {
		return dot.Bytes(), err
	}
	_, err = fmt.Fprintf(&dot, "rankdir=\"%s\";", rankdir)
	if err != nil {
		return dot.Bytes(), err
	}
	for height, blocks := range dag {
		for _, block := range blocks {
			hash := block.BlockHash().String()
			smallHashIndex := len(hash) - smallHashLen
			graphIndex[hash] = n
			dagcoloring := blockcoloring[hash]
			style := stylePicker(dagcoloring)
			creator, exists := blockCreator[hash]
			var err error
			if exists {
				color := colorPicker(creator)
				_, err = fmt.Fprintf(&dot, "n%d [label=\"%s\", tooltip=\"node %d height %d hash %s\", fillcolor=\"%s\", style=\"%s\"];\n",
					n, hash[smallHashIndex:], creator, height, hash, color, style)
			} else {
				_, err = fmt.Fprintf(&dot, "n%d [label=\"%s\", tooltip=\"height %d hash %s\", style=\"%s\"];\n",
					n, hash[smallHashIndex:], height, hash, style)
			}
			if err != nil {
				return dot.Bytes(), err
			}
			n++
		}
	}
	for _, blocks := range dag {
		for _, block := range blocks {
			blockN := graphIndex[block.BlockHash().String()]
			for _, parent := range block.Parents.Parents {
				parentN := graphIndex[parent.Hash.String()]
				_, err := fmt.Fprintf(&dot, "n%d -> n%d;\n", blockN, parentN)
				if err != nil {
					return dot.Bytes(), err
				}
			}
		}
	}
	dot.WriteString("}")
	return dot.Bytes(), nil
}
func SaveDagHTML(r *Harness) (string, error) {
	dot, err := r.Node.RenderDag()
	if err != nil {
		return "", err
	}
	svg, err := soterutil.DotToSvg([]byte(dot.Dot))
	if err != nil {
		return "", err
	}
	svgEmbed, err := soterutil.StripSvgXmlDecl(svg)
	if err != nil {
		return "", err
	}
	h, err := soterutil.RenderSvgHTML(svgEmbed, "dag")
	if err != nil {
		return "", err
	}
	return save(h, "dag_*.html")
}
func checkHeaderEqual(headerA, headerB wire.BlockHeader) error {
	if headerA.Version != headerB.Version {
		return fmt.Errorf("block header Version mismatch: %v != %v", headerA.Version, headerB.Version)
	}
	if !headerA.PrevBlock.IsEqual(&headerB.PrevBlock) {
		return fmt.Errorf("block header PrevBlock mismatch: %v != %v", headerA.PrevBlock, headerB.PrevBlock)
	}
	if !headerA.MerkleRoot.IsEqual(&headerB.MerkleRoot) {
		return fmt.Errorf("block header MerkleRoot mismatch: %v != %v", headerA.MerkleRoot, headerB.MerkleRoot)
	}
	if !headerA.Timestamp.Equal(headerB.Timestamp) {
		return fmt.Errorf("block header Timestamp mismatch: %v != %v", headerA.Timestamp, headerB.Timestamp)
	}
	if headerA.Bits != headerB.Bits {
		return fmt.Errorf("block header Bits mismatch: %v != %v", headerA.Bits, headerB.Bits)
	}
	if headerA.Nonce != headerB.Nonce {
		return fmt.Errorf("block header Nonce mismatch: %v != %v", headerA.Bits, headerB.Bits)
	}
	return nil
}
func assertParentIdentical(parentA, parentB *wire.Parent) error {
	if !parentA.Hash.IsEqual(&parentB.Hash) {
		return fmt.Errorf("block parent Hash mismatch: %v != %v", parentA.Hash, parentB.Hash)
	}
	if parentA.Data != parentB.Data {
		return fmt.Errorf("block parent Data mismatch: %v != %v", parentA.Data, parentB.Data)
	}
	return nil
}
func checkParentSubHeaderEqual(parentsA, parentsB wire.ParentSubHeader) error {
	if parentsA.Version != parentsB.Version {
		return fmt.Errorf("block parent sub-header Version mismatch: %v != %v", parentsA.Version, parentsB.Version)
	}
	if parentsA.Size != parentsB.Size {
		return fmt.Errorf("block parent sub-header Size (claimed number of parents) mismatch: %v != %v", parentsA.Size, parentsB.Size)
	}
	if len(parentsA.Parents) != len(parentsB.Parents) {
		return fmt.Errorf("block parent sub-header actual number of parents mismatch: %v != %v", len(parentsA.Parents), len(parentsB.Parents))
	}
	parentIndexA := make(map[string]int)
	parentIndexB := make(map[string]int)
	for i, hash := range parentsA.ParentHashes() {
		parentIndexA[hash.String()] = i
	}
	for i, hash := range parentsB.ParentHashes() {
		parentIndexB[hash.String()] = i
	}
	for hash, indexA := range parentIndexA {
		indexB, ok := parentIndexB[hash]
		if !ok {
			return fmt.Errorf("block parent sub-header B missing A parent: %v", hash)
		}
		err := assertParentIdentical(parentsA.Parents[indexA], parentsB.Parents[indexB])
		if err != nil {
			return err
		}
	}
	for hash, _ := range parentIndexB {
		_, ok := parentIndexA[hash]
		if !ok {
			return fmt.Errorf("block parent sub-header A missing B parent: %v", hash)
		}
	}
	return nil
}