decred.org/dcrwallet/v3@v3.1.0/wallet/txauthor/author_test.go (about)

     1  // Copyright (c) 2016 The btcsuite developers
     2  // Copyright (c) 2016 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package txauthor_test
     7  
     8  import (
     9  	"testing"
    10  
    11  	"decred.org/dcrwallet/v3/errors"
    12  	"decred.org/dcrwallet/v3/wallet/txauthor"
    13  	. "decred.org/dcrwallet/v3/wallet/txauthor"
    14  	"decred.org/dcrwallet/v3/wallet/txrules"
    15  	"decred.org/dcrwallet/v3/wallet/txsizes"
    16  	"github.com/decred/dcrd/chaincfg/v3"
    17  	"github.com/decred/dcrd/dcrutil/v4"
    18  	"github.com/decred/dcrd/wire"
    19  )
    20  
    21  type AuthorTestChangeSource struct{}
    22  
    23  func (src AuthorTestChangeSource) Script() ([]byte, uint16, error) {
    24  	// Only length matters for these tests.
    25  	return make([]byte, txsizes.P2PKHPkScriptSize), 0, nil
    26  }
    27  
    28  func (src AuthorTestChangeSource) ScriptSize() int {
    29  	return txsizes.P2PKHPkScriptSize
    30  }
    31  
    32  func p2pkhOutputs(amounts ...dcrutil.Amount) []*wire.TxOut {
    33  	v := make([]*wire.TxOut, 0, len(amounts))
    34  	for _, a := range amounts {
    35  		outScript := make([]byte, txsizes.P2PKHOutputSize)
    36  		v = append(v, wire.NewTxOut(int64(a), outScript))
    37  	}
    38  	return v
    39  }
    40  
    41  func makeInputSource(unspents []*wire.TxOut) InputSource {
    42  	// Return outputs in order.
    43  	currentTotal := dcrutil.Amount(0)
    44  	currentInputs := make([]*wire.TxIn, 0, len(unspents))
    45  	redeemScriptSizes := make([]int, 0, len(unspents))
    46  	f := func(target dcrutil.Amount) (*InputDetail, error) {
    47  		for currentTotal < target && len(unspents) != 0 {
    48  			u := unspents[0]
    49  			unspents = unspents[1:]
    50  			nextInput := wire.NewTxIn(&wire.OutPoint{}, u.Value, nil)
    51  			currentTotal += dcrutil.Amount(u.Value)
    52  			currentInputs = append(currentInputs, nextInput)
    53  			redeemScriptSizes = append(redeemScriptSizes, txsizes.RedeemP2PKHSigScriptSize)
    54  		}
    55  
    56  		inputDetail := txauthor.InputDetail{
    57  			Amount:            currentTotal,
    58  			Inputs:            currentInputs,
    59  			Scripts:           make([][]byte, len(currentInputs)),
    60  			RedeemScriptSizes: redeemScriptSizes,
    61  		}
    62  		return &inputDetail, nil
    63  	}
    64  	return InputSource(f)
    65  }
    66  
    67  func TestNewUnsignedTransaction(t *testing.T) {
    68  	tests := []struct {
    69  		UnspentOutputs   []*wire.TxOut
    70  		Outputs          []*wire.TxOut
    71  		RelayFee         dcrutil.Amount
    72  		ChangeAmount     dcrutil.Amount
    73  		InputSourceError bool
    74  		InputCount       int
    75  	}{
    76  		0: {
    77  			UnspentOutputs:   p2pkhOutputs(1e8),
    78  			Outputs:          p2pkhOutputs(1e8),
    79  			RelayFee:         1e3,
    80  			InputSourceError: true,
    81  		},
    82  		1: {
    83  			UnspentOutputs: p2pkhOutputs(1e8),
    84  			Outputs:        p2pkhOutputs(1e6),
    85  			RelayFee:       1e3,
    86  			ChangeAmount: 1e8 - 1e6 - txrules.FeeForSerializeSize(1e3,
    87  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(1e6), txsizes.P2PKHPkScriptSize)),
    88  			InputCount: 1,
    89  		},
    90  		2: {
    91  			UnspentOutputs: p2pkhOutputs(1e8),
    92  			Outputs:        p2pkhOutputs(1e6),
    93  			RelayFee:       1e4,
    94  			ChangeAmount: 1e8 - 1e6 - txrules.FeeForSerializeSize(1e4,
    95  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(1e6), txsizes.P2PKHPkScriptSize)),
    96  			InputCount: 1,
    97  		},
    98  		3: {
    99  			UnspentOutputs: p2pkhOutputs(1e8),
   100  			Outputs:        p2pkhOutputs(1e6, 1e6, 1e6),
   101  			RelayFee:       1e4,
   102  			ChangeAmount: 1e8 - 3e6 - txrules.FeeForSerializeSize(1e4,
   103  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(1e6, 1e6, 1e6), txsizes.P2PKHPkScriptSize)),
   104  			InputCount: 1,
   105  		},
   106  		4: {
   107  			UnspentOutputs: p2pkhOutputs(1e8),
   108  			Outputs:        p2pkhOutputs(1e6, 1e6, 1e6),
   109  			RelayFee:       2.55e3,
   110  			ChangeAmount: 1e8 - 3e6 - txrules.FeeForSerializeSize(2.55e3,
   111  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(1e6, 1e6, 1e6), txsizes.P2PKHPkScriptSize)),
   112  			InputCount: 1,
   113  		},
   114  
   115  		// Test dust thresholds (603 for a 1e3 relay fee).
   116  		5: {
   117  			UnspentOutputs: p2pkhOutputs(1e8),
   118  			Outputs: p2pkhOutputs(1e8 - 602 - txrules.FeeForSerializeSize(1e3,
   119  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(0), txsizes.P2PKHPkScriptSize))),
   120  			RelayFee:     1e3,
   121  			ChangeAmount: 0,
   122  			InputCount:   1,
   123  		},
   124  		6: {
   125  			UnspentOutputs: p2pkhOutputs(1e8),
   126  			Outputs: p2pkhOutputs(1e8 - 603 - txrules.FeeForSerializeSize(1e3,
   127  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(0), txsizes.P2PKHPkScriptSize))),
   128  			RelayFee:     1e3,
   129  			ChangeAmount: 603,
   130  			InputCount:   1,
   131  		},
   132  
   133  		// Test dust thresholds (1537.65 for a 2.55e3 relay fee).
   134  		7: {
   135  			UnspentOutputs: p2pkhOutputs(1e8),
   136  			Outputs: p2pkhOutputs(1e8 - 1537 - txrules.FeeForSerializeSize(2.55e3,
   137  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(0), txsizes.P2PKHPkScriptSize))),
   138  			RelayFee:     2.55e3,
   139  			ChangeAmount: 0,
   140  			InputCount:   1,
   141  		},
   142  		8: {
   143  			UnspentOutputs: p2pkhOutputs(1e8),
   144  			Outputs: p2pkhOutputs(1e8 - 1538 - txrules.FeeForSerializeSize(2.55e3,
   145  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(0), txsizes.P2PKHPkScriptSize))),
   146  			RelayFee:     2.55e3,
   147  			ChangeAmount: 1538,
   148  			InputCount:   1,
   149  		},
   150  
   151  		// Test two unspent outputs available but only one needed
   152  		// (tested fee only includes one input rather than using a
   153  		// serialize size for each).
   154  		9: {
   155  			UnspentOutputs: p2pkhOutputs(1e8, 1e8),
   156  			Outputs: p2pkhOutputs(1e8 - 603 - txrules.FeeForSerializeSize(1e3,
   157  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(0), txsizes.P2PKHPkScriptSize))),
   158  			RelayFee:     1e3,
   159  			ChangeAmount: 603,
   160  			InputCount:   1,
   161  		},
   162  
   163  		// Test that second output is not included to make the change
   164  		// output not dust and be included in the transaction.
   165  		//
   166  		// It's debatable whether or not this is a good idea, but it's
   167  		// how the function was written, so test it anyways.
   168  		10: {
   169  			UnspentOutputs: p2pkhOutputs(1e8, 1e8),
   170  			Outputs: p2pkhOutputs(1e8 - 545 - txrules.FeeForSerializeSize(1e3,
   171  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(0), txsizes.P2PKHPkScriptSize))),
   172  			RelayFee:     1e3,
   173  			ChangeAmount: 0,
   174  			InputCount:   1,
   175  		},
   176  
   177  		// Test two unspent outputs available where both are needed.
   178  		11: {
   179  			UnspentOutputs: p2pkhOutputs(1e8, 1e8),
   180  			Outputs:        p2pkhOutputs(1e8),
   181  			RelayFee:       1e3,
   182  			ChangeAmount: 1e8 - txrules.FeeForSerializeSize(1e3,
   183  				txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHSigScriptSize, txsizes.RedeemP2PKHSigScriptSize}, p2pkhOutputs(1e8), txsizes.P2PKHPkScriptSize)),
   184  			InputCount: 2,
   185  		},
   186  
   187  		// Test that zero change outputs are not included
   188  		// (ChangeAmount=0 means don't include any change output).
   189  		12: {
   190  			UnspentOutputs: p2pkhOutputs(1e8),
   191  			Outputs:        p2pkhOutputs(1e8),
   192  			RelayFee:       0,
   193  			ChangeAmount:   0,
   194  			InputCount:     1,
   195  		},
   196  	}
   197  
   198  	var changeSource AuthorTestChangeSource
   199  
   200  	for i, test := range tests {
   201  		inputSource := makeInputSource(test.UnspentOutputs)
   202  		tx, err := NewUnsignedTransaction(test.Outputs, test.RelayFee, inputSource, changeSource, chaincfg.MainNetParams().MaxTxSize)
   203  		if err != nil {
   204  			insufficientBalance := errors.Is(err, errors.InsufficientBalance)
   205  			if insufficientBalance != test.InputSourceError {
   206  				if !test.InputSourceError {
   207  					t.Errorf("Test %d: InsufficientBalance=%v expected %v", i, insufficientBalance, test.InputSourceError)
   208  				}
   209  				continue
   210  			} else if !insufficientBalance {
   211  				t.Errorf("Test %d: Unexpected error: %v", i, err)
   212  				continue
   213  			}
   214  			continue
   215  		}
   216  		if tx.ChangeIndex < 0 {
   217  			if test.ChangeAmount != 0 {
   218  				t.Errorf("Test %d: No change output added but expected output with amount %v",
   219  					i, test.ChangeAmount)
   220  				continue
   221  			}
   222  		} else {
   223  			changeAmount := dcrutil.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value)
   224  			if test.ChangeAmount == 0 {
   225  				t.Errorf("Test %d: Included change output with value %v but expected no change",
   226  					i, changeAmount)
   227  				continue
   228  			}
   229  			if changeAmount != test.ChangeAmount {
   230  				t.Errorf("Test %d: Got change amount %v, Expected %v",
   231  					i, changeAmount, test.ChangeAmount)
   232  				continue
   233  			}
   234  		}
   235  		if len(tx.Tx.TxIn) != test.InputCount {
   236  			t.Errorf("Test %d: Used %d outputs from input source, Expected %d",
   237  				i, len(tx.Tx.TxIn), test.InputCount)
   238  		}
   239  	}
   240  }