lnd.xprv/sweep/tx_input_set_test.go

251 lines
7.3 KiB
Go

package sweep
import (
"testing"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/stretchr/testify/require"
)
// TestTxInputSet tests adding various sized inputs to the set.
func TestTxInputSet(t *testing.T) {
const (
feeRate = 1000
relayFee = 300
maxInputs = 10
)
set := newTxInputSet(nil, feeRate, relayFee, maxInputs)
if set.dustLimit != 537 {
t.Fatalf("incorrect dust limit")
}
// Create a 300 sat input. The fee to sweep this input to a P2WKH output
// is 439 sats. That means that this input yields -139 sats and we
// expect it not to be added.
if set.add(createP2WKHInput(300), constraintsRegular) {
t.Fatal("expected add of negatively yielding input to fail")
}
// A 700 sat input should be accepted into the set, because it yields
// positively.
if !set.add(createP2WKHInput(700), constraintsRegular) {
t.Fatal("expected add of positively yielding input to succeed")
}
fee := set.weightEstimate(true).fee()
require.Equal(t, btcutil.Amount(439), fee)
// The tx output should now be 700-439 = 261 sats. The dust limit isn't
// reached yet.
if set.totalOutput() != 261 {
t.Fatal("unexpected output value")
}
if set.enoughInput() {
t.Fatal("expected dust limit not yet to be reached")
}
// Add a 1000 sat input. This increases the tx fee to 712 sats. The tx
// output should now be 1000+700 - 712 = 988 sats.
if !set.add(createP2WKHInput(1000), constraintsRegular) {
t.Fatal("expected add of positively yielding input to succeed")
}
if set.totalOutput() != 988 {
t.Fatal("unexpected output value")
}
if !set.enoughInput() {
t.Fatal("expected dust limit to be reached")
}
}
// TestTxInputSetFromWallet tests adding a wallet input to a TxInputSet to reach
// the dust limit.
func TestTxInputSetFromWallet(t *testing.T) {
const (
feeRate = 500
relayFee = 300
maxInputs = 10
)
wallet := &mockWallet{}
set := newTxInputSet(wallet, feeRate, relayFee, maxInputs)
// Add a 700 sat input to the set. It yields positively, but doesn't
// reach the output dust limit.
if !set.add(createP2WKHInput(700), constraintsRegular) {
t.Fatal("expected add of positively yielding input to succeed")
}
if set.enoughInput() {
t.Fatal("expected dust limit not yet to be reached")
}
// Expect that adding a negative yield input fails.
if set.add(createP2WKHInput(50), constraintsRegular) {
t.Fatal("expected negative yield input add to fail")
}
// Force add the negative yield input. It should succeed.
if !set.add(createP2WKHInput(50), constraintsForce) {
t.Fatal("expected forced add to succeed")
}
err := set.tryAddWalletInputsIfNeeded()
if err != nil {
t.Fatal(err)
}
if !set.enoughInput() {
t.Fatal("expected dust limit to be reached")
}
}
// createP2WKHInput returns a P2WKH test input with the specified amount.
func createP2WKHInput(amt btcutil.Amount) input.Input {
input := createTestInput(int64(amt), input.WitnessKeyHash)
return &input
}
type mockWallet struct {
Wallet
}
func (m *mockWallet) ListUnspentWitnessFromDefaultAccount(minconfirms, maxconfirms int32) (
[]*lnwallet.Utxo, error) {
return []*lnwallet.Utxo{
{
AddressType: lnwallet.WitnessPubKey,
Value: 10000,
},
}, nil
}
type reqInput struct {
input.Input
txOut *wire.TxOut
}
func (r *reqInput) RequiredTxOut() *wire.TxOut {
return r.txOut
}
// TestTxInputSetRequiredOutput tests that the tx input set behaves as expected
// when we add inputs that have required tx outs.
func TestTxInputSetRequiredOutput(t *testing.T) {
const (
feeRate = 1000
relayFee = 300
maxInputs = 10
)
set := newTxInputSet(nil, feeRate, relayFee, maxInputs)
if set.dustLimit != 537 {
t.Fatalf("incorrect dust limit")
}
// Attempt to add an input with a required txout below the dust limit.
// This should fail since we cannot trim such outputs.
inp := &reqInput{
Input: createP2WKHInput(500),
txOut: &wire.TxOut{
Value: 500,
PkScript: make([]byte, 33),
},
}
require.False(t, set.add(inp, constraintsRegular),
"expected adding dust required tx out to fail")
// Create a 1000 sat input that also has a required TxOut of 1000 sat.
// The fee to sweep this input to a P2WKH output is 439 sats.
inp = &reqInput{
Input: createP2WKHInput(1000),
txOut: &wire.TxOut{
Value: 1000,
PkScript: make([]byte, 22),
},
}
require.True(t, set.add(inp, constraintsRegular), "failed adding input")
// The fee needed to pay for this input and output should be 439 sats.
fee := set.weightEstimate(false).fee()
require.Equal(t, btcutil.Amount(439), fee)
// Since the tx set currently pays no fees, we expect the current
// change to actually be negative, since this is what it would cost us
// in fees to add a change output.
feeWithChange := set.weightEstimate(true).fee()
if set.changeOutput != -feeWithChange {
t.Fatalf("expected negative change of %v, had %v",
-feeWithChange, set.changeOutput)
}
// This should also be reflected by not having enough input.
require.False(t, set.enoughInput())
// Get a weight estimate without change output, and add an additional
// input to it.
dummyInput := createP2WKHInput(1000)
weight := set.weightEstimate(false)
require.NoError(t, weight.add(dummyInput))
// Now we add a an input that is large enough to pay the fee for the
// transaction without a change output, but not large enough to afford
// adding a change output.
extraInput1 := weight.fee() + 100
require.True(t, set.add(createP2WKHInput(extraInput1), constraintsRegular),
"expected add of positively yielding input to succeed")
// The change should be negative, since we would have to add a change
// output, which we cannot yet afford.
if set.changeOutput >= 0 {
t.Fatal("expected change to be negaitve")
}
// Even though we cannot afford a change output, the tx set is valid,
// since we can pay the fees without the change output.
require.True(t, set.enoughInput())
// Get another weight estimate, this time with a change output, and
// figure out how much we must add to afford a change output.
weight = set.weightEstimate(true)
require.NoError(t, weight.add(dummyInput))
// We add what is left to reach this value.
extraInput2 := weight.fee() - extraInput1 + 100
// Add this input, which should result in the change now being 100 sats.
require.True(t, set.add(createP2WKHInput(extraInput2), constraintsRegular))
// The change should be 100, since this is what is left after paying
// fees in case of a change output.
change := set.changeOutput
if change != 100 {
t.Fatalf("expected change be 100, was %v", change)
}
// Even though the change output is dust, we have enough for fees, and
// we have an output, so it should be considered enough to craft a
// valid sweep transaction.
require.True(t, set.enoughInput())
// Finally we add an input that should push the change output above the
// dust limit.
weight = set.weightEstimate(true)
require.NoError(t, weight.add(dummyInput))
// We expect the change to everything that is left after paying the tx
// fee.
extraInput3 := weight.fee() - extraInput1 - extraInput2 + 1000
require.True(t, set.add(createP2WKHInput(extraInput3), constraintsRegular))
change = set.changeOutput
if change != 1000 {
t.Fatalf("expected change to be %v, had %v", 1000, change)
}
require.True(t, set.enoughInput())
}