// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/decred/dcrd/blockchain/stake/v5"
	"github.com/decred/dcrd/blockchain/standalone/v2"
	"github.com/decred/dcrd/blockchain/v5/chaingen"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/chaincfg/v3"
	"github.com/decred/dcrd/database/v3"
	"github.com/decred/dcrd/dcrec"
	"github.com/decred/dcrd/dcrec/secp256k1/v4"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/gcs/v4/blockcf2"
	"github.com/decred/dcrd/lru"
	"github.com/decred/dcrd/txscript/v4"
	"github.com/decred/dcrd/txscript/v4/sign"
	"github.com/decred/dcrd/txscript/v4/stdaddr"
	"github.com/decred/dcrd/txscript/v4/stdscript"
	"github.com/decred/dcrd/wire"
)

var (
	// These keys come from chaincfg/regnetparams.go
	privKey  = hexToBytes("68ab7efdac0eb99b1edf83b23374cc7a9c8d0a4183a2627afc8ea0437b20589e")
	privKey2 = hexToBytes("2527f13f61024c9b9f4b30186f16e0b0af35b08c54ed2ed67def863b447ea11b")
)

// TestTreasuryValueTypeDebits ensures the IsDebit() method of
// treasuryValueType works as expected.
func TestTreasuryValueTypeDebits(t *testing.T) {
	tests := []struct {
		name      string
		typ       treasuryValueType
		wantDebit bool
	}{{
		name:      "treasuryValueTBase",
		typ:       treasuryValueTBase,
		wantDebit: false,
	}, {
		name:      "treasuryValueTAdd",
		typ:       treasuryValueTAdd,
		wantDebit: false,
	}, {
		name:      "treasuryValueFee",
		typ:       treasuryValueFee,
		wantDebit: true,
	}, {
		name:      "treasuryValueTSpend",
		typ:       treasuryValueTSpend,
		wantDebit: true,
	}}

	for _, test := range tests {
		gotDebit := test.typ.IsDebit()
		if gotDebit != test.wantDebit {
			t.Fatalf("mismatched IsDebit result for %s -- got %v, "+
				"want %v", test.name, gotDebit, test.wantDebit)
		}
	}
}

// TestTreasuryStateSerialization ensures serializing and deserializing treasury
// states works as expected, including the possible error conditions.
func TestTreasuryStateSerialization(t *testing.T) {
	tests := []struct {
		name       string
		state      *treasuryState
		serialized []byte
		encodeErr  error
		decodeErr  error
	}{{
		name: "equal",
		state: &treasuryState{
			balance: 100,
			values: []treasuryValue{
				{treasuryValueTBase, 1},
				{treasuryValueTAdd, 2},
				{treasuryValueTAdd, 3},
				{treasuryValueTSpend, -3},
				{treasuryValueFee, -2},
			},
		},
		serialized: hexToBytes("640501010202020304030302"),
	}, {
		name: "negative balance",
		state: &treasuryState{
			balance: -100,
			values: []treasuryValue{
				{treasuryValueTBase, 1},
				{treasuryValueTAdd, 2},
				{treasuryValueTAdd, 3},
				{treasuryValueTSpend, -3},
				{treasuryValueFee, -2},
			},
		},
		encodeErr: errDbTreasury(""),
		decodeErr: errDeserialize(""),
	}, {
		name:       "empty data",
		state:      nil,
		serialized: hexToBytes(""),
		decodeErr:  errDeserialize(""),
	}, {
		name:       "no data after balance",
		state:      nil,
		serialized: hexToBytes("64"),
		decodeErr:  errDeserialize(""),
	}, {
		name:       "no data after num values",
		state:      nil,
		serialized: hexToBytes("6401"),
		decodeErr:  errDeserialize(""),
	}, {
		name:       "no data after first value",
		state:      nil,
		serialized: hexToBytes("640201"),
		decodeErr:  errDeserialize(""),
	}, {
		name: "trying to serialize incorrect negative tbase",
		state: &treasuryState{
			values: []treasuryValue{{typ: treasuryValueTBase, amount: -1}},
		},
		encodeErr:  errDbTreasury(""),
		serialized: nil,
		decodeErr:  errDeserialize(""),
	}, {
		name: "trying to serialize incorrect negative tadd",
		state: &treasuryState{
			values: []treasuryValue{{typ: treasuryValueTAdd, amount: -1}},
		},
		encodeErr:  errDbTreasury(""),
		serialized: nil,
		decodeErr:  errDeserialize(""),
	}, {
		name: "trying to serialize incorrect positive tspend",
		state: &treasuryState{
			values: []treasuryValue{{typ: treasuryValueTSpend, amount: 1}},
		},
		encodeErr:  errDbTreasury(""),
		serialized: nil,
		decodeErr:  errDeserialize(""),
	}, {
		name: "trying to serialize incorrect positive fee",
		state: &treasuryState{
			values: []treasuryValue{{typ: treasuryValueFee, amount: 1}},
		},
		encodeErr:  errDbTreasury(""),
		serialized: nil,
		decodeErr:  errDeserialize(""),
	}, {
		name: "serialize zero amounts for all value types",
		state: &treasuryState{
			balance: 100,
			values: []treasuryValue{
				{typ: treasuryValueTBase, amount: 0},
				{typ: treasuryValueTAdd, amount: 0},
				{typ: treasuryValueTSpend, amount: 0},
				{typ: treasuryValueFee, amount: 0},
			},
		},
		serialized: hexToBytes("64040100020004000300"),
	}}

	for _, test := range tests {
		// Ensure the serialized bytes are deserialized back to the expected
		// treasury state.
		gotState, err := deserializeTreasuryState(test.serialized)
		if !errors.Is(err, test.decodeErr) {
			t.Errorf("%s: mismatched err -- got %v, want %v", test.name, err,
				test.decodeErr)
			continue
		}
		if test.decodeErr == nil && !reflect.DeepEqual(gotState, test.state) {
			t.Errorf("%s: mismatched states\ngot %+v\nwant %+v", test.name,
				gotState, test.state)
			continue
		}

		// Skip serialization tests for nil states.
		if test.state == nil {
			continue
		}

		// Ensure the treasury state serializes to the expected value.
		gotSerialized, err := serializeTreasuryState(*test.state)
		if !errors.Is(err, test.encodeErr) {
			t.Errorf("%s: mismatched err -- got %v, want %v", test.name, err,
				test.encodeErr)
		}
		if !bytes.Equal(gotSerialized, test.serialized) {
			t.Errorf("%s: did not get expected bytes - got %x, want %x",
				test.name, gotSerialized, test.serialized)
			continue
		}
	}
}

// TestTreasuryDatabase tests treasury database functionality.
func TestTreasuryDatabase(t *testing.T) {
	// Create a new database to store treasury state.
	dbPath := t.TempDir()
	net := chaincfg.RegNetParams().Net
	testDb, err := database.Create(testDbType, dbPath, net)
	if err != nil {
		t.Fatalf("error creating treasury db: %v", err)
	}
	defer testDb.Close()

	// Add bucket.
	err = testDb.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(treasuryBucketName)
		return err
	})
	if err != nil {
		t.Fatal(err)
	}

	// Write maxTreasuryState records out.
	maxTreasuryState := uint64(1024)
	for i := uint64(0); i < maxTreasuryState; i++ {
		// Create synthetic treasury state
		ts := treasuryState{
			balance: int64(i),
			values: []treasuryValue{
				{typ: treasuryValueTBase, amount: int64(i)},
				{typ: treasuryValueTSpend, amount: -1 - int64(i)},
			},
		}

		// Create hash of counter.
		b := make([]byte, 16)
		binary.LittleEndian.PutUint64(b[0:], i)
		hash := chainhash.HashH(b)

		err = testDb.Update(func(dbTx database.Tx) error {
			return dbPutTreasuryBalance(dbTx, hash, ts)
		})
		if err != nil {
			t.Fatalf("%v", err)
		}
	}

	// Pull records back out.
	for i := uint64(0); i < maxTreasuryState; i++ {
		// Create synthetic treasury state
		ts := treasuryState{
			balance: int64(i),
			values: []treasuryValue{
				{typ: treasuryValueTBase, amount: int64(i)},
				{typ: treasuryValueTSpend, amount: -1 - int64(i)},
			},
		}

		// Create hash of counter.
		b := make([]byte, 16)
		binary.LittleEndian.PutUint64(b[0:], i)
		hash := chainhash.HashH(b)

		var tsr *treasuryState
		err = testDb.View(func(dbTx database.Tx) error {
			tsr, err = dbFetchTreasuryBalance(dbTx, hash)
			return err
		})
		if err != nil {
			t.Fatalf("%v", err)
		}

		if !reflect.DeepEqual(ts, *tsr) {
			t.Fatalf("not same treasury state got %v wanted %v", ts, *tsr)
		}
	}
}

// TestTspendDatabase tests tspend database functionality including
// serialization and deserialization.
func TestTSpendDatabase(t *testing.T) {
	// Create a new database to store treasury state.
	dbPath := t.TempDir()
	net := chaincfg.RegNetParams().Net
	testDb, err := database.Create(testDbType, dbPath, net)
	if err != nil {
		t.Fatalf("error creating tspend db: %v", err)
	}
	defer testDb.Close()

	// Add bucket.
	err = testDb.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(treasuryTSpendBucketName)
		return err
	})
	if err != nil {
		t.Fatal(err)
	}

	// Write maxTSpendState records out.
	maxTSpendState := uint64(8)
	txHash := chainhash.Hash{}
	for i := uint64(0); i < maxTSpendState; i++ {
		// Create hash of counter.
		b := make([]byte, 16)
		binary.LittleEndian.PutUint64(b[0:], i)
		blockHash := chainhash.HashH(b)

		err = testDb.Update(func(dbTx database.Tx) error {
			return dbUpdateTSpend(dbTx, txHash, blockHash)
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
	}

	// Pull records back out.
	var hashes []chainhash.Hash
	err = testDb.View(func(dbTx database.Tx) error {
		hashes, err = dbFetchTSpend(dbTx, txHash)
		return err
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	for i := uint64(0); i < maxTSpendState; i++ {
		// Create hash of counter.
		b := make([]byte, 16)
		binary.LittleEndian.PutUint64(b[0:], i)
		hash := chainhash.HashH(b)
		if !hash.IsEqual(&hashes[i]) {
			t.Fatalf("not same tspend hash got %v wanted %v", hashes[i], hash)
		}
	}
}

// appendHashes takes a slice of chainhash and votebits and appends it all
// together for a TV script.
func appendHashes(tspendHashes []*chainhash.Hash, votes []stake.TreasuryVoteT) []byte {
	if len(tspendHashes) != len(votes) {
		panic(fmt.Sprintf("assert appendHashes %v != %v", len(tspendHashes),
			len(votes)))
	}
	blob := make([]byte, 0, 2+chainhash.HashSize*7+7)
	blob = append(blob, 'T', 'V')
	for k, v := range tspendHashes {
		blob = append(blob, v[:]...)
		blob = append(blob, byte(votes[k]))
	}
	return blob
}

// addTSpendVotes return a munge function that votes according to voteBits.
func addTSpendVotes(t *testing.T, tspendHashes []*chainhash.Hash, votes []stake.TreasuryVoteT, nrVotes uint16, skipAssert bool) func(*wire.MsgBlock) {
	if len(tspendHashes) != len(votes) {
		panic(fmt.Sprintf("assert addTSpendVotes %v != %v", len(tspendHashes),
			len(votes)))
	}
	return func(b *wire.MsgBlock) {
		// Find SSGEN and append votes.
		for k, v := range b.STransactions {
			if !stake.IsSSGen(v) {
				continue
			}
			if len(v.TxOut) != 3 {
				t.Fatalf("expected SSGEN.TxOut len 3 got %v", len(v.TxOut))
			}

			// Only allow provided number of votes.
			if uint16(k) > nrVotes {
				break
			}

			// Append vote:
			// OP_RETURN OP_DATA <TV> <tspend hash> <vote bits>
			vote := appendHashes(tspendHashes, votes)
			s, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).
				AddData(vote).Script()
			if err != nil {
				t.Fatal(err)
			}
			b.STransactions[k].TxOut = append(b.STransactions[k].TxOut,
				&wire.TxOut{
					PkScript: s,
				})
			// Only TxVersionTreasury supports optional votes.
			b.STransactions[k].Version = wire.TxVersionTreasury

			// See if we should skip asserts. This is used for
			// munging votes and bits.
			if skipAssert {
				continue
			}

			// Assert vote insertion worked.
			_, err = stake.GetSSGenTreasuryVotes(s)
			if err != nil {
				t.Fatalf("expected treasury vote: %v", err)
			}

			// Assert this remains a valid SSGEN.
			err = stake.CheckSSGen(b.STransactions[k])
			if err != nil {
				t.Fatalf("expected SSGen: %v", err)
			}
		}
	}
}

const devsub = 5000000000

// standardTreasurybaseOpReturn creates a standard OP_RETURN output to insert
// into coinbase. This function autogenerates the extranonce. The OP_RETURN
// pushes 12 bytes.
func standardTreasurybaseOpReturn(height uint32) []byte {
	extraNonce, err := wire.RandomUint64()
	if err != nil {
		panic(err)
	}

	enData := make([]byte, 12)
	binary.LittleEndian.PutUint32(enData[0:4], height)
	binary.LittleEndian.PutUint64(enData[4:12], extraNonce)
	extraNonceScript, err := stdscript.ProvablyPruneableScriptV0(enData)
	if err != nil {
		panic(err)
	}

	return extraNonceScript
}

// replaceCoinbase is a munge function that takes the coinbase and removes the
// treasury payout and moves it to a TADD treasury agenda based version. It
// also bumps all STransactions indexes by 1 since we require treasurybase to
// be the 0th entry in the stake tree.
func replaceCoinbase(b *wire.MsgBlock) {
	// Find coinbase tx and remove dev subsidy.
	coinbaseTx := b.Transactions[0]
	devSubsidy := coinbaseTx.TxOut[0].Value
	coinbaseTx.TxOut = coinbaseTx.TxOut[1:]
	coinbaseTx.Version = wire.TxVersionTreasury
	coinbaseTx.TxIn[0].ValueIn -= devSubsidy

	// Create treasuryBase and insert it at position 0 of the stake
	// tree.
	oldSTransactions := b.STransactions
	b.STransactions = make([]*wire.MsgTx, len(b.STransactions)+1)
	for k, v := range oldSTransactions {
		b.STransactions[k+1] = v
	}
	treasurybaseTx := wire.NewMsgTx()
	treasurybaseTx.Version = wire.TxVersionTreasury
	treasurybaseTx.AddTxIn(&wire.TxIn{
		PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
			wire.MaxPrevOutIndex, wire.TxTreeRegular),
		Sequence:    wire.MaxTxInSequenceNum,
		BlockHeight: wire.NullBlockHeight,
		BlockIndex:  wire.NullBlockIndex,
		// Required 0 len SignatureScript
	})
	treasurybaseTx.TxIn[0].ValueIn = devSubsidy
	treasurybaseTx.AddTxOut(&wire.TxOut{
		Value:    devSubsidy,
		PkScript: []byte{txscript.OP_TADD},
	})
	// Encoded height.
	treasurybaseTx.AddTxOut(&wire.TxOut{
		Value:    0,
		PkScript: standardTreasurybaseOpReturn(b.Header.Height),
	})
	retTx := dcrutil.NewTx(treasurybaseTx)
	retTx.SetTree(wire.TxTreeStake)
	b.STransactions[0] = retTx.MsgTx()

	// Sanity check treasury base.
	if !standalone.IsTreasuryBase(retTx.MsgTx()) {
		panic(stake.CheckTreasuryBase(retTx.MsgTx()))
	}
}

func TestTSpendVoteCount(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	startTip := g.TipName()

	// ---------------------------------------------------------------------
	// Create TSpend in "mempool"
	// ---------------------------------------------------------------------

	nextBlockHeight := g.Tip().Header.Height + 1
	tspendAmount := devsub
	tspendFee := 100
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, end, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{{
		Amount: dcrutil.Amount(tspendAmount - tspendFee),
	}}, dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()

	// ---------------------------------------------------------------------
	// Try to insert TSPEND while not on a TVI
	//
	//   ... -> bva19
	//                  \-> bnottvi0
	// ---------------------------------------------------------------------

	// Assert we are not on a TVI and generate block. This should fail.
	if standalone.IsTreasuryVoteInterval(uint64(nextBlockHeight), tvi) {
		t.Fatalf("expected !TVI %v", nextBlockHeight)
	}
	outs := g.OldestCoinbaseOuts()
	name := "bnottvi0"
	g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrNotTVI)

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	//                  \-> bnottvi0
	// ---------------------------------------------------------------------

	// Generate votes up to TVI. This is legal however they should NOT be
	// counted in the totals since they are outside of the voting window.
	g.SetTip(startTip)
	voteCount := params.TicketsPerBlock
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Add TSpend on first block of window. This should fail with not
	// enough votes.
	//
	//   ... -> bpretvi1
	//         \-> btvinotenough0
	// ---------------------------------------------------------------------

	// Assert we are on a TVI and generate block. This should fail.
	startTip = g.TipName()
	if standalone.IsTreasuryVoteInterval(uint64(g.Tip().Header.Height), tvi) {
		t.Fatalf("expected !TVI %v", g.Tip().Header.Height)
	}
	name = "btvinotenough0"
	g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrNotEnoughTSpendVotes)

	// ---------------------------------------------------------------------
	// Generate 1 TVI of No votes and add TSpend,
	//
	//   ... -> bpretvi1 -> btvi0 -> ... -> btvi3
	// ---------------------------------------------------------------------
	g.SetTip(startTip)
	for i := uint64(0); i < tvi; i++ {
		name := fmt.Sprintf("btvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteNo},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Assert we are on a TVI and generate block. This should fail.
	startTip = g.TipName()
	if standalone.IsTreasuryVoteInterval(uint64(g.Tip().Header.Height), tvi) {
		t.Fatalf("expected !TVI %v", g.Tip().Header.Height)
	}
	name = "btvinotenough1"
	g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrNotEnoughTSpendVotes)

	// ---------------------------------------------------------------------
	// Generate two more TVI of no votes.
	//
	//   ... -> btvinotenough0 -> btvi4 -> ... -> btvi7
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	for i := uint64(0); i < tvi*2; i++ {
		name := fmt.Sprintf("btvi%v", tvi+i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteNo},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Assert we are on a TVI and generate block. This should fail with No
	// vote (TSpend should not have been submitted).
	startTip = g.TipName()
	if standalone.IsTreasuryVoteInterval(uint64(g.Tip().Header.Height), tvi) {
		t.Fatalf("expected !TVI %v", g.Tip().Header.Height)
	}
	name = "btvienough0"
	g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrNotEnoughTSpendVotes)

	// Assert we have the correct number of votes and voting window.
	tv, err := g.chain.tSpendCountVotes(g.chain.bestChain.Tip(),
		dcrutil.NewTx(tspend))
	if err != nil {
		t.Fatal(err)
	}

	if start != tv.start {
		t.Fatalf("invalid start block got %v wanted %v", tv.start, start)
	}
	if end != tv.end {
		t.Fatalf("invalid end block got %v wanted %v", tv.end, end)
	}

	expectedYesVotes := 0 // We voted a bunch of times outside the window
	expectedNoVotes := tvi * mul * uint64(params.TicketsPerBlock)
	if expectedYesVotes != tv.yes {
		t.Fatalf("invalid yes votes got %v wanted %v",
			expectedYesVotes, tv.yes)
	}
	if expectedNoVotes != uint64(tv.no) {
		t.Fatalf("invalid no votes got %v wanted %v",
			expectedNoVotes, tv.no)
	}

	// ---------------------------------------------------------------------
	// Generate one more TVI and append expired TSpend.
	//
	//   ... -> bposttvi0 ... -> bposttvi3 ->
	//                                     \-> bexpired0
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	for i := uint64(0); i < tvi; i++ {
		name := fmt.Sprintf("bposttvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Assert TSpend expired
	startTip = g.TipName()
	if standalone.IsTreasuryVoteInterval(uint64(g.Tip().Header.Height), tvi) {
		t.Fatalf("expected !TVI %v", g.Tip().Header.Height)
	}
	name = "bexpired0"
	g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrExpiredTx)

	// ---------------------------------------------------------------------
	// Create TSpend in "mempool"
	//
	// Test corner of quorum-1 vote and exact quorum yes vote.
	// ---------------------------------------------------------------------

	// Use exact hight to validate that tspend starts on next tvi.
	expiry = standalone.CalcTSpendExpiry(int64(g.Tip().Header.Height), tvi, mul)
	start, _, err = standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	// While here test that start is next tvi while on tvi.
	if g.Tip().Header.Height+uint32(tvi) != start {
		t.Fatalf("expected to see exactly next tvi got %v wanted %v",
			start, g.Tip().Header.Height+uint32(tvi))
	}

	tspend = g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{
		{
			Amount: dcrutil.Amount(tspendAmount - tspendFee),
		},
	},
		dcrutil.Amount(tspendFee), expiry)
	tspendHash = tspend.TxHash()

	// Fast forward to next tvi and add no votes which should not count.
	g.SetTip(startTip)
	for i := uint64(0); i < tvi; i++ {
		name := fmt.Sprintf("bnovote%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteNo},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Hit quorum-1 yes votes.
	maxVotes := uint32(params.TicketsPerBlock) *
		(tv.end - tv.start)
	quorum := uint64(maxVotes) * params.TreasuryVoteQuorumMultiplier /
		params.TreasuryVoteQuorumDivisor
	totalVotes := uint16(quorum - 1)
	for i := uint64(0); i < tvi; i++ {
		name := fmt.Sprintf("byesvote%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				totalVotes, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()

		if totalVotes > params.TicketsPerBlock {
			totalVotes -= params.TicketsPerBlock
		} else {
			totalVotes = 0
		}
	}

	// Verify we are one vote shy of quorum
	startTip = g.TipName()
	if standalone.IsTreasuryVoteInterval(uint64(g.Tip().Header.Height), tvi) {
		t.Fatalf("expected !TVI %v", g.Tip().Header.Height)
	}
	name = "bquorum0"
	g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrNotEnoughTSpendVotes)

	// Count votes.
	tv, err = g.chain.tSpendCountVotes(g.chain.bestChain.Tip(),
		dcrutil.NewTx(tspend))
	if err != nil {
		t.Fatal(err)
	}
	if int(quorum-1) != tv.yes {
		t.Fatalf("unexpected yesVote count got %v wanted %v",
			tv.yes, quorum-1)
	}

	// Hit exact yes vote quorum
	g.SetTip(startTip)
	totalVotes = uint16(1)
	for i := uint64(0); i < tvi*2; i++ {
		name := fmt.Sprintf("byesvote%v", tvi+i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				totalVotes, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()

		if totalVotes > params.TicketsPerBlock {
			totalVotes -= params.TicketsPerBlock
		} else {
			totalVotes = 0
		}
	}

	// Count votes.
	tv, err = g.chain.tSpendCountVotes(g.chain.bestChain.Tip(),
		dcrutil.NewTx(tspend))
	if err != nil {
		t.Fatal(err)
	}
	if int(quorum) != tv.yes {
		t.Fatalf("unexpected yesVote count got %v wanted %v",
			tv.yes, quorum)
	}

	// Verify TSpend can be added exactly on quorum.
	if standalone.IsTreasuryVoteInterval(uint64(g.Tip().Header.Height), tvi) {
		t.Fatalf("expected !TVI %v", g.Tip().Header.Height)
	}
	name = "bquorum1"
	g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.AcceptTipBlock()
}

// getTreasuryState retrieves the treasury state for the provided hash.
func getTreasuryState(g *chaingenHarness, hash chainhash.Hash) (*treasuryState, error) {
	var tsr *treasuryState
	err := g.chain.db.View(func(dbTx database.Tx) error {
		var err error
		tsr, err = dbFetchTreasuryBalance(dbTx, hash)
		return err
	})
	return tsr, err
}

// TestTSpendEmptyTreasury tests that we can't generate a tspend that spends
// more funds than available in the treasury even when otherwise allowed by
// policy.
func TestTSpendEmptyTreasury(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// To ensure we can completely drain the treasury, increase the
	// bootstrap policy to allow a tspend with a _very_ high value.
	params.TreasuryExpenditureBootstrap = 21e6 * 1e8

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Create TSPEND in mempool for exact amount of treasury + 1 atom
	// ---------------------------------------------------------------------
	nextBlockHeight := g.Tip().Header.Height + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	tspendAmount := devsub*(tvi*mul-uint64(params.CoinbaseMaturity)+
		uint64(start-nextBlockHeight)) + 1 // One atom too many
	tspendFee := uint64(0)
	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{
		{
			Amount: dcrutil.Amount(tspendAmount - tspendFee),
		},
	},
		dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------

	// Generate votes up to TVI. This is legal however they should NOT be
	// counted in the totals since they are outside of the voting window.
	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Generate a TVI worth of rewards and try to spend more.
	//
	//   ... -> b0 ... -> b7
	//                 \-> btoomuch0
	// ---------------------------------------------------------------------

	voteCount := params.TicketsPerBlock
	for i := uint64(0); i < tvi*mul; i++ {
		name := fmt.Sprintf("b%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Assert treasury balance is 1 atom less than calculated amount.
	ts, err := getTreasuryState(g, g.Tip().BlockHash())
	if err != nil {
		t.Fatal(err)
	}
	if int64(tspendAmount-tspendFee)-ts.balance != 1 {
		t.Fatalf("Assert treasury balance error: got %v want %v",
			ts.balance, int64(tspendAmount-tspendFee)-ts.balance)
	}

	// Try spending 1 atom more than treasury balance.
	name := "btoomuch0"
	g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrInvalidExpenditure)
}

// TestExpendituresReorg tests that the correct treasury balance is tracked
// when reorgs remove tspends/tadds from the main chain. Test plan:
//
// - Approve and mine TSpend and TAdd
// - Reorg to chain without TSpend/Tadd
// - Mine until TSpend/TAdd would have been mature
// - Extend TSpend/TAdd chain and reorg back to it
func TestExpendituresReorg(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// Helper to check the treasury balance at tip.
	assertTipTreasuryBalance := func(wantBalance int64) {
		t.Helper()
		ts, err := getTreasuryState(g, g.Tip().BlockHash())
		if err != nil {
			t.Fatal(err)
		}
		if ts.balance != wantBalance {
			t.Fatalf("unexpected treasury balance. want=%d got %d",
				wantBalance, ts.balance)
		}
	}

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// tbaseBlocks will keep track of how many blocks with a treasury base
	// have been added to the chain.
	var tbaseBlocks int

	// ---------------------------------------------------------------------
	// Generate enough blocks to have spendable outputs during the reorg
	// stage of the test. Due to having lowered the coinbase maturity,
	// we'll need to manually generate and store these outputs until we
	// have enough to create two sidechains in the future.
	//
	//   ... -> bva19 -> bout0 -> .. boutn
	// ---------------------------------------------------------------------
	outs := g.OldestCoinbaseOuts()
	neededBlocks := (params.CoinbaseMaturity + 1) * 2
	neededOuts := neededBlocks * params.TicketsPerBlock
	oldOuts := make([][]chaingen.SpendableOut, 1)
	for i := uint32(0); i < uint32(neededOuts); i++ {
		name := fmt.Sprintf("bout%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
		lastOuts := &oldOuts[len(oldOuts)-1]
		*lastOuts = append(*lastOuts, outs[0])
		if len(*lastOuts) == int(params.TicketsPerBlock) && i < uint32(neededOuts-1) {
			oldOuts = append(oldOuts, make([]chaingen.SpendableOut, 0, int(params.TicketsPerBlock)))
		}
		tbaseBlocks++
	}

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------

	nextBlockHeight := g.Tip().Header.Height + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	// Generate up to TVI blocks.
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
		tbaseBlocks++
	}

	// Generate a TSpend for some amount.
	tspendAmount := int64(1e7)
	tspendFee := uint64(0)
	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{{
		Amount: dcrutil.Amount(uint64(tspendAmount) - tspendFee),
	}}, dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()

	// Generate a TAdd for some amount.
	taddAmount := int64(1701)
	taddFee := dcrutil.Amount(0)
	tadd := g.CreateTreasuryTAdd(&outs[0], dcrutil.Amount(taddAmount), taddFee)
	tadd.Version = wire.TxVersionTreasury

	// ---------------------------------------------------------------------
	// Generate enough blocks to approve the tspend.
	//
	// ... -> bpretvi1 -> bv0 -> ... -> bvn
	// ---------------------------------------------------------------------
	voteCount := params.TicketsPerBlock
	for i := uint64(0); i < tvi*mul; i++ {
		name := fmt.Sprintf("bv%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
		tbaseBlocks++
	}

	// Remember the block immediately before the TSpend is mined so we can
	// create sidechains.
	preTSpendBlock := g.TipName()

	// ---------------------------------------------------------------------
	// Generate a block that includes the tspend and tadd.
	//
	// ... -> bvn -> btspend
	// ---------------------------------------------------------------------
	g.NextBlock("btspend", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			b.AddSTransaction(tspend)
			b.AddSTransaction(tadd)
		})
	g.AcceptTipBlock()

	// Balance only reflects the tbase added so far. We add 1 to
	// tbaseBlocks to account for the `btspend` block.
	wantBalance := (int64(tbaseBlocks+1) - int64(params.CoinbaseMaturity)) * devsub
	assertTipTreasuryBalance(wantBalance)

	// ---------------------------------------------------------------------
	// Generate a sidechain that does _not_ include the TSpend and mine it
	// all the way to when the TSpend would have been mature and ensure the
	// treasury balance does _not_ reflect the reorged txs.
	//
	// ... -> bvn -> btspend
	//           \-> bnotxs0 -> ... -> bnotxsn
	// ---------------------------------------------------------------------
	g.SetTip(preTSpendBlock)
	blocksToTreasuryChange := uint64(params.CoinbaseMaturity + 1)
	for i := uint64(0); i < blocksToTreasuryChange; i++ {
		name := fmt.Sprintf("bnotxs%v", i)
		g.NextBlock(name, nil, oldOuts[0], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		switch i {
		case 0:
			// The first block creates a sidechain.
			g.AcceptedToSideChainWithExpectedTip("btspend")
		default:
			g.AcceptTipBlock()
		}
		oldOuts = oldOuts[1:]
		tbaseBlocks++
	}

	// Given we reorged to a side chain that does _not_ include the TSpend
	// and TAdd, we expect the current balance to have only tbases.
	wantBalance = (int64(tbaseBlocks) - int64(params.CoinbaseMaturity)) * devsub
	assertTipTreasuryBalance(wantBalance)

	// ---------------------------------------------------------------------
	// Extend the chain starting at btspend again, until it becomes the
	// main chain again.
	//
	// ... -> bvn -> btspend -> btspend0 -> ... btspendn
	//           \-> bnotxs0 -> ... -> bnotxsn
	// ---------------------------------------------------------------------
	tip := g.TipName()
	g.SetTip("btspend")
	for i := uint64(0); i < blocksToTreasuryChange; i++ {
		name := fmt.Sprintf("btspend%v", i)
		g.NextBlock(name, nil, oldOuts[0], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))

		switch {
		case i < blocksToTreasuryChange-1:
			// Only the last block changes tip.
			g.AcceptedToSideChainWithExpectedTip(tip)
		default:
			g.AcceptTipBlock()
		}
		oldOuts = oldOuts[1:]
	}

	// We mined one more block vs the previous chain.
	tbaseBlocks++

	// We reorged back to the chain that includes the mature TSpend/TAdd so
	// now ensure the treasury balance reflects their presence.
	wantBalance = (int64(tbaseBlocks)-int64(params.CoinbaseMaturity))*devsub -
		tspendAmount + taddAmount
	assertTipTreasuryBalance(wantBalance)
}

// TestSpendableTreasuryTxs tests that the outputs of mined TSpends and TAdds
// are actually spendable by transactions that can be mined.
//
// - Approve and mine TSpend and TAdd
// - Mine until TSpend/TAdd are mature
// - Create a tx that spends from the TSpend and TAdd change.
func TestSpendableTreasuryTxs(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------

	nextBlockHeight := g.Tip().Header.Height + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	// Generate up to TVI blocks.
	outs := g.OldestCoinbaseOuts()
	taddOut1 := outs[0] // Save this output for a tadd.
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Create P2PKH and P2SH scripts to test spendability of TSpend and
	// TAdd outputs. Each one is unique so we can do a cfilter test after
	// mining these txs.
	spendPrivKey := secp256k1.NewPrivateKey(new(secp256k1.ModNScalar).SetInt(1))
	pubKey := spendPrivKey.PubKey().SerializeCompressed()
	pubKeyHash := stdaddr.Hash160(pubKey)
	spendP2pkhAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(
		pubKeyHash, params)
	if err != nil {
		t.Fatal(err)
	}
	spendP2shScript := []byte{txscript.OP_NOP, txscript.OP_TRUE}
	spendP2shAddr, err := stdaddr.NewAddressScriptHashV0(spendP2shScript,
		params)
	if err != nil {
		t.Fatal(err)
	}

	addPrivKey := secp256k1.NewPrivateKey(new(secp256k1.ModNScalar).SetInt(2))
	pubKey = addPrivKey.PubKey().SerializeCompressed()
	pubKeyHash = stdaddr.Hash160(pubKey)
	addP2pkhAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(
		pubKeyHash, params)
	if err != nil {
		t.Fatal(err)
	}
	addP2shScript := []byte{txscript.OP_NOP, txscript.OP_NOP, txscript.OP_TRUE}
	addP2shAddr, err := stdaddr.NewAddressScriptHashV0(addP2shScript, params)
	if err != nil {
		t.Fatal(err)
	}

	// Generate a TSpend for some amount.
	tspendAmount := int64(1e7)
	tspendFee := uint64(0)
	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{
		{
			Amount:  dcrutil.Amount(uint64(tspendAmount) - tspendFee),
			Address: spendP2pkhAddr,
		},
		{
			Amount:  dcrutil.Amount(uint64(tspendAmount) - tspendFee),
			Address: spendP2shAddr,
		},
	},
		dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()

	// Generate a TAdd for some amount ensuring there's change and direct
	// the change to a P2PKH addr.
	taddAmount := int64(outs[0].Amount() / 2)
	taddChange := taddAmount
	taddFee := dcrutil.Amount(0)
	tadd1 := g.CreateTreasuryTAddChange(&taddOut1, dcrutil.Amount(taddAmount),
		taddFee, addP2pkhAddr)
	tadd1.Version = wire.TxVersionTreasury
	taddHash1 := tadd1.TxHash()

	// Generate a second TAdd that pays the change to a unique P2SH script.
	tadd2 := g.CreateTreasuryTAddChange(&outs[0], dcrutil.Amount(taddAmount),
		taddFee, addP2shAddr)
	tadd2.Version = wire.TxVersionTreasury
	taddHash2 := tadd2.TxHash()

	// ---------------------------------------------------------------------
	// Generate enough blocks to approve the tspend.
	//
	// ... -> bpretvi1 -> bv0 -> ... -> bvn
	// ---------------------------------------------------------------------
	voteCount := params.TicketsPerBlock
	for i := uint64(0); i < tvi*mul; i++ {
		name := fmt.Sprintf("bv%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Generate a block that includes the tspend and tadd.
	//
	// ... -> bvn -> btspend
	// ---------------------------------------------------------------------
	var tspendHeight, tspendIndex uint32
	g.NextBlock("btspend", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			tspendHeight = b.Header.Height
			tspendIndex = uint32(len(b.STransactions))
			b.AddSTransaction(tspend)
			b.AddSTransaction(tadd1)
			b.AddSTransaction(tadd2)
		})
	g.SaveTipCoinbaseOutsWithTreasury()
	g.AcceptTipBlock()
	outs = g.OldestCoinbaseOuts()

	// Ensure the CFilter committed to the outputs of the TSpend and TAdds.
	tipHash = &g.chain.BestSnapshot().Hash
	bcf, _, err := g.chain.FilterByBlockHash(tipHash)
	if err != nil {
		t.Fatal(err)
	}
	bcfKey := blockcf2.Key(&(g.Tip().Header.MerkleRoot))
	wantMatches := [][]byte{
		tspend.TxOut[1].PkScript[1:], // P2PKH without OP_TGEN
		tspend.TxOut[2].PkScript[1:], // P2SH without OP_TGEN
		tadd1.TxOut[1].PkScript[1:],  // P2PKH without OP_SSTxChange
		tadd2.TxOut[1].PkScript[1:],  // P2SH without OP_SSTxChange
	}
	for i, wantMatch := range wantMatches {
		if !bcf.Match(bcfKey, wantMatch) {
			t.Fatalf("bcf did not match script %d %x", i, wantMatch)
		}
	}

	// Create a tx that spends from the TSPend outputs and the TAdd change
	// output.
	tx := wire.NewMsgTx()
	tx.AddTxIn(&wire.TxIn{ // TSpend P2PKH output
		PreviousOutPoint: wire.OutPoint{
			Hash:  tspendHash,
			Index: 1,
			Tree:  1,
		},
		Sequence:    wire.MaxTxInSequenceNum,
		ValueIn:     tspendAmount,
		BlockHeight: tspendHeight,
		BlockIndex:  tspendIndex,
	})
	tx.AddTxIn(&wire.TxIn{ // TSPend P2SH output
		PreviousOutPoint: wire.OutPoint{
			Hash:  tspendHash,
			Index: 2,
			Tree:  1,
		},
		Sequence:        wire.MaxTxInSequenceNum,
		ValueIn:         tspendAmount,
		BlockHeight:     tspendHeight,
		BlockIndex:      tspendIndex,
		SignatureScript: append([]byte{txscript.OP_DATA_2}, spendP2shScript...),
	})
	tx.AddTxIn(&wire.TxIn{ // TAdd P2PKH output
		PreviousOutPoint: wire.OutPoint{
			Hash:  taddHash1,
			Index: 1,
			Tree:  1,
		},
		Sequence:    wire.MaxTxInSequenceNum,
		ValueIn:     taddChange,
		BlockHeight: tspendHeight,
		BlockIndex:  tspendIndex + 1,
	})
	tx.AddTxIn(&wire.TxIn{ // TAdd P2SH output
		PreviousOutPoint: wire.OutPoint{
			Hash:  taddHash2,
			Index: 1,
			Tree:  1,
		},
		Sequence:        wire.MaxTxInSequenceNum,
		ValueIn:         taddChange,
		BlockHeight:     tspendHeight,
		BlockIndex:      tspendIndex + 2,
		SignatureScript: append([]byte{txscript.OP_DATA_3}, addP2shScript...),
	})
	tx.AddTxOut(&wire.TxOut{
		Version: 0,
		Value:   tspendAmount*2 + taddChange,
	})

	// Generate the valid signature for the first input, which is a P2PKH.
	sig, err := sign.SignatureScript(tx, 0, tspend.TxOut[1].PkScript,
		txscript.SigHashAll, spendPrivKey.Serialize(), dcrec.STEcdsaSecp256k1,
		true)
	if err != nil {
		t.Fatalf("unable to generate sig: %v", err)
	}
	tx.TxIn[0].SignatureScript = sig

	// Generate the valid signature for the third input, which is a P2PKH.
	sig, err = sign.SignatureScript(tx, 2, tadd1.TxOut[1].PkScript,
		txscript.SigHashAll, addPrivKey.Serialize(), dcrec.STEcdsaSecp256k1,
		true)
	if err != nil {
		t.Fatalf("unable to generate sig: %v", err)
	}
	tx.TxIn[2].SignatureScript = sig

	// ---------------------------------------------------------------------
	// Generate up to the block where the tspend is spendable.
	//
	// ... -> btspend -> bpremat0 -> ... -> bprematn
	// ---------------------------------------------------------------------
	for i := uint32(0); i < uint32(params.CoinbaseMaturity-1); i++ {
		name := fmt.Sprintf("bpremat%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Generate a block that includes the transaction spending from the
	// TSpend and TAdd.
	//
	// ... -> btpremat -> btredeem
	// ---------------------------------------------------------------------
	g.NextBlock("btredeem", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			b.AddTransaction(tx)
		})
	g.SaveTipCoinbaseOutsWithTreasury()
	g.AcceptTipBlock()
}

func TestTSpendDupVote(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Create two TSPEND with invalid bits and duplicate votes.
	// ---------------------------------------------------------------------
	nextBlockHeight := g.Tip().Header.Height + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	tspendAmount := devsub * (tvi*mul - uint64(params.CoinbaseMaturity) +
		uint64(start-nextBlockHeight))
	tspendFee := uint64(0)
	tspend := g.CreateTreasuryTSpend(privKey,
		[]chaingen.AddressAmountTuple{

			{
				Amount: dcrutil.Amount(tspendAmount - tspendFee),
			},
		},
		dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()
	tspend2 := g.CreateTreasuryTSpend(privKey,
		[]chaingen.AddressAmountTuple{
			{
				Amount: dcrutil.Amount(tspendAmount - tspendFee),
			},
		},
		dcrutil.Amount(tspendFee), expiry)
	tspendHash2 := tspend2.TxHash()

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------

	// Generate votes up to TVI. This is legal however they should NOT be
	// counted in the totals since they are outside of the voting window.
	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Duplicate votes on the same treasury spend tx hash illegal and
	// therefore the tx is NOT recognized as a vote.
	//
	//   ... -> pretvi1
	//                 \-> bdv0
	// ---------------------------------------------------------------------

	startTip := g.TipName()
	voteCount := params.TicketsPerBlock
	g.NextBlock("bdv0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		addTSpendVotes(t,
			[]*chainhash.Hash{
				&tspendHash,
				&tspendHash,
			},
			[]stake.TreasuryVoteT{
				stake.TreasuryVoteYes,
				stake.TreasuryVoteYes,
			},
			voteCount, true))
	g.RejectTipBlock(ErrBadTxInput)

	// ---------------------------------------------------------------------
	// Invalid treasury spend tx vote bits are illegal and therefore the tx
	// is NOT recognized as a vote.
	//
	//   ... -> pretvi1
	//                 \-> bdv1
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	g.NextBlock("bdv1", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		addTSpendVotes(t,
			[]*chainhash.Hash{
				&tspendHash2,
			},
			[]stake.TreasuryVoteT{
				0x04, // Invalid bits
			},
			voteCount, true))
	g.RejectTipBlock(ErrBadTxInput)
}

func TestTSpendTooManyTSpend(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Create two TSPEND with invalid bits and duplicate votes.
	// ---------------------------------------------------------------------
	nextBlockHeight := g.Tip().Header.Height + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	tspendAmount := devsub * (tvi*mul - uint64(params.CoinbaseMaturity) +
		uint64(start-nextBlockHeight))
	tspendFee := uint64(0)
	maxTspends := 7
	tspends := make([]*wire.MsgTx, maxTspends+1)
	tspendHashes := make([]*chainhash.Hash, maxTspends+1)
	tspendVotes := make([]stake.TreasuryVoteT, maxTspends+1)
	for i := 0; i < maxTspends+1; i++ {
		tspends[i] = g.CreateTreasuryTSpend(privKey,
			[]chaingen.AddressAmountTuple{
				{
					Amount: dcrutil.Amount(tspendAmount - tspendFee),
				},
			},
			dcrutil.Amount(tspendFee), expiry)
		hash := tspends[i].TxHash()
		tspendHashes[i] = &hash
		tspendVotes[i] = stake.TreasuryVoteYes
	}

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------

	// Generate votes up to TVI. This is legal however they should NOT be
	// counted in the totals since they are outside of the voting window.
	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// 8 votes is illegal and therefore the tx is NOT recognized as a vote.
	//
	//   ... -> pretvi1
	//                 \-> bdv0
	// ---------------------------------------------------------------------

	voteCount := params.TicketsPerBlock
	g.NextBlock("bdv0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		addTSpendVotes(t, tspendHashes, tspendVotes, voteCount, true))
	g.RejectTipBlock(ErrBadTxInput)
}

func TestTSpendWindow(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Create TSPEND in mempool
	// ---------------------------------------------------------------------
	nextBlockHeight := g.Tip().Header.Height + uint32(tvi*mul*4) + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	tspendAmount := devsub * (tvi*mul - uint64(params.CoinbaseMaturity) +
		uint64(start-nextBlockHeight))
	tspendFee := uint64(0)
	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{
		{
			Amount: dcrutil.Amount(tspendAmount - tspendFee),
		},
	},
		dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------

	// Generate votes up to TVI. This is legal however they should NOT be
	// counted in the totals since they are outside of the voting window.
	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Generate a TVI worth of rewards and try to spend more.
	//
	//   ... -> b0 ... -> b7
	//                 \-> bvidaltoosoon0
	// ---------------------------------------------------------------------

	voteCount := params.TicketsPerBlock
	for i := uint64(0); i < tvi*mul; i++ {
		name := fmt.Sprintf("b%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Try accepting TSpend from the future.
	g.NextBlock("bvidaltoosoon0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrInvalidTSpendWindow)
}

// TestTSpendSignature verifies that both PI keys work and in addition that an
// invalid key is indeed rejected.
func TestTSpendSignature(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Create TSPEND in mempool with key 1 and 2
	// ---------------------------------------------------------------------
	nextBlockHeight := g.Tip().Header.Height //+ uint32(tvi*mul*4) + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	tspendAmount1 := uint64(devsub / 3)
	tspendFee := uint64(0)
	tspend1 := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{
		{
			Amount: dcrutil.Amount(tspendAmount1 - tspendFee),
		},
	},
		dcrutil.Amount(tspendFee), expiry)
	tspend1Hash := tspend1.TxHash()

	// tspend 2.
	tspendAmount2 := uint64(devsub / 5)
	tspend2 := g.CreateTreasuryTSpend(privKey2, []chaingen.AddressAmountTuple{
		{
			Amount: dcrutil.Amount(tspendAmount2 - tspendFee),
		},
	},
		dcrutil.Amount(tspendFee), expiry)
	tspend2Hash := tspend2.TxHash()

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------
	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < start-nextBlockHeight-1; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Generate TVI*MUL of yes votes for both TSpends,
	//
	//   ... -> bpretvi1 -> btvi0 -> ... -> btvi7
	// ---------------------------------------------------------------------

	voteCount := params.TicketsPerBlock
	for i := uint64(0); i < tvi*mul; i++ {
		name := fmt.Sprintf("btvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t,
				[]*chainhash.Hash{
					&tspend1Hash,
					&tspend2Hash,
				},
				[]stake.TreasuryVoteT{
					stake.TreasuryVoteYes,
					stake.TreasuryVoteYes,
				},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Assert that we are on a TVI.
	if standalone.IsTreasuryVoteInterval(uint64(g.Tip().Header.Height), tvi) {
		t.Fatalf("expected !TVI %v", g.Tip().Header.Height)
	}

	// Assert treasury balance
	ts, err := getTreasuryState(g, g.Tip().BlockHash())
	if err != nil {
		t.Fatal(err)
	}
	if ts.balance != int64(devsub*tvi*mul) {
		t.Fatalf("assert balance got %v want devsub %v", ts.balance,
			devsub*tvi*mul)
	}

	// ---------------------------------------------------------------------
	// Add tspend1 twice
	//
	//   ... -> btvi7 ->
	//                \-> double0
	// ---------------------------------------------------------------------

	startTip := g.TipName()
	g.NextBlock("double0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpends
			b.AddSTransaction(tspend1)
			b.AddSTransaction(tspend2)
			b.AddSTransaction(tspend1)
		})
	g.RejectTipBlock(ErrDuplicateTx)

	// ---------------------------------------------------------------------
	// Add tspend1 and tspend2
	//
	//   ... -> btvi7 -> doubleok0
	//                \-> double0
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	g.NextBlock("doubleok0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpends
			b.AddSTransaction(tspend1)
			b.AddSTransaction(tspend2)
		})
	g.SaveTipCoinbaseOutsWithTreasury()
	g.AcceptTipBlock()
	outs = g.OldestCoinbaseOuts()

	// Assert treasury balance
	ts, err = getTreasuryState(g, g.Tip().BlockHash())
	if err != nil {
		t.Fatal(err)
	}
	if ts.balance != int64(devsub*tvi*mul)+devsub {
		t.Fatalf("assert balance got %v want devsub %v", ts.balance,
			devsub*tvi*mul+devsub)
	}

	// ---------------------------------------------------------------------
	// Add Coinbase maturity blocks and assert treasury balance.
	//
	//   ... -> btvi7 -> doubleok0 -> cbm0 -> cbm1
	//                \-> double0
	// ---------------------------------------------------------------------

	for i := uint32(0); i < uint32(params.CoinbaseMaturity); i++ {
		name := fmt.Sprintf("cbm%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Assert treasury balance
	ts, err = getTreasuryState(g, g.Tip().BlockHash())
	if err != nil {
		t.Fatal(err)
	}
	expectedBalance := int64(devsub*tvi*mul) +
		(int64(devsub) * int64(params.CoinbaseMaturity+1)) // doubleok0+cbm0+cbm1
	expectedBalance -= int64(tspendAmount1)
	expectedBalance -= int64(tspendAmount2)
	if ts.balance != expectedBalance {
		t.Fatalf("assert balance got %v want devsub %v", ts.balance,
			expectedBalance)
	}
}

// TestTSpendSignatureInvalid verifies that a tspend is disallowed with an
// invalid key.
func TestTSpendSignatureInvalid(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Create TSPEND in mempool.
	// ---------------------------------------------------------------------
	nextBlockHeight := g.Tip().Header.Height //+ uint32(tvi*mul*4) + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	// Corrupt key for test.
	wrongKey := make([]byte, len(privKey))
	copy(wrongKey, privKey)
	wrongKey[len(wrongKey)-1] = ^wrongKey[len(wrongKey)-1]

	tspendAmount1 := uint64(devsub / 3)
	tspendFee := uint64(0)
	tspend1 := g.CreateTreasuryTSpend(wrongKey, []chaingen.AddressAmountTuple{
		{
			Amount: dcrutil.Amount(tspendAmount1 - tspendFee),
		},
	},
		dcrutil.Amount(tspendFee), expiry)
	tspend1Hash := tspend1.TxHash()

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bva19 -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------
	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < start-nextBlockHeight-1; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Generate TVI*MUL of yes votes for TSpend,
	//
	//   ... -> bpretvi1 -> btvi0 -> ... -> btvi7
	// ---------------------------------------------------------------------

	voteCount := params.TicketsPerBlock
	for i := uint64(0); i < tvi*mul; i++ {
		name := fmt.Sprintf("btvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t,
				[]*chainhash.Hash{
					&tspend1Hash,
				},
				[]stake.TreasuryVoteT{
					stake.TreasuryVoteYes,
				},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// Assert that we are on a TVI.
	if standalone.IsTreasuryVoteInterval(uint64(g.Tip().Header.Height), tvi) {
		t.Fatalf("expected !TVI %v", g.Tip().Header.Height)
	}

	// Assert treasury balance
	ts, err := getTreasuryState(g, g.Tip().BlockHash())
	if err != nil {
		t.Fatal(err)
	}
	if ts.balance != int64(devsub*tvi*mul) {
		t.Fatalf("assert balance got %v want devsub %v", ts.balance,
			devsub*tvi*mul)
	}

	// ---------------------------------------------------------------------
	// Add tspend with invalid signature
	//
	//   ... -> btvi7 ->
	//                \-> double0
	// ---------------------------------------------------------------------

	g.NextBlock("invalidsig0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend1)
		})
	g.RejectTipBlock(ErrUnknownPiKey)
}

func TestTSpendExists(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Shorter versions of useful params for convenience.
	cbm := params.CoinbaseMaturity
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// splitSecondRegularTxOutputs is a munge function which modifies the
	// provided block by replacing its second regular transaction with one
	// that creates several utxos.
	const splitTxNumOutputs = 6
	splitSecondRegularTxOutputs := func(b *wire.MsgBlock) {
		// Remove the current outputs of the second transaction while
		// saving the relevant public key script, input amount, and fee
		// for below.
		tx := b.Transactions[1]
		inputAmount := tx.TxIn[0].ValueIn
		pkScript := tx.TxOut[0].PkScript
		fee := inputAmount - tx.TxOut[0].Value
		tx.TxOut = tx.TxOut[:0]

		// Final outputs are the input amount minus the fee split into
		// more than one output.  These are intended to provide
		// additional utxos for testing.
		outputAmount := inputAmount - fee
		splitAmount := outputAmount / splitTxNumOutputs
		for i := 0; i < splitTxNumOutputs; i++ {
			if i == splitTxNumOutputs-1 {
				splitAmount = outputAmount -
					splitAmount*(splitTxNumOutputs-1)
			}
			tx.AddTxOut(wire.NewTxOut(splitAmount, pkScript))
		}
	}

	// Generate spendable outputs to do fork tests with.
	var txOuts [][]chaingen.SpendableOut
	genBlocks := cbm * 8
	for i := uint16(0); i < genBlocks; i++ {
		outs := g.OldestCoinbaseOuts()
		name := fmt.Sprintf("bouts%v", i)
		g.NextBlock(name, &outs[0], outs[1:], replaceTreasuryVersions,
			replaceCoinbase, splitSecondRegularTxOutputs)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()

		souts := make([]chaingen.SpendableOut, 0, splitTxNumOutputs)
		for j := 0; j < splitTxNumOutputs; j++ {
			spendableOut := chaingen.MakeSpendableOut(g.Tip(), 1,
				uint32(j))
			souts = append(souts, spendableOut)
		}
		txOuts = append(txOuts, souts)
	}

	// ---------------------------------------------------------------------
	// Create TSPEND in mempool
	// ---------------------------------------------------------------------
	nextBlockHeight := g.Tip().Header.Height + 1
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}

	tspendAmount := uint64(devsub)
	tspendFee := uint64(0)
	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{{
		Amount: dcrutil.Amount(tspendAmount - tspendFee),
	}}, dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()

	// ---------------------------------------------------------------------
	// Generate enough blocks to get to TVI.
	//
	//   ... -> bouts -> bpretvi0 -> bpretvi1
	// ---------------------------------------------------------------------

	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Generate two TVI with votes.
	//
	//   ... -> b0 ... -> b7
	// ---------------------------------------------------------------------

	voteCount := params.TicketsPerBlock
	for i := uint64(0); i < tvi*2; i++ {
		name := fmt.Sprintf("b%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Generate a TVI and mine same TSpend, which has to be rejected.
	//
	//   ... -> be0 ... -> be3 ->
	//                         \-> bexists0
	// ---------------------------------------------------------------------

	startTip := g.TipName()
	for i := uint64(0); i < tvi; i++ {
		name := fmt.Sprintf("be%v", i)
		if i == 0 {
			// Mine tspend.
			g.NextBlock(name, nil, txOuts[i][1:],
				replaceTreasuryVersions,
				replaceCoinbase, func(b *wire.MsgBlock) {
					// Add TSpend
					b.AddSTransaction(tspend)
				})
		} else {
			g.NextBlock(name, nil, txOuts[i][1:],
				replaceTreasuryVersions,
				replaceCoinbase)
		}
		g.AcceptTipBlock()
	}
	oldTip := g.TipName()

	// Mine tspend again.
	g.NextBlock("bexists0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrTSpendExists)

	// ---------------------------------------------------------------------
	// Generate a TVI and mine same TSpend, should not exist since it is a
	// fork.
	//
	//      /-> be0 ... -> be3 -> bexists0
	// ... -> b3
	//      \-> bep0 ... -> bep3 -> bexists1
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	var nextFork uint64
	for i := uint64(0); i < tvi; i++ {
		name := fmt.Sprintf("bep%v", i)
		g.NextBlock(name, nil, txOuts[i][1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.AcceptedToSideChainWithExpectedTip(oldTip)
		nextFork = i + 1
	}

	// Mine tspend again.
	g.NextBlock("bexists1", nil, txOuts[nextFork][1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.AcceptTipBlock()

	// ---------------------------------------------------------------------
	// Generate a TVI and mine same TSpend, should not exist since it is a
	// fork.
	//
	//      /-> be0 ... -> be3 -> bexists0
	// ... -> b3
	//      \-> bep0 ... -> bep3 -> bexists1 ->bep4
	//       \-> bepp0 ... -> bepp3 -> bexists2 -> bxxx0 .. bxxx3
	// ---------------------------------------------------------------------

	// Generate one more block to extend current best chain.
	name := fmt.Sprintf("bep%v", nextFork)
	g.NextBlock(name, nil, txOuts[nextFork+1][1:], replaceTreasuryVersions,
		replaceCoinbase)
	g.AcceptTipBlock()
	oldTip = g.TipName()
	oldHeight := g.Tip().Header.Height

	// Create bepp fork
	g.SetTip(startTip)
	txIdx := uint64(0)
	for i := uint64(0); i < tvi; i++ {
		name := fmt.Sprintf("bepp%v", i)
		g.NextBlock(name, nil, txOuts[i][1:],
			replaceTreasuryVersions, replaceCoinbase)
		g.AcceptedToSideChainWithExpectedTip(oldTip)
		txIdx = i + 1
	}

	// Mine tspend yet again.
	g.NextBlock("bexists2", nil, txOuts[txIdx][1:],
		replaceTreasuryVersions, replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.AcceptedToSideChainWithExpectedTip(oldTip)
	txIdx++

	// Force reorg
	for i := uint64(0); i < tvi; i++ {
		name := fmt.Sprintf("bxxx%v", i)
		b := g.NextBlock(name, nil, txOuts[txIdx][1:],
			replaceTreasuryVersions, replaceCoinbase)
		if b.Header.Height <= oldHeight {
			g.AcceptedToSideChainWithExpectedTip(oldTip)
		} else {
			g.AcceptTipBlock()
		}
		txIdx++
	}
}

func TestTreasuryBalance(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Create 10 blocks that has a tadd without change.
	//
	//   ... -> b0
	// ---------------------------------------------------------------------

	blockCount := 10
	expectedTotal := devsub *
		(blockCount - int(params.CoinbaseMaturity)) // dev subsidy
	skippedTotal := 0
	for i := 0; i < blockCount; i++ {
		amount := i + 1
		if i < blockCount-int(params.CoinbaseMaturity) {
			expectedTotal += amount
		} else {
			skippedTotal += amount
		}
		outs := g.OldestCoinbaseOuts()
		name := fmt.Sprintf("b%v", i)
		_ = g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			func(b *wire.MsgBlock) {
				// Add TADD
				tx := g.CreateTreasuryTAdd(&outs[0],
					dcrutil.Amount(amount),
					dcrutil.Amount(0))
				tx.Version = wire.TxVersionTreasury
				b.AddSTransaction(tx)
			})
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
	}
	iterations := 1

	ts, err := getTreasuryState(g, g.Tip().BlockHash())
	if err != nil {
		t.Fatal(err)
	}

	if ts.balance != int64(expectedTotal) {
		t.Fatalf("invalid balance: total %v expected %v",
			ts.balance, expectedTotal)
	}
	if ts.values[1].amount != int64(blockCount) {
		t.Fatalf("invalid Value: total %v expected %v",
			ts.values[0], int64(blockCount))
	}

	// ---------------------------------------------------------------------
	// Create 10 blocks that has a tadd with change. Pretend that the TSpend
	// transaction is in the mempool and vote on it.
	//
	//   ... -> b10
	// ---------------------------------------------------------------------

	// This looks a little funky but it was coppied from the prior TSPEND
	// test that created this many tspends. Since that is no longer
	// possible use the for loop to get to the same totals.
	var tspendAmount, tspendFee int
	nextTipHeight := int64(g.Tip().Header.Height + 1)
	expiry := standalone.CalcTSpendExpiry(nextTipHeight,
		params.TreasuryVoteInterval,
		params.TreasuryVoteIntervalMultiplier)
	for i := 0; i < blockCount*2+int(params.CoinbaseMaturity); i++ {
		if i > (blockCount * 2) {
			// Skip last CoinbaseMaturity blocks.
			break
		}
		tspendAmount += i + 1
		tspendFee++
	}
	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{
		{
			Amount: dcrutil.Amount(tspendAmount - tspendFee),
		},
	},
		dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()

	// Treasury votes munger.
	addTSpendVotes := func(b *wire.MsgBlock) {
		// Find SSGEN and append Yes vote.
		for k, v := range b.STransactions {
			if !stake.IsSSGen(v) {
				continue
			}
			if len(v.TxOut) != 3 {
				t.Fatalf("expected SSGEN.TxOut len 3 got %v",
					len(v.TxOut))
			}

			// Append vote: OP_RET OP_DATA <TV> <tspend hash> <vote bits>
			vote := make([]byte, 2+chainhash.HashSize+1)
			vote[0] = 'T'
			vote[1] = 'V'
			copy(vote[2:], tspendHash[:])
			vote[len(vote)-1] = 0x01 // Yes
			s, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).
				AddData(vote).Script()
			if err != nil {
				t.Fatal(err)
			}
			b.STransactions[k].TxOut = append(b.STransactions[k].TxOut,
				&wire.TxOut{
					PkScript: s,
				})
			b.STransactions[k].Version = wire.TxVersionTreasury

			// Assert vote insertion worked.
			_, err = stake.GetSSGenTreasuryVotes(s)
			if err != nil {
				t.Fatalf("expected treasury vote: %v", err)
			}

			// Assert this remains a valid SSGEN.
			err = stake.CheckSSGen(b.STransactions[k])
			if err != nil {
				t.Fatalf("expected SSGen: %v", err)
			}
		}
	}

	expectedTotal += skippedTotal
	expectedTotal += devsub * blockCount // dev subsidy
	for i := blockCount; i < blockCount*2; i++ {
		amount := i + 1
		if i < (blockCount*2)-int(params.CoinbaseMaturity) {
			expectedTotal += amount
		}
		outs := g.OldestCoinbaseOuts()
		name := fmt.Sprintf("b%v", i)
		_ = g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes,
			func(b *wire.MsgBlock) {
				tx := g.CreateTreasuryTAdd(&outs[0],
					dcrutil.Amount(amount),
					dcrutil.Amount(1))
				tx.Version = wire.TxVersionTreasury
				b.AddSTransaction(tx)
			})
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
	}
	iterations++

	ts, err = getTreasuryState(g, g.Tip().BlockHash())
	if err != nil {
		t.Fatal(err)
	}

	if ts.balance != int64(expectedTotal) {
		t.Fatalf("invalid balance: total %v expected %v",
			ts.balance, expectedTotal)
	}
	if ts.values[1].amount != int64(blockCount*2) {
		t.Fatalf("invalid Value: total %v expected %v",
			ts.values[0], int64(blockCount)*2)
	}

	// ---------------------------------------------------------------------
	// Create 20 blocks that has a tspend and params.CoinbaseMaturity more
	// to bring treasury balance back to 0.
	//
	//   ... -> b20
	// ---------------------------------------------------------------------

	var doneTSpend bool
	for i := 0; i < blockCount*2+int(params.CoinbaseMaturity); i++ {
		outs := g.OldestCoinbaseOuts()
		name := fmt.Sprintf("b%v", i+blockCount*2)
		if (g.Tip().Header.Height+1)%4 == 0 && !doneTSpend {
			// Insert TSPEND
			g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
				replaceCoinbase,
				func(b *wire.MsgBlock) {
					// Add TSpend
					b.AddSTransaction(tspend)
				})
			doneTSpend = true
		} else {
			g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
				replaceCoinbase)
		}
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
	}
	iterations += 2 // We generate 2*blockCount

	ts, err = getTreasuryState(g, g.Tip().BlockHash())
	if err != nil {
		t.Fatal(err)
	}

	expected := int64(devsub*blockCount*iterations - tspendFee) // Expected devsub
	if ts.balance != expected {
		t.Fatalf("invalid balance: total %v expected %v",
			ts.balance, expected)
	}
}

// newTxOut returns a new transaction output with the given parameters.
func newTxOut(amount int64, pkScriptVer uint16, pkScript []byte) *wire.TxOut {
	return &wire.TxOut{
		Value:    amount,
		Version:  pkScriptVer,
		PkScript: pkScript,
	}
}

func createTAdd(spend chaingen.SpendableOut, changeDivisor dcrutil.Amount, params *chaincfg.Params) func(b *wire.MsgBlock) {
	redeemScript := []byte{txscript.OP_TRUE}
	p2shOpTrueAddr, err := stdaddr.NewAddressScriptHashV0(redeemScript, params)
	if err != nil {
		panic(err)
	}

	return func(b *wire.MsgBlock) {
		amount := spend.Amount()
		tx := wire.NewMsgTx()
		tx.AddTxIn(&wire.TxIn{
			PreviousOutPoint: spend.PrevOut(),
			Sequence:         wire.MaxTxInSequenceNum,
			ValueIn:          int64(amount),
			BlockHeight:      spend.BlockHeight(),
			BlockIndex:       spend.BlockIndex(),
			SignatureScript:  []byte{txscript.OP_DATA_1, txscript.OP_TRUE},
		})

		// Add negative change.
		trsyAddScript := []byte{txscript.OP_TADD}
		if changeDivisor != 0 {
			changeScriptVer, changeScript := p2shOpTrueAddr.StakeChangeScript()

			change := spend.Amount() / changeDivisor
			amount := spend.Amount() - change
			tx.AddTxOut(wire.NewTxOut(int64(amount), trsyAddScript))
			tx.AddTxOut(newTxOut(int64(change), changeScriptVer, changeScript))
		} else {
			tx.AddTxOut(wire.NewTxOut(int64(amount), trsyAddScript))
		}
		tx.Version = wire.TxVersionTreasury
		b.AddSTransaction(tx)
	}
}

func TestTAddCorners(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier
	cbm := params.CoinbaseMaturity

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with the
	// treasury agenda version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// splitSecondRegularTxOutputs is a munge function which modifies the
	// provided block by replacing its second regular transaction with one
	// that creates several utxos.
	const splitTxNumOutputs = 6
	splitSecondRegularTxOutputs := func(b *wire.MsgBlock) {
		// Remove the current outputs of the second transaction while
		// saving the relevant public key script, input amount, and fee
		// for below.
		tx := b.Transactions[1]
		inputAmount := tx.TxIn[0].ValueIn
		pkScript := tx.TxOut[0].PkScript
		fee := inputAmount - tx.TxOut[0].Value
		tx.TxOut = tx.TxOut[:0]

		// Final outputs are the input amount minus the fee split into
		// more than one output.  These are intended to provide
		// additional utxos for testing.
		outputAmount := inputAmount - fee
		splitAmount := outputAmount / splitTxNumOutputs
		for i := 0; i < splitTxNumOutputs; i++ {
			if i == splitTxNumOutputs-1 {
				splitAmount = outputAmount -
					splitAmount*(splitTxNumOutputs-1)
			}
			tx.AddTxOut(wire.NewTxOut(splitAmount, pkScript))
		}
	}

	// Generate spendable outputs to do fork tests with.
	var txOuts [][]chaingen.SpendableOut
	genBlocks := cbm * 8
	for i := uint16(0); i < genBlocks; i++ {
		outs := g.OldestCoinbaseOuts()
		name := fmt.Sprintf("bouts%v", i)
		g.NextBlock(name, &outs[0], outs[1:], replaceTreasuryVersions,
			replaceCoinbase, splitSecondRegularTxOutputs)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()

		souts := make([]chaingen.SpendableOut, 0, splitTxNumOutputs)
		for j := 0; j < splitTxNumOutputs; j++ {
			spendableOut := chaingen.MakeSpendableOut(g.Tip(), 1,
				uint32(j))
			souts = append(souts, spendableOut)
		}
		txOuts = append(txOuts, souts)
	}

	// ---------------------------------------------------------------------
	// Create TAdd with negative change.
	//
	//   ... -> bn0
	// ---------------------------------------------------------------------
	mungeValueChange := func(b *wire.MsgBlock) {
		for k := range b.STransactions {
			if !stake.IsTAdd(b.STransactions[k]) {
				continue
			}
			b.STransactions[k].TxOut[1].Value =
				-b.STransactions[k].TxOut[1].Value
			break
		}
	}

	startTip := g.TipName()
	g.NextBlock("bn0", nil, txOuts[0][1:], replaceTreasuryVersions,
		replaceCoinbase,
		createTAdd(txOuts[0][0], 10, params),
		mungeValueChange)
	g.RejectTipBlock(ErrBadTxOutValue)

	// ---------------------------------------------------------------------
	// Create TAdd with negative in amount.
	//
	//   ... -> bn1
	// ---------------------------------------------------------------------

	mungeValueIn := func(b *wire.MsgBlock) {
		for k := range b.STransactions {
			if !stake.IsTAdd(b.STransactions[k]) {
				continue
			}
			b.STransactions[k].TxIn[0].ValueIn =
				-b.STransactions[k].TxIn[0].ValueIn
			break
		}
	}

	g.SetTip(startTip)
	g.NextBlock("bn1", nil, txOuts[0][1:], replaceTreasuryVersions,
		replaceCoinbase,
		createTAdd(txOuts[0][0], 10, params),
		mungeValueIn)
	g.RejectTipBlock(ErrFraudAmountIn)

	// ---------------------------------------------------------------------
	// Create TAdd with negative out amount.
	//
	//   ... -> bn2
	// ---------------------------------------------------------------------

	mungeValueOut := func(b *wire.MsgBlock) {
		for k := range b.STransactions {
			if !stake.IsTAdd(b.STransactions[k]) {
				continue
			}
			b.STransactions[k].TxOut[0].Value =
				-b.STransactions[k].TxOut[0].Value
			break
		}
	}

	g.SetTip(startTip)
	g.NextBlock("bn2", nil, txOuts[0][1:], replaceTreasuryVersions,
		replaceCoinbase,
		createTAdd(txOuts[0][0], 10, params),
		mungeValueOut)
	g.RejectTipBlock(ErrBadTxOutValue)

	// ---------------------------------------------------------------------
	// Create TAdd with 0 change.
	//
	//   ... -> bn3
	// ---------------------------------------------------------------------

	mungeChangeValue := func(b *wire.MsgBlock) {
		for k := range b.STransactions {
			if !stake.IsTAdd(b.STransactions[k]) {
				continue
			}
			b.STransactions[k].TxOut[1].Value = 0
			break
		}
	}

	g.SetTip(startTip)
	g.NextBlock("bn3", nil, txOuts[0][1:], replaceTreasuryVersions,
		replaceCoinbase,
		createTAdd(txOuts[0][0], 10, params),
		mungeChangeValue)
	g.RejectTipBlock(ErrInvalidTAddChange)

	_ = tvi
	_ = mul
}

func TestTreasuryBaseCorners(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the provided
	// block by replacing the block, stake, and vote versions with the treasury
	// deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// removeAllButTreasurybase is a munge function which modifies the provided
	// block by removing all stake transactions other than the treasurybase.
	removeAllButTreasurybase := func(b *wire.MsgBlock) {
		b.Header.FreshStake = 0
		b.STransactions = b.STransactions[:1]
	}

	// removeStakeTxns is a munge function which modifies the provided block by
	// removing all stake transactions other than votes.
	removeStakeTxns := func(b *wire.MsgBlock) {
		b.Header.FreshStake = 0
		b.Header.Revocations = 0
		b.STransactions = b.STransactions[1:6]
	}

	// dupTreasurybase is a munge function which modifies the provided block by
	// adding a duplicate treasurybase at the end of the stake tree.
	dupTreasurybase := func(b *wire.MsgBlock) {
		txCopy := b.STransactions[0].Copy()
		txCopy.TxOut[1].PkScript[len(txCopy.TxOut[1].PkScript)-1] ^= 0x55
		b.AddSTransaction(txCopy)
	}

	// flipTreasurybase is a munge function which modifies the provided block by
	// swapping the treasurybase with the second transaction in the stake tree.
	flipTreasurybase := func(b *wire.MsgBlock) {
		stakeTxns := b.STransactions
		stakeTxns[0], stakeTxns[1] = stakeTxns[1], stakeTxns[0]
	}

	// removeTreasurybase is a munge function which modifies the provided block
	// by removing the treasurybase from the stake tree.
	removeTreasurybase := func(b *wire.MsgBlock) {
		b.STransactions = b.STransactions[1:]
	}

	// changeHeightTreasurybase is a munge function which modifies the provided
	// block by adding one to the height encoded in the treasurybase.
	changeHeightTreasurybase := func(b *wire.MsgBlock) {
		b.STransactions[0].TxOut[1].PkScript[2]++
	}

	// corruptLengthTreasurybase is a munge function which modifies the provided
	// block by mutating the treasurybase script to corrupt it.
	corruptLengthTreasurybase := func(b *wire.MsgBlock) {
		s := b.STransactions[0].TxOut[1].PkScript
		b.STransactions[0].TxOut[1].PkScript = s[:len(s)-1]
	}

	// corruptTreasurybaseValueIn is a munge function which modifies the
	// provided block by mutating the treasurybase in value.
	corruptTreasurybaseValueIn := func(b *wire.MsgBlock) {
		b.STransactions[0].TxIn[0].ValueIn--
	}

	// corruptTreasurybaseValueOut is a munge function which modifies the
	// provided block by mutating the treasurybase out value.
	corruptTreasurybaseValueOut := func(b *wire.MsgBlock) {
		b.STransactions[0].TxOut[0].Value--
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Append a treasurybase to STransactions.
	// ---------------------------------------------------------------------

	outs := g.OldestCoinbaseOuts()
	startTip := g.TipName()
	g.NextBlock("twotb0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, dupTreasurybase)
	g.RejectTipBlock(ErrMultipleTreasurybases)

	// ---------------------------------------------------------------------
	// Flip STransactions 0 and 1.
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	g.NextBlock("notbon0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, flipTreasurybase)
	g.RejectTipBlock(ErrFirstTxNotTreasurybase)

	// ---------------------------------------------------------------------
	// No treasurybase.
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	g.NextBlock("notb0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, removeTreasurybase)
	g.RejectTipBlock(ErrFirstTxNotTreasurybase)

	// ---------------------------------------------------------------------
	// No treasurybase and no tickets.
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	g.NextBlock("nothing0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, removeStakeTxns)
	g.RejectTipBlock(ErrFirstTxNotTreasurybase)

	// ---------------------------------------------------------------------
	// Treasury base invalid height
	// ---------------------------------------------------------------------
	g.SetTip(startTip)
	g.NextBlock("height0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, changeHeightTreasurybase)
	g.RejectTipBlock(ErrTreasurybaseHeight)

	// ---------------------------------------------------------------------
	// Treasury base invalid OP_RETURN script length.
	// ---------------------------------------------------------------------
	g.SetTip(startTip)
	g.NextBlock("length0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, corruptLengthTreasurybase)
	g.RejectTipBlock(ErrFirstTxNotTreasurybase)

	// ---------------------------------------------------------------------
	// Only treasury base.
	// ---------------------------------------------------------------------
	g.SetTip(startTip)
	g.NextBlock("novotes0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, removeAllButTreasurybase)
	g.RejectTipBlock(ErrVotesMismatch)

	// ---------------------------------------------------------------------
	// Treasury base invalid TxIn[0].Value
	// ---------------------------------------------------------------------
	g.SetTip(startTip)
	g.NextBlock("invalidin0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, corruptTreasurybaseValueIn)
	g.RejectTipBlock(ErrBadTreasurybaseAmountIn)

	// ---------------------------------------------------------------------
	// Treasury base invalid TxOut[0].Value
	// ---------------------------------------------------------------------
	g.SetTip(startTip)
	g.NextBlock("invalidout0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, corruptTreasurybaseValueOut)
	g.RejectTipBlock(ErrTreasurybaseOutValue)

	// Note we can't hit the following errors in consensus:
	// * ErrFirstTxNotTreasurybase (missing OP_RETURN)
	// * ErrFirstTxNotTreasurybase (version)
	// * ErrTreasurybaseTxNotOpReturn
	// * ErrInvalidTreasurybaseTxOutputs
	// * ErrInvalidTreasurybaseVersion
	// * ErrInvalidTreasurybaseScript
}

func TestTSpendCorners(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the provided
	// block by replacing the block, stake, and vote versions with the treasury
	// deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Create TSpend in mempool
	// ---------------------------------------------------------------------
	nextBlockHeight := g.Tip().Header.Height + 1
	tspendAmount := devsub
	tspendFee := 100
	expiry := standalone.CalcTSpendExpiry(int64(nextBlockHeight), tvi, mul)
	start, _, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}
	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{{
		Amount: dcrutil.Amount(tspendAmount - tspendFee),
	}}, dcrutil.Amount(tspendFee), expiry)
	tspendHash := tspend.TxHash()

	// ---------------------------------------------------------------------
	// Get to TVI
	//   ... -> pretvi1
	// ---------------------------------------------------------------------

	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < start-nextBlockHeight; i++ {
		name := fmt.Sprintf("bpretvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Vote on TSpend
	//   pretvi1 -> btvi0 -> ... -> btvi7
	// ---------------------------------------------------------------------

	voteCount := params.TicketsPerBlock
	for i := uint64(0); i < tvi*mul; i++ {
		name := fmt.Sprintf("btvi%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
	}

	// ---------------------------------------------------------------------
	// Mine TSpend with improperly encoded ValueIn.
	// -> btvi7
	// \-> btspend0
	// ---------------------------------------------------------------------

	tspend.TxIn[0].ValueIn++
	g.NextBlock("btspend0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrInvalidTSpendValueIn)
}

func TestTSpendFirstTVICorner(t *testing.T) {
	t.Parallel()

	// Clone the parameters so they can be mutated and mark the treasury agenda
	// as always active.
	const voteID = chaincfg.VoteIDTreasury
	params := cloneParams(chaincfg.RegNetParams())
	forceDeploymentResult(t, params, voteID, "yes")

	// Change params to hit corner case.
	params.StakeValidationHeight = 144
	params.TicketPoolSize = 144
	params.TreasuryVoteInterval = 11
	params.TreasuryVoteIntervalMultiplier = 1

	// Shorter versions of useful params for convenience.
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier
	svh := params.StakeValidationHeight
	t.Logf("svh: %v tvi %v mul %v", svh, tvi, mul)

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// With an SVH of 144 and a TVI of 11 and a MUL of 1 create a tspend
	// that expires on block 156 (voting interval [143,154]). Start voting
	// on block 144 (SVH) up to 153 (inclusive) which is 10*5 votes of the
	// total of 55 possible.  That easily is voted in since 60% is 33 yes
	// votes.
	expiry := standalone.CalcTSpendExpiry(140, tvi, mul)
	start, end, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("tspend expiry %v start %v end %v", expiry, start, end)
	tspend := newFakeCreateTSpend(privKey, []dcrutil.Amount{100000000}, 1,
		expiry)
	tspendHash := tspend.TxHash()
	_, _, err = stake.CheckTSpend(tspend)
	if err != nil {
		t.Fatal(err)
	}

	// ---------------------------------------------------------------------
	// Block One.
	// ---------------------------------------------------------------------

	// Add the required first block.
	//
	//   genesis -> bfb
	g.CreateBlockOne("bfb", 0)
	g.AssertTipHeight(1)
	g.AcceptTipBlock()

	// Assert treasury agenda is indeed active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Generate enough blocks to have mature coinbase outputs to work with.
	// Note that going forward all coinbase is munged to also output
	// treasurybase.
	//
	//   genesis -> bfb -> bm0 -> bm1 -> ... -> bm#
	// ---------------------------------------------------------------------

	ticketsPerBlock := params.TicketsPerBlock
	coinbaseMaturity := params.CoinbaseMaturity
	stakeEnabledHeight := params.StakeEnabledHeight
	stakeValidationHeight := params.StakeValidationHeight
	for i := uint16(0); i < coinbaseMaturity; i++ {
		blockName := fmt.Sprintf("bm%d", i)
		g.NextBlock(blockName, nil, nil, replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
	}
	g.AssertTipHeight(uint32(coinbaseMaturity) + 1)

	// ---------------------------------------------------------------------
	// Generate enough blocks to reach the stake enabled height while
	// creating ticket purchases that spend from the coinbases matured
	// above.  This will also populate the pool of immature tickets.
	//
	//   ... -> bm# ... -> bse0 -> bse1 -> ... -> bse#
	// ---------------------------------------------------------------------

	var ticketsPurchased int
	for i := int64(0); int64(g.Tip().Header.Height) < stakeEnabledHeight; i++ {
		outs := g.OldestCoinbaseOuts()
		ticketOuts := outs[1:]
		ticketsPurchased += len(ticketOuts)
		blockName := fmt.Sprintf("bse%d", i)
		g.NextBlock(blockName, nil, ticketOuts, replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
	}
	g.AssertTipHeight(uint32(stakeEnabledHeight))

	// ---------------------------------------------------------------------
	// Generate enough blocks to reach the stake validation height while
	// continuing to purchase tickets using the coinbases matured above and
	// allowing the immature tickets to mature and thus become live.
	//
	//   ... -> bse# -> bsv0 -> bsv1 -> ... -> bsv#
	// ---------------------------------------------------------------------

	targetPoolSize := g.Params().TicketPoolSize * ticketsPerBlock
	for i := int64(0); int64(g.Tip().Header.Height) < stakeValidationHeight-1; i++ {
		// Only purchase tickets until the target ticket pool size is
		// reached.
		outs := g.OldestCoinbaseOuts()
		ticketOuts := outs[1:]
		if ticketsPurchased+len(ticketOuts) > int(targetPoolSize) {
			ticketsNeeded := int(targetPoolSize) - ticketsPurchased
			if ticketsNeeded > 0 {
				ticketOuts = ticketOuts[1 : ticketsNeeded+1]
			} else {
				ticketOuts = nil
			}
		}
		ticketsPurchased += len(ticketOuts)

		blockName := fmt.Sprintf("bsv%d", i)
		g.NextBlock(blockName, nil, ticketOuts, replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
	}

	g.AssertTipHeight(uint32(stakeValidationHeight - 1))

	// ---------------------------------------------------------------------
	// Test adding a tspend on the first possible height despite not being
	// on a TVI.
	//
	//   ... -> bse# -> bsv0 -> bsv1 -> ... -> bsv#
	//                                      \-> boink0
	// ---------------------------------------------------------------------

	startTip := g.TipName()
	g.NextBlock("boink0", nil, nil, replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrNotTVI)

	// ---------------------------------------------------------------------
	// Start TSpend 'yes' voting here.
	//
	//   ... -> bse# -> bsv0 -> bsv1 -> ... -> bsv# -> bsvv#
	//                                      \-> boink0
	// ---------------------------------------------------------------------

	// Full voting window -1 for block that contained no votes and another
	// -1 because the TSpend can only be mined on a TVI.
	numVotingBlocks := int64(tvi*mul - 2)

	g.SetTip(startTip)
	targetPoolSize = g.Params().TicketPoolSize * ticketsPerBlock
	voteCount := params.TicketsPerBlock
	for i := int64(0); int64(g.Tip().Header.Height) <
		stakeValidationHeight+numVotingBlocks; i++ {

		// Only purchase tickets until the target ticket pool size is
		// reached.
		outs := g.OldestCoinbaseOuts()
		ticketOuts := outs[1:]
		if ticketsPurchased+len(ticketOuts) > int(targetPoolSize) {
			ticketsNeeded := int(targetPoolSize) - ticketsPurchased
			if ticketsNeeded > 0 {
				ticketOuts = ticketOuts[1 : ticketsNeeded+1]
			} else {
				ticketOuts = nil
			}
		}
		ticketsPurchased += len(ticketOuts)

		blockName := fmt.Sprintf("bsvv%d", i)
		g.NextBlock(blockName, nil, ticketOuts, replaceCoinbase,
			addTSpendVotes(t, []*chainhash.Hash{&tspendHash},
				[]stake.TreasuryVoteT{stake.TreasuryVoteYes},
				voteCount, false))
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
	}
	g.AssertTipHeight(uint32(stakeValidationHeight + numVotingBlocks))

	// ---------------------------------------------------------------------
	// Mine TSpend on TVI.
	//
	//   ... -> bse# -> bsv0 -> bsv1 -> ... -> bsv# -> bsvv# ->
	//                                      \-> boink0       \-> boink1
	// ---------------------------------------------------------------------

	g.NextBlock("boink1", nil, nil,
		replaceCoinbase,
		func(b *wire.MsgBlock) {
			// Add TSpend
			b.AddSTransaction(tspend)
		})
	g.RejectTipBlock(ErrInvalidTVoteWindow)
}

func TestTreasuryInRegularTree(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the provided
	// block by replacing the block, stake, and vote versions with the treasury
	// deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// addTreasuryBaseRegular is a munge function which modifies the provided
	// block to add a treasurybase to the regular transaction tree.
	addTreasuryBaseRegular := func(b *wire.MsgBlock) {
		txCopy := b.STransactions[0].Copy()
		txCopy.TxOut[1].PkScript[len(txCopy.TxOut[1].PkScript)-1] ^= 0x55
		b.AddTransaction(txCopy)
	}

	// moveTAddRegular is a munge function which modifies the provided block by
	// moving a treasury add transaction from the stake transaction tree to the
	// regular transaction tree.
	moveTAddRegular := func(b *wire.MsgBlock) {
		// Move TAdd to regular tree
		tAddIdx := len(b.STransactions) - 1
		b.Transactions = append(b.Transactions, b.STransactions[tAddIdx])
		b.STransactions = b.STransactions[:tAddIdx]
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// ---------------------------------------------------------------------
	// Append a treasurybase to transactions.
	// ---------------------------------------------------------------------

	outs := g.OldestCoinbaseOuts()
	startTip := g.TipName()
	g.NextBlock("tb0", nil, outs[1:], replaceTreasuryVersions, replaceCoinbase,
		addTreasuryBaseRegular)
	g.RejectTipBlock(ErrMultipleCoinbases)

	// ---------------------------------------------------------------------
	// Append tadd to transactions
	// ---------------------------------------------------------------------

	g.SetTip(startTip)
	g.NextBlock("tadd0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, createTAdd(outs[0], 15, params), moveTAddRegular)
	g.RejectTipBlock(ErrStakeTxInRegularTree)

	// ---------------------------------------------------------------------
	// Append TSpend to transactions.
	// ---------------------------------------------------------------------

	tspendAmount := uint64(devsub)
	tspendFee := uint64(0)
	tspend := g.CreateTreasuryTSpend(privKey, []chaingen.AddressAmountTuple{{
		Amount: dcrutil.Amount(tspendAmount - tspendFee),
	}}, dcrutil.Amount(tspendFee), 78) // Expiry check not hit.

	g.SetTip(startTip)
	g.NextBlock("tspend0", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, func(b *wire.MsgBlock) {
			b.AddTransaction(tspend)
		})
	g.RejectTipBlock(ErrStakeTxInRegularTree)
}

func TestTSpendVoteCountSynthetic(t *testing.T) {
	params := chaincfg.RegNetParams()
	tmh := params.TicketMaturity + 2 + 2 // Genesis + block 1 + 2 to reach tvi
	tvi := params.TreasuryVoteInterval
	mul := params.TreasuryVoteIntervalMultiplier
	tpb := params.TicketsPerBlock

	expiry := standalone.CalcTSpendExpiry(int64(tmh), tvi, mul)
	start, end, err := standalone.CalcTSpendWindow(expiry, tvi, mul)
	if err != nil {
		t.Fatal(err)
	}
	maxVotes := uint32(tpb) * (end - start)
	quorum := uint64(maxVotes) * params.TreasuryVoteQuorumMultiplier /
		params.TreasuryVoteQuorumDivisor
	requiredVotes := uint64(maxVotes) *
		params.TreasuryVoteRequiredMultiplier /
		params.TreasuryVoteRequiredDivisor

	// Create tspend.
	tspend := newFakeCreateTSpend(privKey, []dcrutil.Amount{100000000}, 1,
		expiry)
	tspendHash := tspend.TxHash()
	_, _, err = stake.CheckTSpend(tspend)
	if err != nil {
		t.Fatal(err)
	}

	// Create yes vote
	yesVote := newFakeCreateVoteTx([]treasuryVoteTuple{{
		tspendHash: tspendHash,
		vote:       0x01, // Yes
	}})
	_, err = stake.CheckSSGenVotes(yesVote)
	if err != nil {
		t.Fatal(err)
	}

	// Create no vote
	noVote := newFakeCreateVoteTx([]treasuryVoteTuple{{
		tspendHash: tspendHash,
		vote:       0x02, // No
	}})
	_, err = stake.CheckSSGenVotes(noVote)
	if err != nil {
		t.Fatal(err)
	}

	fakeTreasuryBase := &wire.MsgTx{}

	var ticketCount int // Overall ticket counter if needed in tests

	tests := []struct {
		name             string
		numNodes         int64
		set              func(*BlockChain, *blockNode)
		blockVersion     int32
		expectedYesVotes int
		expectedNoVotes  int
		failure          bool
	}{{
		name:             "All yes",
		numNodes:         int64(end),
		expectedYesVotes: int(tpb) * int(tvi*mul),
		expectedNoVotes:  0,
		failure:          false,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
					yesVote,
					yesVote,
					yesVote,
					yesVote,
					yesVote,
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "All no",
		numNodes:         int64(end),
		expectedYesVotes: 0,
		expectedNoVotes:  int(tpb) * int(tvi*mul),
		failure:          true,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
					noVote,
					noVote,
					noVote,
					noVote,
					noVote,
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "All abstain",
		numNodes:         int64(end),
		expectedYesVotes: 0,
		expectedNoVotes:  0,
		failure:          true,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) && node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "Undervote one tvi",
		numNodes:         int64(end),
		expectedYesVotes: 0,
		expectedNoVotes:  0,
		failure:          true,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			x := uint32(tvi) // subtract one tvi
			if node.height >= int64(start-x) &&
				node.height < int64(end-x) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "Overvote",
		numNodes:         int64(end),
		expectedYesVotes: 0,
		expectedNoVotes:  0,
		failure:          true,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			x := uint32(tvi) // add one tvi
			if node.height >= int64(start+x) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "All yes quorum",
		numNodes:         int64(end),
		expectedYesVotes: int(quorum),
		expectedNoVotes:  0,
		failure:          false,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}

				for i := 0; i < int(tpb); i++ {
					if ticketCount >= int(quorum) {
						break
					}
					mblk.STransactions = append(mblk.STransactions,
						yesVote)
					ticketCount++
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "All yes quorum - 1",
		numNodes:         int64(end),
		expectedYesVotes: int(quorum - 1),
		expectedNoVotes:  0,
		failure:          true,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}

				for i := 0; i < int(tpb); i++ {
					if ticketCount >= int(quorum-1) {
						break
					}
					mblk.STransactions = append(mblk.STransactions,
						yesVote)
					ticketCount++
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "All yes quorum + 1",
		numNodes:         int64(end),
		expectedYesVotes: int(quorum + 1),
		expectedNoVotes:  0,
		failure:          false,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}

				for i := 0; i < int(tpb); i++ {
					if ticketCount >= int(quorum+1) {
						break
					}
					mblk.STransactions = append(mblk.STransactions,
						yesVote)
					ticketCount++
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "Exactly yes required",
		numNodes:         int64(end),
		expectedYesVotes: int(requiredVotes),
		expectedNoVotes:  int(maxVotes) - int(requiredVotes),
		failure:          false,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}

				for i := 0; i < int(tpb); i++ {
					if ticketCount >= int(requiredVotes) {
						mblk.STransactions = append(mblk.STransactions,
							noVote)
					} else {
						mblk.STransactions = append(mblk.STransactions,
							yesVote)
					}
					ticketCount++
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "Yes required - 1",
		numNodes:         int64(end),
		expectedYesVotes: int(requiredVotes - 1),
		expectedNoVotes:  int(maxVotes) - int(requiredVotes-1),
		failure:          true,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}

				for i := 0; i < int(tpb); i++ {
					if ticketCount < int(requiredVotes-1) {
						mblk.STransactions = append(mblk.STransactions,
							yesVote)
					} else {
						mblk.STransactions = append(mblk.STransactions,
							noVote)
					}
					ticketCount++
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "Yes required + 1",
		numNodes:         int64(end),
		expectedYesVotes: int(requiredVotes + 1),
		expectedNoVotes:  int(maxVotes) - int(requiredVotes+1),
		failure:          false,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}

				for i := 0; i < int(tpb); i++ {
					if ticketCount < int(requiredVotes+1) {
						mblk.STransactions = append(mblk.STransactions,
							yesVote)
					} else {
						mblk.STransactions = append(mblk.STransactions,
							noVote)
					}
					ticketCount++
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}, {
		name:             "Exactly yes required with abstain",
		numNodes:         int64(end),
		expectedYesVotes: int(requiredVotes),
		expectedNoVotes:  0,
		failure:          false,
		set: func(bc *BlockChain, node *blockNode) {
			// Create block.
			bh := node.Header()
			mblk := wire.NewMsgBlock(&bh)

			// Append SSGen
			if node.height >= int64(start) &&
				node.height < int64(end) {
				mblk.STransactions = []*wire.MsgTx{
					fakeTreasuryBase,
				}

				for i := 0; i < int(tpb); i++ {
					if ticketCount >= int(requiredVotes) {
						// Do nothing.
					} else {
						mblk.STransactions = append(mblk.STransactions,
							yesVote)
					}
					ticketCount++
				}
			}

			// Insert block into the cache.
			bc.addRecentBlock(dcrutil.NewBlock(mblk))
		},
	}}

	for _, test := range tests {
		// Create new BlockChain in order to blow away cache.  Also notice the
		// cache needs to be large enough to hold all of the blocks for these
		// tests without eviction.
		bc := newFakeChain(params)
		node := bc.bestChain.Tip()
		bc.recentBlocks = lru.NewKVCache(uint(test.numNodes))

		ticketCount = 0
		for i := int64(0); i < test.numNodes; i++ {
			// Version 9 is regnet decentralized treasury agenda.
			node = newFakeNode(node, test.blockVersion,
				9, 0, time.Now())

			// Override version.
			if test.set != nil {
				test.set(bc, node)
			}

			bc.index.AddNode(node)
			bc.bestChain.SetTip(node)
		}

		if !standalone.IsTreasuryVoteInterval(uint64(node.height), tvi) {
			t.Fatalf("Not a TVI: %v", node.height)
		}

		tv, err := bc.tSpendCountVotes(node.parent, dcrutil.NewTx(tspend))
		if err != nil {
			t.Fatal(err)
		}
		if tv.start != start {
			t.Fatalf("invalid start want %v got %v", start, start)
		}
		if tv.end != end {
			t.Fatalf("invalid end want %v got %v", end, end)
		}
		if tv.yes != test.expectedYesVotes {
			t.Fatalf("invalid yes want %v got %v",
				test.expectedYesVotes, tv.yes)
		}
		if tv.no != test.expectedNoVotes {
			t.Fatalf("invalid no want %v got %v",
				test.expectedNoVotes, tv.no)
		}

		// Check error
		err = bc.checkTSpendHasVotes(node.parent, dcrutil.NewTx(tspend))
		if test.failure {
			if err == nil {
				t.Fatalf("expected failure")
			}
		} else {
			if err != nil {
				t.Fatalf("%v: %v", test.name, err)
			}
		}
	}
}

// TestTSpendTooManyTAdds tests that a block that contains more TAdds than
// allowed by consensus rules is rejected while a block containing exactly the
// maximum amount is accepted.
func TestTSpendTooManyTAdds(t *testing.T) {
	t.Parallel()

	// Use a set of test chain parameters which allow for quicker vote
	// activation as compared to various existing network params.
	params := quickVoteActivationParams()

	// Clone the parameters so they can be mutated, find the correct
	// deployment for the treasury agenda, and, finally, ensure it is
	// always available to vote by removing the time constraints to prevent
	// test failures when the real expiration time passes.
	const tVoteID = chaincfg.VoteIDTreasury
	params = cloneParams(params)
	tVersion, deployment := findDeployment(t, params, tVoteID)
	removeDeploymentTimeConstraints(deployment)

	// Create a test harness initialized with the genesis block as the tip.
	g := newChaingenHarness(t, params)

	// replaceTreasuryVersions is a munge function which modifies the
	// provided block by replacing the block, stake, and vote versions with
	// the treasury deployment version.
	replaceTreasuryVersions := func(b *wire.MsgBlock) {
		chaingen.ReplaceBlockVersion(int32(tVersion))(b)
		chaingen.ReplaceStakeVersion(tVersion)(b)
		chaingen.ReplaceVoteVersions(tVersion)(b)
	}

	// addTAdds is a helper function that returns a munge function that
	// includes the specified tadds in the stake tree of a block.
	addTAdds := func(tadds []*wire.MsgTx) func(b *wire.MsgBlock) {
		return func(b *wire.MsgBlock) {
			for _, tx := range tadds {
				b.AddSTransaction(tx)
			}
		}
	}

	// ---------------------------------------------------------------------
	// Generate and accept enough blocks with the appropriate vote bits set
	// to reach one block prior to the treasury agenda becoming active.
	// ---------------------------------------------------------------------

	g.AdvanceToStakeValidationHeight()
	g.AdvanceFromSVHToActiveAgendas(tVoteID)

	// Ensure treasury agenda is active.
	tipHash := &g.chain.BestSnapshot().Hash
	gotActive, err := g.chain.IsTreasuryAgendaActive(tipHash)
	if err != nil {
		t.Fatalf("IsTreasuryAgendaActive: %v", err)
	}
	if !gotActive {
		t.Fatalf("IsTreasuryAgendaActive: expected enabled treasury")
	}

	// The maximum number of tadds is hardcoded here instead of using a
	// global const so that any code changes that unintentionally cause
	// this maximum to change in blockchain code are flagged by this test.
	maxNbTadds := 20

	// ---------------------------------------------------------------------
	// Generate enough blocks to have spendable outputs to create the
	// required number of tadds. Due to having lowered the coinbase
	// maturity, we'll need to manually generate and store these outputs
	// until we have enough.
	//
	//   ... -> bout0 -> .. boutn
	// ---------------------------------------------------------------------
	neededOuts := maxNbTadds + 1
	var oldOuts []chaingen.SpendableOut
	outs := g.OldestCoinbaseOuts()
	for i := uint32(0); i < uint32(neededOuts); i++ {
		name := fmt.Sprintf("bout%v", i)
		g.NextBlock(name, nil, outs[1:], replaceTreasuryVersions,
			replaceCoinbase)
		g.SaveTipCoinbaseOutsWithTreasury()
		g.AcceptTipBlock()
		outs = g.OldestCoinbaseOuts()
		oldOuts = append(oldOuts, outs[0])
	}

	// Create more than the maximum allowed number of TAdds.
	taddAmount := int64(1701)
	taddFee := dcrutil.Amount(0)
	tadds := make([]*wire.MsgTx, 0, maxNbTadds+1)
	for i := 0; i < maxNbTadds+1; i++ {
		tadd := g.CreateTreasuryTAdd(&oldOuts[i], dcrutil.Amount(taddAmount), taddFee)
		tadd.Version = wire.TxVersionTreasury
		tadds = append(tadds, tadd)
	}

	// ---------------------------------------------------------------------
	// Attempt to mine a block with an invalid number of tadds.
	//
	// ... -> boutn -> btoomanytadds
	// ---------------------------------------------------------------------
	tipName := g.TipName()
	g.NextBlock("btoomanytadds", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, addTAdds(tadds))
	g.SaveTipCoinbaseOutsWithTreasury()
	g.RejectTipBlock(ErrTooManyTAdds)

	// ---------------------------------------------------------------------
	// Mine a block with the maximum number of tadds.
	//
	// ... -> boutn -> btoomanytadds
	//             \-> bmaxtadds
	// ---------------------------------------------------------------------
	g.SetTip(tipName)
	g.NextBlock("bmaxtadds", nil, outs[1:], replaceTreasuryVersions,
		replaceCoinbase, addTAdds(tadds[1:]))
	g.SaveTipCoinbaseOutsWithTreasury()
	g.AcceptTipBlock()
}
