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 }