lbcwallet/wallet/txauthor/author_test.go
Josh Rickmar 7b2e1ac282 Do not include zero value change outputs.
Due to the way dust is calculated, if the transaction relay fee is
zero, then a zero output amount is not considered dust.  As the
transaction authoring code used this dust check to determine whether a
change output can be included or not, it could create unnecessary
change outputs which return no value back to the wallet.  Prevent this
by including an explicit check for zero values.
2016-04-20 10:46:12 -04:00

219 lines
6.6 KiB
Go

// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txauthor_test
import (
"testing"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
. "github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/btcsuite/btcwallet/wallet/internal/txsizes"
)
func p2pkhOutputs(amounts ...btcutil.Amount) []*wire.TxOut {
v := make([]*wire.TxOut, 0, len(amounts))
for _, a := range amounts {
outScript := make([]byte, txsizes.P2PKHOutputSize)
v = append(v, wire.NewTxOut(int64(a), outScript))
}
return v
}
func makeInputSource(unspents []*wire.TxOut) InputSource {
// Return outputs in order.
currentTotal := btcutil.Amount(0)
currentInputs := make([]*wire.TxIn, 0, len(unspents))
f := func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, [][]byte, error) {
for currentTotal < target && len(unspents) != 0 {
u := unspents[0]
unspents = unspents[1:]
nextInput := wire.NewTxIn(&wire.OutPoint{}, nil)
currentTotal += btcutil.Amount(u.Value)
currentInputs = append(currentInputs, nextInput)
}
return currentTotal, currentInputs, make([][]byte, len(currentInputs)), nil
}
return InputSource(f)
}
func TestNewUnsignedTransaction(t *testing.T) {
tests := []struct {
UnspentOutputs []*wire.TxOut
Outputs []*wire.TxOut
RelayFee btcutil.Amount
ChangeAmount btcutil.Amount
InputSourceError bool
InputCount int
}{
0: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8),
RelayFee: 1e3,
InputSourceError: true,
},
1: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e6),
RelayFee: 1e3,
ChangeAmount: 1e8 - 1e6 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(1e6), true)),
InputCount: 1,
},
2: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e6),
RelayFee: 1e4,
ChangeAmount: 1e8 - 1e6 - txrules.FeeForSerializeSize(1e4,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(1e6), true)),
InputCount: 1,
},
3: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e6, 1e6, 1e6),
RelayFee: 1e4,
ChangeAmount: 1e8 - 3e6 - txrules.FeeForSerializeSize(1e4,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(1e6, 1e6, 1e6), true)),
InputCount: 1,
},
4: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e6, 1e6, 1e6),
RelayFee: 2.55e3,
ChangeAmount: 1e8 - 3e6 - txrules.FeeForSerializeSize(2.55e3,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(1e6, 1e6, 1e6), true)),
InputCount: 1,
},
// Test dust thresholds (546 for a 1e3 relay fee).
5: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8 - 545 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(0), true))),
RelayFee: 1e3,
ChangeAmount: 0,
InputCount: 1,
},
6: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8 - 546 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(0), true))),
RelayFee: 1e3,
ChangeAmount: 546,
InputCount: 1,
},
// Test dust thresholds (1392.3 for a 2.55e3 relay fee).
7: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8 - 1392 - txrules.FeeForSerializeSize(2.55e3,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(0), true))),
RelayFee: 2.55e3,
ChangeAmount: 0,
InputCount: 1,
},
8: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8 - 1393 - txrules.FeeForSerializeSize(2.55e3,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(0), true))),
RelayFee: 2.55e3,
ChangeAmount: 1393,
InputCount: 1,
},
// Test two unspent outputs available but only one needed
// (tested fee only includes one input rather than using a
// serialize size for each).
9: {
UnspentOutputs: p2pkhOutputs(1e8, 1e8),
Outputs: p2pkhOutputs(1e8 - 546 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(0), true))),
RelayFee: 1e3,
ChangeAmount: 546,
InputCount: 1,
},
// Test that second output is not included to make the change
// output not dust and be included in the transaction.
//
// It's debatable whether or not this is a good idea, but it's
// how the function was written, so test it anyways.
10: {
UnspentOutputs: p2pkhOutputs(1e8, 1e8),
Outputs: p2pkhOutputs(1e8 - 545 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateSerializeSize(1, p2pkhOutputs(0), true))),
RelayFee: 1e3,
ChangeAmount: 0,
InputCount: 1,
},
// Test two unspent outputs available where both are needed.
11: {
UnspentOutputs: p2pkhOutputs(1e8, 1e8),
Outputs: p2pkhOutputs(1e8),
RelayFee: 1e3,
ChangeAmount: 1e8 - txrules.FeeForSerializeSize(1e3,
txsizes.EstimateSerializeSize(2, p2pkhOutputs(1e8), true)),
InputCount: 2,
},
// Test that zero change outputs are not included
// (ChangeAmount=0 means don't include any change output).
12: {
UnspentOutputs: p2pkhOutputs(1e8),
Outputs: p2pkhOutputs(1e8),
RelayFee: 0,
ChangeAmount: 0,
InputCount: 1,
},
}
changeSource := func() ([]byte, error) {
// Only length matters for these tests.
return make([]byte, txsizes.P2PKHPkScriptSize), nil
}
for i, test := range tests {
inputSource := makeInputSource(test.UnspentOutputs)
tx, err := NewUnsignedTransaction(test.Outputs, test.RelayFee, inputSource, changeSource)
switch e := err.(type) {
case nil:
case InputSourceError:
if !test.InputSourceError {
t.Errorf("Test %d: Returned InputSourceError but expected "+
"change output with amount %v", i, test.ChangeAmount)
}
continue
default:
t.Errorf("Test %d: Unexpected error: %v", i, e)
continue
}
if tx.ChangeIndex < 0 {
if test.ChangeAmount != 0 {
t.Errorf("Test %d: No change output added but expected output with amount %v",
i, test.ChangeAmount)
continue
}
} else {
changeAmount := btcutil.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value)
if test.ChangeAmount == 0 {
t.Errorf("Test %d: Included change output with value %v but expected no change",
i, changeAmount)
continue
}
if changeAmount != test.ChangeAmount {
t.Errorf("Test %d: Got change amount %v, Expected %v",
i, changeAmount, test.ChangeAmount)
continue
}
}
if len(tx.Tx.TxIn) != test.InputCount {
t.Errorf("Test %d: Used %d outputs from input source, Expected %d",
i, len(tx.Tx.TxIn), test.InputCount)
}
}
}