func TestFullBlocks(t *testing.T) {
	tests, err := fullblocktests.Generate(false)
	if err != nil {
		t.Fatalf("failed to generate tests: %v", err)
	}
	chain, teardownFunc, err := chainSetup("fullblocktest",
		&chaincfg.RegressionNetParams)
	if err != nil {
		t.Errorf("Failed to setup chain instance: %v", err)
		return
	}
	defer teardownFunc()
	testAcceptedBlock := func(item fullblocktests.AcceptedBlock) {
		blockHeight := item.Height
		block := provautil.NewBlock(item.Block)
		block.SetHeight(blockHeight)
		t.Logf("Testing block %s (hash %s, height %d)",
			item.Name, block.Hash(), blockHeight)
		isMainChain, isOrphan, err := chain.ProcessBlock(block,
			blockchain.BFNone)
		if err != nil {
			t.Fatalf("block %q (hash %s, height %d) should "+
				"have been accepted: %v", item.Name,
				block.Hash(), blockHeight, err)
		}
		if isMainChain != item.IsMainChain {
			t.Fatalf("block %q (hash %s, height %d) unexpected main "+
				"chain flag -- got %v, want %v", item.Name,
				block.Hash(), blockHeight, isMainChain,
				item.IsMainChain)
		}
		if isOrphan != item.IsOrphan {
			t.Fatalf("block %q (hash %s, height %d) unexpected "+
				"orphan flag -- got %v, want %v", item.Name,
				block.Hash(), blockHeight, isOrphan,
				item.IsOrphan)
		}
		if chain.ThreadTips()[provautil.RootThread].String() != item.ThreadTips[provautil.RootThread].String() {
			t.Fatalf("block %q (hash %s, height %d) should "+
				"have threadTips %v, got %v", item.Name, block.Hash(),
				blockHeight, item.ThreadTips[provautil.RootThread], chain.ThreadTips()[provautil.RootThread])
		}
		if chain.TotalSupply() != item.TotalSupply {
			t.Fatalf("block %q (hash %s, height %d) should "+
				"have totalSupply %v, got %v", item.Name, block.Hash(),
				blockHeight, item.TotalSupply, chain.TotalSupply())
		}
		if item.IsMainChain && !item.AdminKeySets[btcec.RootKeySet].Equal(chain.AdminKeySets()[btcec.RootKeySet]) {
			t.Fatalf("block %q (hash %s, height %d) should "+
				"have ROOT KEYS %v, got %v", item.Name, block.Hash(),
				blockHeight, item.AdminKeySets[btcec.RootKeySet].ToStringArray(),
				chain.AdminKeySets()[btcec.RootKeySet].ToStringArray())
		}
		provisionKeySet := btcec.PublicKeySet(item.AdminKeySets[btcec.ProvisionKeySet])
		if item.IsMainChain && !provisionKeySet.Equal(chain.AdminKeySets()[btcec.ProvisionKeySet]) {
			t.Fatalf("block %q (hash %s, height %d) should "+
				"have PROVISION KEYS %v, got %v", item.Name, block.Hash(),
				blockHeight, provisionKeySet.ToStringArray(),
				chain.AdminKeySets()[btcec.ProvisionKeySet].ToStringArray())
		}
		if item.IsMainChain && !item.AdminKeySets[btcec.ValidateKeySet].Equal(chain.AdminKeySets()[btcec.ValidateKeySet]) {
			t.Fatalf("block %q (hash %s, height %d) should "+
				"have VALIDATE KEYS %x, got %x", item.Name, block.Hash(),
				blockHeight, item.AdminKeySets[btcec.ValidateKeySet],
				chain.AdminKeySets()[btcec.ValidateKeySet])
		}
		if item.IsMainChain && !item.AdminKeySets[btcec.IssueKeySet].Equal(chain.AdminKeySets()[btcec.IssueKeySet]) {
			t.Fatalf("block %q (hash %s, height %d) should "+
				"have ISSUE KEYS %v, got %v", item.Name, block.Hash(),
				blockHeight, item.AdminKeySets[btcec.IssueKeySet].ToStringArray(),
				chain.AdminKeySets()[btcec.IssueKeySet].ToStringArray())
		}
		if item.IsMainChain && !item.ASPKeyIdMap.Equal(chain.KeyIDs()) {
			t.Fatalf("block %q (hash %s, height %d) should "+
				"have keyID %x, got %v", item.Name, block.Hash(),
				blockHeight, item.ASPKeyIdMap, chain.KeyIDs())
		}
	}
	testRejectedBlock := func(item fullblocktests.RejectedBlock) {
		blockHeight := item.Height
		block := provautil.NewBlock(item.Block)
		block.SetHeight(blockHeight)
		t.Logf("Testing block %s (hash %s, height %d)",
			item.Name, block.Hash(), blockHeight)
		_, _, err := chain.ProcessBlock(block, blockchain.BFNone)
		if err == nil {
			t.Fatalf("block %q (hash %s, height %d) should not "+
				"have been accepted", item.Name, block.Hash(),
				blockHeight)
		}
		rerr, ok := err.(blockchain.RuleError)
		if !ok {
			t.Fatalf("block %q (hash %s, height %d) returned "+
				"unexpected error type -- got %T, want "+
				"blockchain.RuleError", item.Name, block.Hash(),
				blockHeight, err)
		}
		if rerr.ErrorCode != item.RejectCode {
			t.Fatalf("block %q (hash %s, height %d) does not have "+
				"expected reject code -- got %v %v, want %v",
				item.Name, block.Hash(), blockHeight,
				rerr.ErrorCode, rerr, item.RejectCode)
		}
	}
	testRejectedNonCanonicalBlock := func(item fullblocktests.RejectedNonCanonicalBlock) {
		headerLen := len(item.RawBlock)
		if headerLen > 80 {
			headerLen = 80
		}
		blockHash := chainhash.DoubleHashH(item.RawBlock[0:headerLen])
		blockHeight := item.Height
		t.Logf("Testing block %s (hash %s, height %d)", item.Name,
			blockHash, blockHeight)
		var msgBlock wire.MsgBlock
		err := msgBlock.BtcDecode(bytes.NewReader(item.RawBlock), 0)
		if _, ok := err.(*wire.MessageError); !ok {
			t.Fatalf("block %q (hash %s, height %d) should have "+
				"failed to decode", item.Name, blockHash,
				blockHeight)
		}
	}
	testOrphanOrRejectedBlock := func(item fullblocktests.OrphanOrRejectedBlock) {
		blockHeight := item.Height
		block := provautil.NewBlock(item.Block)
		block.SetHeight(blockHeight)
		t.Logf("Testing block %s (hash %s, height %d)",
			item.Name, block.Hash(), blockHeight)
		_, isOrphan, err := chain.ProcessBlock(block, blockchain.BFNone)
		if err != nil {
			if _, ok := err.(blockchain.RuleError); !ok {
				t.Fatalf("block %q (hash %s, height %d) "+
					"returned unexpected error type -- "+
					"got %T, want blockchain.RuleError",
					item.Name, block.Hash(), blockHeight,
					err)
			}
		}
		if !isOrphan {
			t.Fatalf("block %q (hash %s, height %d) was accepted, "+
				"but is not considered an orphan", item.Name,
				block.Hash(), blockHeight)
		}
	}
	testExpectedTip := func(item fullblocktests.ExpectedTip) {
		blockHeight := item.Height
		block := provautil.NewBlock(item.Block)
		block.SetHeight(blockHeight)
		t.Logf("Testing tip for block %s (hash %s, height %d)",
			item.Name, block.Hash(), blockHeight)
		best := chain.BestSnapshot()
		if *best.Hash != item.Block.BlockHash() ||
			best.Height != blockHeight {
			t.Fatalf("block %q (hash %s, height %d) should be "+
				"the current tip -- got (hash %s, height %d)",
				item.Name, block.Hash(), blockHeight, best.Hash,
				best.Height)
		}
	}
	for testNum, test := range tests {
		for itemNum, item := range test {
			switch item := item.(type) {
			case fullblocktests.AcceptedBlock:
				testAcceptedBlock(item)
			case fullblocktests.RejectedBlock:
				testRejectedBlock(item)
			case fullblocktests.RejectedNonCanonicalBlock:
				testRejectedNonCanonicalBlock(item)
			case fullblocktests.OrphanOrRejectedBlock:
				testOrphanOrRejectedBlock(item)
			case fullblocktests.ExpectedTip:
				testExpectedTip(item)
			default:
				t.Fatalf("test #%d, item #%d is not one of "+
					"the supported test instance types -- "+
					"got type: %T", testNum, itemNum, item)
			}
		}
	}
}
TestFullBlocks ensures all tests generated by the fullblocktests package
have the expected result when processed via ProcessBlock.
package blockchain_test
import (
	"bytes"
	"github.com/bitgo/prova/blockchain"
	"github.com/bitgo/prova/blockchain/fullblocktests"
	"github.com/bitgo/prova/btcec"
	"github.com/bitgo/prova/chaincfg"
	"github.com/bitgo/prova/chaincfg/chainhash"
	"github.com/bitgo/prova/provautil"
	"github.com/bitgo/prova/wire"
	"testing"
)