2016-02-28 05:30:56 +01:00
|
|
|
// 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"
|
|
|
|
|
2016-05-05 21:04:36 +02:00
|
|
|
"github.com/roasbeef/btcd/wire"
|
|
|
|
"github.com/roasbeef/btcutil"
|
|
|
|
. "github.com/roasbeef/btcwallet/wallet/txauthor"
|
|
|
|
"github.com/roasbeef/btcwallet/wallet/txrules"
|
2016-02-28 05:30:56 +01:00
|
|
|
|
2016-05-05 21:04:36 +02:00
|
|
|
"github.com/roasbeef/btcwallet/wallet/internal/txsizes"
|
2016-02-28 05:30:56 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
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,
|
|
|
|
},
|
2016-04-18 18:47:09 +02:00
|
|
|
|
|
|
|
// 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,
|
|
|
|
},
|
2016-02-28 05:30:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2016-04-18 18:47:09 +02:00
|
|
|
if test.ChangeAmount == 0 {
|
|
|
|
t.Errorf("Test %d: Included change output with value %v but expected no change",
|
|
|
|
i, changeAmount)
|
|
|
|
continue
|
|
|
|
}
|
2016-02-28 05:30:56 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|