github.com/decred/dcrlnd@v0.7.6/sweep/tx_input_set_test.go (about) 1 package sweep 2 3 import ( 4 "testing" 5 6 "github.com/decred/dcrd/dcrutil/v4" 7 "github.com/decred/dcrd/wire" 8 "github.com/decred/dcrlnd/input" 9 "github.com/decred/dcrlnd/lnwallet" 10 "github.com/stretchr/testify/require" 11 ) 12 13 // TestTxInputSet tests adding various sized inputs to the set. 14 func TestTxInputSet(t *testing.T) { 15 const ( 16 feeRate = 5e4 17 maxInputs = 10 18 ) 19 set := newTxInputSet(nil, feeRate, maxInputs) 20 21 // Create a 10001 atom input. The fee to sweep this input to a P2PKH 22 // output is 10850 atoms. That means that this input yields -849 atoms 23 // and we expect it not to be added. 24 if set.add(createP2PKHInput(10001), constraintsRegular) { 25 t.Fatal("expected add of negatively yielding input to fail") 26 } 27 28 // A 15000 atom input should be accepted into the set, because it 29 // yields positively. 30 if !set.add(createP2PKHInput(15001), constraintsRegular) { 31 t.Fatal("expected add of positively yielding input to succeed") 32 } 33 34 fee := set.sizeEstimate(true).fee() 35 require.Equal(t, dcrutil.Amount(10850), fee) 36 37 // The tx output should now be 15000-10850 = 4151 atoms. The dust limit 38 // isn't reached yet. 39 wantOutputValue := dcrutil.Amount(4151) 40 if set.totalOutput() != wantOutputValue { 41 t.Fatalf("unexpected output value. want=%d got=%d", wantOutputValue, set.totalOutput()) 42 } 43 if set.enoughInput() { 44 t.Fatal("expected dust limit not yet to be reached") 45 } 46 47 // Add a 13703 atoms input. This increases the tx fee to 19150 atoms. 48 // The tx output should now be 13703+15001 - 19150 = 9554 atoms. 49 if !set.add(createP2PKHInput(13703), constraintsRegular) { 50 t.Fatal("expected add of positively yielding input to succeed") 51 } 52 wantOutputValue = 9554 53 if set.totalOutput() != wantOutputValue { 54 t.Fatalf("unexpected output value. want=%d got=%d", wantOutputValue, set.totalOutput()) 55 } 56 if !set.enoughInput() { 57 t.Fatal("expected dust limit to be reached") 58 } 59 } 60 61 // TestTxInputSetFromWallet tests adding a wallet input to a TxInputSet to 62 // reach the dust limit. 63 func TestTxInputSetFromWallet(t *testing.T) { 64 const ( 65 feeRate = 2e4 66 maxInputs = 10 67 ) 68 69 wallet := &mockWallet{} 70 set := newTxInputSet(wallet, feeRate, maxInputs) 71 72 // Add a 10000 atoms input to the set. It yields positively, but 73 // doesn't reach the output dust limit. 74 if !set.add(createP2PKHInput(10000), constraintsRegular) { 75 t.Fatal("expected add of positively yielding input to succeed") 76 } 77 if set.enoughInput() { 78 t.Fatal("expected dust limit not yet to be reached") 79 } 80 81 // Expect that adding a negative yield input fails. 82 if set.add(createP2PKHInput(50), constraintsRegular) { 83 t.Fatal("expected negative yield input add to fail") 84 } 85 86 // Force add the negative yield input. It should succeed. 87 if !set.add(createP2PKHInput(50), constraintsForce) { 88 t.Fatal("expected forced add to succeed") 89 } 90 91 err := set.tryAddWalletInputsIfNeeded() 92 if err != nil { 93 t.Fatal(err) 94 } 95 96 if !set.enoughInput() { 97 t.Fatal("expected dust limit to be reached") 98 } 99 } 100 101 // createP2PKHInput returns a P2PKH test input with the specified amount. 102 func createP2PKHInput(amt dcrutil.Amount) input.Input { 103 input := createTestInput(int64(amt), input.PublicKeyHash) 104 return &input 105 } 106 107 type mockWallet struct { 108 Wallet 109 } 110 111 func (m *mockWallet) ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) ( 112 []*lnwallet.Utxo, error) { 113 114 return []*lnwallet.Utxo{ 115 { 116 AddressType: lnwallet.PubKeyHash, 117 Value: 8000, 118 }, 119 }, nil 120 } 121 122 type reqInput struct { 123 input.Input 124 125 txOut *wire.TxOut 126 } 127 128 func (r *reqInput) RequiredTxOut() *wire.TxOut { 129 return r.txOut 130 } 131 132 // TestTxInputSetRequiredOutput tests that the tx input set behaves as expected 133 // when we add inputs that have required tx outs. 134 func TestTxInputSetRequiredOutput(t *testing.T) { 135 const ( 136 feeRate = 50000 137 maxInputs = 10 138 ) 139 set := newTxInputSet(nil, feeRate, maxInputs) 140 141 // Attempt to add an input with a required txout below the dust limit. 142 // This should fail since we cannot trim such outputs. 143 inp := &reqInput{ 144 Input: createP2PKHInput(10001), 145 txOut: &wire.TxOut{ 146 Value: 10001, 147 PkScript: make([]byte, 25), 148 }, 149 } 150 require.False(t, set.add(inp, constraintsRegular), 151 "expected adding dust required tx out to fail") 152 153 // Create a 15001 atoms input that also has a required TxOut of 15001 atoms. 154 // The fee to sweep this input to a P2PKH output is 10850 sats. 155 inp = &reqInput{ 156 Input: createP2PKHInput(15001), 157 txOut: &wire.TxOut{ 158 Value: 15001, 159 PkScript: make([]byte, 25), 160 }, 161 } 162 require.True(t, set.add(inp, constraintsRegular), "failed adding input") 163 164 // The fee needed to pay for this input and output should be 10850 atoms. 165 fee := set.sizeEstimate(false).fee() 166 require.Equal(t, dcrutil.Amount(10850), fee) 167 168 // Since the tx set currently pays no fees, we expect the current 169 // change to actually be negative, since this is what it would cost us 170 // in fees to add a change output. 171 feeWithChange := set.sizeEstimate(true).fee() 172 if set.changeOutput != -feeWithChange { 173 t.Fatalf("expected negative change of %v, had %v", 174 -feeWithChange, set.changeOutput) 175 } 176 177 // This should also be reflected by not having enough input. 178 require.False(t, set.enoughInput()) 179 180 // Get a weight estimate without change output, and add an additional 181 // input to it. 182 dummyInput := createP2PKHInput(1000) 183 weight := set.sizeEstimate(false) 184 require.NoError(t, weight.add(dummyInput)) 185 186 // Now we add a an input that is large enough to pay the fee for the 187 // transaction without a change output, but not large enough to afford 188 // adding a change output. 189 extraInput1 := weight.fee() + 100 190 require.True(t, set.add(createP2PKHInput(extraInput1), constraintsRegular), 191 "expected add of positively yielding input to succeed") 192 193 // The change should be negative, since we would have to add a change 194 // output, which we cannot yet afford. 195 if set.changeOutput >= 0 { 196 t.Fatal("expected change to be negaitve") 197 } 198 199 // Even though we cannot afford a change output, the tx set is valid, 200 // since we can pay the fees without the change output. 201 require.True(t, set.enoughInput()) 202 203 // Get another weight estimate, this time with a change output, and 204 // figure out how much we must add to afford a change output. 205 weight = set.sizeEstimate(true) 206 require.NoError(t, weight.add(dummyInput)) 207 208 // We add what is left to reach this value. 209 extraInput2 := weight.fee() - extraInput1 + 100 210 211 // Add this input, which should result in the change now being 100 sats. 212 require.True(t, set.add(createP2PKHInput(extraInput2), constraintsRegular)) 213 214 // The change should be 100, since this is what is left after paying 215 // fees in case of a change output. 216 change := set.changeOutput 217 if change != 100 { 218 t.Fatalf("expected change be 100, was %v", change) 219 } 220 221 // Even though the change output is dust, we have enough for fees, and 222 // we have an output, so it should be considered enough to craft a 223 // valid sweep transaction. 224 require.True(t, set.enoughInput()) 225 226 // Finally we add an input that should push the change output above the 227 // dust limit. 228 weight = set.sizeEstimate(true) 229 require.NoError(t, weight.add(dummyInput)) 230 231 // We expect the change to everything that is left after paying the tx 232 // fee. 233 extraInput3 := weight.fee() - extraInput1 - extraInput2 + 1000 234 require.True(t, set.add(createP2PKHInput(extraInput3), constraintsRegular)) 235 236 change = set.changeOutput 237 if change != 1000 { 238 t.Fatalf("expected change to be %v, had %v", 1000, change) 239 240 } 241 require.True(t, set.enoughInput()) 242 }