func TestRollbackLastOutputWhenNewInputAdded(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	net := pool.Manager().ChainParams()
	series, eligible := tstCreateCreditsOnNewSeries(t, dbtx, pool, []int64{6, 5, 4, 3, 2, 1})
	requests := []OutputRequest{
		TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", 1, net),
		TstNewOutputRequest(t, 3, "3Qt1EaKRD9g9FeL2DGkLLswhK1AKmmXFSe", 6, net),
		TstNewOutputRequest(t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", 3, net),
	}
	changeStart := TstNewChangeAddress(t, pool, series, 0)
	w := newWithdrawal(0, requests, eligible, *changeStart)
	w.txOptions = func(tx *withdrawalTx) {
		tx.calculateFee = TstConstantFee(0)
		tx.calculateSize = func() int {
			if len(tx.inputs) > 3 {
				return txMaxSize + 1
			}
			return txMaxSize - 1
		}
	}
	if err := w.fulfillRequests(); err != nil {
		t.Fatal("Unexpected error:", err)
	}
	if len(w.transactions) != 2 {
		t.Fatalf("Wrong number of finalized transactions; got %d, want 2", len(w.transactions))
	}
	firstTx := w.transactions[0]
	req1 := requests[0]
	checkTxOutputs(t, firstTx,
		[]*withdrawalTxOut{{request: req1, amount: req1.Amount}})
	checkTxInputs(t, firstTx, eligible[5:6])
	secondTx := w.transactions[1]
	wantOutputs := []*withdrawalTxOut{
		{request: requests[1], amount: requests[1].Amount},
		{request: requests[2], amount: requests[2].Amount}}
	checkTxOutputs(t, secondTx, wantOutputs)
	wantInputs := []Credit{eligible[4], eligible[3], eligible[2]}
	checkTxInputs(t, secondTx, wantInputs)
}
TestRollbackLastOutputWhenNewInputAdded checks that we roll back the last
output if a tx becomes too big right after we add a new input to it.
package votingpool
import (
	"bytes"
	"reflect"
	"sort"
	"testing"
	"github.com/gcash/bchd/chaincfg"
	"github.com/gcash/bchd/txscript"
	"github.com/gcash/bchd/wire"
	"github.com/gcash/bchutil"
	"github.com/gcash/bchutil/hdkeychain"
	"github.com/gcash/bchwallet/waddrmgr"
	"github.com/gcash/bchwallet/walletdb"
	"github.com/gcash/bchwallet/wtxmgr"
)
func TestOutputSplittingNotEnoughInputs(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	net := pool.Manager().ChainParams()
	output1Amount := bchutil.Amount(2)
	output2Amount := bchutil.Amount(3)
	requests := []OutputRequest{
		TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", output1Amount, net),
		TstNewOutputRequest(t, 2, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", output2Amount, net),
	}
	seriesID, eligible := tstCreateCreditsOnNewSeries(t, dbtx, pool, []int64{7})
	w := newWithdrawal(0, requests, eligible, *TstNewChangeAddress(t, pool, seriesID, 0))
	w.txOptions = func(tx *withdrawalTx) {
		tx.calculateFee = TstConstantFee(3)
	}
	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}
	if len(w.transactions) != 1 {
		t.Fatalf("Wrong number of finalized transactions; got %d, want 1", len(w.transactions))
	}
	tx := w.transactions[0]
	if len(tx.outputs) != 2 {
		t.Fatalf("Wrong number of outputs; got %d, want 2", len(tx.outputs))
	}
	if tx.outputs[0].amount != output1Amount {
		t.Fatalf("Wrong amount for first tx output; got %v, want %v",
			tx.outputs[0].amount, output1Amount)
	}
	newAmount := tx.inputTotal() - output1Amount - tx.calculateFee()
	checkLastOutputWasSplit(t, w, tx, output2Amount, newAmount)
}
func TestOutputSplittingOversizeTx(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	requestAmount := bchutil.Amount(5)
	bigInput := int64(3)
	smallInput := int64(2)
	request := TstNewOutputRequest(
		t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", requestAmount, pool.Manager().ChainParams())
	seriesID, eligible := tstCreateCreditsOnNewSeries(t, dbtx, pool, []int64{smallInput, bigInput})
	changeStart := TstNewChangeAddress(t, pool, seriesID, 0)
	w := newWithdrawal(0, []OutputRequest{request}, eligible, *changeStart)
	w.txOptions = func(tx *withdrawalTx) {
		tx.calculateFee = TstConstantFee(0)
		tx.calculateSize = func() int {
			if len(tx.inputs) == 2 {
				return txMaxSize + 1
			}
			return txMaxSize - 1
		}
	}
	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}
	if len(w.transactions) != 2 {
		t.Fatalf("Wrong number of finalized transactions; got %d, want 2", len(w.transactions))
	}
	tx1 := w.transactions[0]
	if len(tx1.outputs) != 1 {
		t.Fatalf("Wrong number of outputs on tx1; got %d, want 1", len(tx1.outputs))
	}
	if tx1.outputs[0].amount != bchutil.Amount(bigInput) {
		t.Fatalf("Wrong amount for output in tx1; got %d, want %d", tx1.outputs[0].amount,
			bigInput)
	}
	tx2 := w.transactions[1]
	if len(tx2.outputs) != 1 {
		t.Fatalf("Wrong number of outputs on tx2; got %d, want 1", len(tx2.outputs))
	}
	if tx2.outputs[0].amount != bchutil.Amount(smallInput) {
		t.Fatalf("Wrong amount for output in tx2; got %d, want %d", tx2.outputs[0].amount,
			smallInput)
	}
	if len(w.status.outputs) != 1 {
		t.Fatalf("Wrong number of output statuses; got %d, want 1", len(w.status.outputs))
	}
	status := w.status.outputs[request.outBailmentID()].status
	if status != statusSplit {
		t.Fatalf("Wrong output status; got '%s', want '%s'", status, statusSplit)
	}
}
func TestSplitLastOutputNoOutputs(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	w := newWithdrawal(0, []OutputRequest{}, []Credit{}, ChangeAddress{})
	w.current = createWithdrawalTx(t, dbtx, pool, []int64{}, []int64{})
	err = w.splitLastOutput()
	TstCheckError(t, "", err, ErrPreconditionNotMet)
}
func TestWithdrawalTxOutputs(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	net := pool.Manager().ChainParams()
	seriesID, eligible := tstCreateCreditsOnNewSeries(t, dbtx, pool, []int64{2e6, 4e6})
	outputs := []OutputRequest{
		TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", 3e6, net),
		TstNewOutputRequest(t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", 2e6, net),
	}
	changeStart := TstNewChangeAddress(t, pool, seriesID, 0)
	w := newWithdrawal(0, outputs, eligible, *changeStart)
	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}
	if len(w.transactions) != 1 {
		t.Fatalf("Unexpected number of transactions; got %d, want 1", len(w.transactions))
	}
	tx := w.transactions[0]
	inputAmount := eligible[0].Amount + eligible[1].Amount
	change := inputAmount - (outputs[0].Amount + outputs[1].Amount + tx.calculateFee())
	expectedOutputs := append(
		outputs, TstNewOutputRequest(t, 3, changeStart.addr.String(), change, net))
	msgtx := tx.toMsgTx()
	checkMsgTxOutputs(t, msgtx, expectedOutputs)
}
func TestFulfillRequestsNoSatisfiableOutputs(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	seriesID, eligible := tstCreateCreditsOnNewSeries(t, dbtx, pool, []int64{1e6})
	request := TstNewOutputRequest(
		t, 1, "3Qt1EaKRD9g9FeL2DGkLLswhK1AKmmXFSe", bchutil.Amount(3e6), pool.Manager().ChainParams())
	changeStart := TstNewChangeAddress(t, pool, seriesID, 0)
	w := newWithdrawal(0, []OutputRequest{request}, eligible, *changeStart)
	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}
	if len(w.transactions) != 0 {
		t.Fatalf("Unexpected number of transactions; got %d, want 0", len(w.transactions))
	}
	if len(w.status.outputs) != 1 {
		t.Fatalf("Unexpected number of outputs in WithdrawalStatus; got %d, want 1",
			len(w.status.outputs))
	}
	status := w.status.outputs[request.outBailmentID()].status
	if status != statusPartial {
		t.Fatalf("Unexpected status for requested outputs; got '%s', want '%s'",
			status, statusPartial)
	}
}
func TestFulfillRequestsNotEnoughCreditsForAllRequests(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	net := pool.Manager().ChainParams()
	seriesID, eligible := tstCreateCreditsOnNewSeries(t, dbtx, pool, []int64{2e6, 4e6})
	out1 := TstNewOutputRequest(
		t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", bchutil.Amount(3e6), net)
	out2 := TstNewOutputRequest(
		t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", bchutil.Amount(2e6), net)
	out3 := TstNewOutputRequest(
		t, 3, "3Qt1EaKRD9g9FeL2DGkLLswhK1AKmmXFSe", bchutil.Amount(5e6), net)
	outputs := []OutputRequest{out1, out2, out3}
	changeStart := TstNewChangeAddress(t, pool, seriesID, 0)
	w := newWithdrawal(0, outputs, eligible, *changeStart)
	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}
	tx := w.transactions[0]
	inputAmount := eligible[0].Amount + eligible[1].Amount
	change := inputAmount - (out1.Amount + out2.Amount + tx.calculateFee())
	expectedOutputs := []OutputRequest{out1, out2}
	sort.Sort(byOutBailmentID(expectedOutputs))
	expectedOutputs = append(
		expectedOutputs, TstNewOutputRequest(t, 4, changeStart.addr.String(), change, net))
	msgtx := tx.toMsgTx()
	checkMsgTxOutputs(t, msgtx, expectedOutputs)
	expectedStatuses := map[OutBailmentID]outputStatus{
		out1.outBailmentID(): statusSuccess,
		out2.outBailmentID(): statusSuccess,
		out3.outBailmentID(): statusPartial}
	for _, wOutput := range w.status.outputs {
		if wOutput.status != expectedStatuses[wOutput.request.outBailmentID()] {
			t.Fatalf("Unexpected status for %v; got '%s', want '%s'", wOutput.request,
				wOutput.status, expectedStatuses[wOutput.request.outBailmentID()])
		}
	}
}
func TestRollbackLastOutput(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	tx := createWithdrawalTx(t, dbtx, pool, []int64{3, 3, 2, 1, 3}, []int64{3, 3, 2, 2})
	initialInputs := tx.inputs
	initialOutputs := tx.outputs
	tx.calculateFee = TstConstantFee(1)
	removedInputs, removedOutput, err := tx.rollBackLastOutput()
	if err != nil {
		t.Fatal("Unexpected error:", err)
	}
	lastOutput := initialOutputs[len(initialOutputs)-1]
	if removedOutput != lastOutput {
		t.Fatalf("Wrong rolled back output; got %s want %s", removedOutput, lastOutput)
	}
	if len(removedInputs) != 1 {
		t.Fatalf("Unexpected number of inputs removed; got %d, want 1", len(removedInputs))
	}
	lastInput := initialInputs[len(initialInputs)-1]
	if !reflect.DeepEqual(removedInputs[0], lastInput) {
		t.Fatalf("Wrong rolled back input; got %v want %v", removedInputs[0], lastInput)
	}
	checkTxOutputs(t, tx, initialOutputs[:len(initialOutputs)-1])
	checkTxInputs(t, tx, initialInputs[:len(initialInputs)-1])
}
func TestRollbackLastOutputMultipleInputsRolledBack(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	tx := createWithdrawalTx(t, dbtx, pool, []int64{1, 2, 3, 4}, []int64{1, 8})
	initialInputs := tx.inputs
	initialOutputs := tx.outputs
	tx.calculateFee = TstConstantFee(0)
	removedInputs, _, err := tx.rollBackLastOutput()
	if err != nil {
		t.Fatal("Unexpected error:", err)
	}
	if len(removedInputs) != 3 {
		t.Fatalf("Unexpected number of inputs removed; got %d, want 3", len(removedInputs))
	}
	for i, amount := range []bchutil.Amount{4, 3, 2} {
		if removedInputs[i].Amount != amount {
			t.Fatalf("Unexpected input amount; got %v, want %v", removedInputs[i].Amount, amount)
		}
	}
	checkTxOutputs(t, tx, initialOutputs[:len(initialOutputs)-1])
	checkTxInputs(t, tx, initialInputs[:len(initialInputs)-len(removedInputs)])
}
func TestRollbackLastOutputNoInputsRolledBack(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	tx := createWithdrawalTx(t, dbtx, pool, []int64{4}, []int64{2, 3})
	initialInputs := tx.inputs
	initialOutputs := tx.outputs
	tx.calculateFee = TstConstantFee(1)
	removedInputs, removedOutput, err := tx.rollBackLastOutput()
	if err != nil {
		t.Fatal("Unexpected error:", err)
	}
	lastOutput := initialOutputs[len(initialOutputs)-1]
	if removedOutput != lastOutput {
		t.Fatalf("Wrong output; got %s want %s", removedOutput, lastOutput)
	}
	if len(removedInputs) != 0 {
		t.Fatalf("Expected no removed inputs, but got %d inputs", len(removedInputs))
	}
	checkTxOutputs(t, tx, initialOutputs[:len(initialOutputs)-1])
	checkTxInputs(t, tx, initialInputs)
}
func TestRollBackLastOutputInsufficientOutputs(t *testing.T) {
	tx := newWithdrawalTx(defaultTxOptions)
	_, _, err := tx.rollBackLastOutput()
	TstCheckError(t, "", err, ErrPreconditionNotMet)
	output := &WithdrawalOutput{request: TstNewOutputRequest(
		t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", bchutil.Amount(3), &chaincfg.MainNetParams)}
	tx.addOutput(output.request)
	_, _, err = tx.rollBackLastOutput()
	TstCheckError(t, "", err, ErrPreconditionNotMet)
}
func TestRollbackLastOutputWhenNewOutputAdded(t *testing.T) {
	tearDown, db, pool := TstCreatePool(t)
	defer tearDown()
	dbtx, err := db.BeginReadWriteTx()
	if err != nil {
		t.Fatal(err)
	}
	defer dbtx.Commit()
	net := pool.Manager().ChainParams()
	series, eligible := tstCreateCreditsOnNewSeries(t, dbtx, pool, []int64{5, 5})
	requests := []OutputRequest{
		TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", 1, net),
		TstNewOutputRequest(t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", 2, net),
	}
	changeStart := TstNewChangeAddress(t, pool, series, 0)
	w := newWithdrawal(0, requests, eligible, *changeStart)
	w.txOptions = func(tx *withdrawalTx) {
		tx.calculateFee = TstConstantFee(0)
		tx.calculateSize = func() int {
			if len(tx.outputs) > 1 {
				return txMaxSize + 1
			}
			return txMaxSize - 1
		}
	}
	if err := w.fulfillRequests(); err != nil {
		t.Fatal("Unexpected error:", err)
	}
	if len(w.transactions) != 2 {
		t.Fatalf("Wrong number of finalized transactions; got %d, want 2", len(w.transactions))
	}
	firstTx := w.transactions[0]
	req1 := requests[0]
	checkTxOutputs(t, firstTx,
		[]*withdrawalTxOut{{request: req1, amount: req1.Amount}})
	checkTxChangeAmount(t, firstTx, bchutil.Amount(4))
	secondTx := w.transactions[1]
	req2 := requests[1]
	checkTxOutputs(t, secondTx,
		[]*withdrawalTxOut{{request: req2, amount: req2.Amount}})
	checkTxChangeAmount(t, secondTx, bchutil.Amount(3))
}