multi: replace DefaultDustLimit with script-specific DustLimitForSize

This commit updates call-sites to use the proper dust limits for
various script types. This also updates the default dust limit used
in the funding flow to be 354 satoshis instead of 573 satoshis.
This commit is contained in:
eugene committed 2021-09-29 13:33:10 -04:00
1 parent 3385d38414
commit fdcd726f9a
27 files changed
+330 -154

No files matched your search

+5 -5
View File
@@ -1150,7 +1150,7 @@ func (s *UtxoSweeper) getInputLists(cluster inputCluster,
if len(retryInputs) > 0 {
var err error
allSets, err = generateInputPartitionings(
append(retryInputs, newInputs...), s.relayFeeRate,
append(retryInputs, newInputs...),
cluster.sweepFeeRate, s.cfg.MaxInputsPerTx,
s.cfg.Wallet,
)
@@ -1161,8 +1161,8 @@ func (s *UtxoSweeper) getInputLists(cluster inputCluster,
// Create sets for just the new inputs.
newSets, err := generateInputPartitionings(
newInputs, s.relayFeeRate, cluster.sweepFeeRate,
s.cfg.MaxInputsPerTx, s.cfg.Wallet,
newInputs, cluster.sweepFeeRate, s.cfg.MaxInputsPerTx,
s.cfg.Wallet,
)
if err != nil {
return nil, fmt.Errorf("input partitionings: %v", err)
@@ -1193,7 +1193,7 @@ func (s *UtxoSweeper) sweep(inputs inputSet, feeRate chainfee.SatPerKWeight,
// Create sweep tx.
tx, err := createSweepTx(
inputs, nil, s.currentOutputScript, uint32(currentHeight),
feeRate, dustLimit(s.relayFeeRate), s.cfg.Signer,
feeRate, s.cfg.Signer,
)
if err != nil {
return fmt.Errorf("create sweep tx: %v", err)
@@ -1488,7 +1488,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []input.Input, feePref FeePreference,
return createSweepTx(
inputs, nil, pkScript, currentBlockHeight, feePerKw,
dustLimit(s.relayFeeRate), s.cfg.Signer,
s.cfg.Signer,
)
}
+39 -20
View File
@@ -136,7 +136,8 @@ func createSweeperTestContext(t *testing.T) *sweeperTestContext {
Store: store,
Signer: &mock.DummySigner{},
GenSweepScript: func() ([]byte, error) {
script := []byte{outputScriptCount}
script := make([]byte, input.P2WPKHSize)
script[0] = outputScriptCount
outputScriptCount++
return script, nil
},
@@ -1801,6 +1802,24 @@ func TestRequiredTxOuts(t *testing.T) {
locktime2 := uint32(52)
locktime3 := uint32(53)
aPkScript := make([]byte, input.P2WPKHSize)
aPkScript[0] = 'a'
bPkScript := make([]byte, input.P2WSHSize)
bPkScript[0] = 'b'
cPkScript := make([]byte, input.P2PKHSize)
cPkScript[0] = 'c'
dPkScript := make([]byte, input.P2SHSize)
dPkScript[0] = 'd'
ePkScript := make([]byte, input.UnknownWitnessSize)
ePkScript[0] = 'e'
fPkScript := make([]byte, input.P2WSHSize)
fPkScript[0] = 'f'
testCases := []struct {
name string
inputs []*testInput
@@ -1815,7 +1834,7 @@ func TestRequiredTxOuts(t *testing.T) {
{
BaseInput: inputs[0],
reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"),
PkScript: aPkScript,
Value: 100000,
},
},
@@ -1836,7 +1855,7 @@ func TestRequiredTxOuts(t *testing.T) {
// output must be the first one.
require.Equal(t, 2, len(tx.TxOut))
out := tx.TxOut[0]
require.Equal(t, []byte("aaa"), out.PkScript)
require.Equal(t, aPkScript, out.PkScript)
require.Equal(t, int64(100000), out.Value)
},
},
@@ -1848,13 +1867,13 @@ func TestRequiredTxOuts(t *testing.T) {
{
BaseInput: inputs[0],
reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"),
PkScript: aPkScript,
// Fee will be about 5340 sats.
// Subtract a bit more to
// ensure no dust change output
// is manifested.
Value: inputs[0].SignDesc().Output.Value - 5600,
Value: inputs[0].SignDesc().Output.Value - 6300,
},
},
},
@@ -1871,10 +1890,10 @@ func TestRequiredTxOuts(t *testing.T) {
require.Equal(t, 1, len(tx.TxOut))
out := tx.TxOut[0]
require.Equal(t, []byte("aaa"), out.PkScript)
require.Equal(t, aPkScript, out.PkScript)
require.Equal(
t,
inputs[0].SignDesc().Output.Value-5600,
inputs[0].SignDesc().Output.Value-6300,
out.Value,
)
},
@@ -1897,7 +1916,7 @@ func TestRequiredTxOuts(t *testing.T) {
// The second input requires a TxOut.
BaseInput: inputs[0],
reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"),
PkScript: aPkScript,
Value: inputs[0].SignDesc().Output.Value,
},
},
@@ -1916,7 +1935,7 @@ func TestRequiredTxOuts(t *testing.T) {
// The required TxOut should be the first one.
out := tx.TxOut[0]
require.Equal(t, []byte("aaa"), out.PkScript)
require.Equal(t, aPkScript, out.PkScript)
require.Equal(
t, inputs[0].SignDesc().Output.Value,
out.Value,
@@ -1947,7 +1966,7 @@ func TestRequiredTxOuts(t *testing.T) {
{
BaseInput: inputs[0],
reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"),
PkScript: aPkScript,
Value: inputs[0].SignDesc().Output.Value,
},
},
@@ -1965,7 +1984,7 @@ func TestRequiredTxOuts(t *testing.T) {
require.Equal(t, 2, len(tx.TxOut))
out := tx.TxOut[0]
require.Equal(t, []byte("aaa"), out.PkScript)
require.Equal(t, aPkScript, out.PkScript)
require.Equal(
t, inputs[0].SignDesc().Output.Value,
out.Value,
@@ -1980,21 +1999,21 @@ func TestRequiredTxOuts(t *testing.T) {
{
BaseInput: inputs[0],
reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"),
PkScript: aPkScript,
Value: inputs[0].SignDesc().Output.Value,
},
},
{
BaseInput: inputs[1],
reqTxOut: &wire.TxOut{
PkScript: []byte("bbb"),
PkScript: bPkScript,
Value: inputs[1].SignDesc().Output.Value,
},
},
{
BaseInput: inputs[2],
reqTxOut: &wire.TxOut{
PkScript: []byte("ccc"),
PkScript: cPkScript,
Value: inputs[2].SignDesc().Output.Value,
},
},
@@ -2041,7 +2060,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[0],
locktime: &locktime1,
reqTxOut: &wire.TxOut{
PkScript: []byte("aaa"),
PkScript: aPkScript,
Value: inputs[0].SignDesc().Output.Value,
},
},
@@ -2049,7 +2068,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[1],
locktime: &locktime1,
reqTxOut: &wire.TxOut{
PkScript: []byte("bbb"),
PkScript: bPkScript,
Value: inputs[1].SignDesc().Output.Value,
},
},
@@ -2057,7 +2076,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[2],
locktime: &locktime2,
reqTxOut: &wire.TxOut{
PkScript: []byte("ccc"),
PkScript: cPkScript,
Value: inputs[2].SignDesc().Output.Value,
},
},
@@ -2065,7 +2084,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[3],
locktime: &locktime2,
reqTxOut: &wire.TxOut{
PkScript: []byte("ddd"),
PkScript: dPkScript,
Value: inputs[3].SignDesc().Output.Value,
},
},
@@ -2073,7 +2092,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[4],
locktime: &locktime3,
reqTxOut: &wire.TxOut{
PkScript: []byte("eee"),
PkScript: ePkScript,
Value: inputs[4].SignDesc().Output.Value,
},
},
@@ -2081,7 +2100,7 @@ func TestRequiredTxOuts(t *testing.T) {
BaseInput: inputs[5],
locktime: &locktime3,
reqTxOut: &wire.TxOut{
PkScript: []byte("fff"),
PkScript: fPkScript,
Value: inputs[5].SignDesc().Output.Value,
},
},
+9 -19
View File
@@ -7,7 +7,6 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -109,9 +108,6 @@ func (t *txInputSetState) clone() txInputSetState {
type txInputSet struct {
txInputSetState
// dustLimit is the minimum output value of the tx.
dustLimit btcutil.Amount
// maxInputs is the maximum number of inputs that will be accepted in
// the set.
maxInputs int
@@ -121,25 +117,15 @@ type txInputSet struct {
wallet Wallet
}
func dustLimit(relayFee chainfee.SatPerKWeight) btcutil.Amount {
return txrules.GetDustThreshold(
input.P2WPKHSize,
btcutil.Amount(relayFee.FeePerKVByte()),
)
}
// newTxInputSet constructs a new, empty input set.
func newTxInputSet(wallet Wallet, feePerKW,
relayFee chainfee.SatPerKWeight, maxInputs int) *txInputSet {
dustLimit := dustLimit(relayFee)
func newTxInputSet(wallet Wallet, feePerKW chainfee.SatPerKWeight,
maxInputs int) *txInputSet {
state := txInputSetState{
feeRate: feePerKW,
}
b := txInputSet{
dustLimit: dustLimit,
maxInputs: maxInputs,
wallet: wallet,
txInputSetState: state,
@@ -153,7 +139,7 @@ func newTxInputSet(wallet Wallet, feePerKW,
func (t *txInputSet) enoughInput() bool {
// If we have a change output above dust, then we certainly have enough
// inputs to the transaction.
if t.changeOutput >= t.dustLimit {
if t.changeOutput >= lnwallet.DustLimitForSize(input.P2WPKHSize) {
return true
}
@@ -192,8 +178,12 @@ func (t *txInputSet) addToState(inp input.Input, constraints addConstraints) *tx
// If the input comes with a required tx out that is below dust, we
// won't add it.
reqOut := inp.RequiredTxOut()
if reqOut != nil && btcutil.Amount(reqOut.Value) < t.dustLimit {
return nil
if reqOut != nil {
// Fetch the dust limit for this output.
dustLimit := lnwallet.DustLimitForSize(len(reqOut.PkScript))
if btcutil.Amount(reqOut.Value) < dustLimit {
return nil
}
}
// Clone the current set state.
+7 -17
View File
@@ -14,14 +14,9 @@ import (
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")
}
set := newTxInputSet(nil, feeRate, maxInputs)
// 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
@@ -66,16 +61,15 @@ func TestTxInputSet(t *testing.T) {
func TestTxInputSetFromWallet(t *testing.T) {
const (
feeRate = 500
relayFee = 300
maxInputs = 10
)
wallet := &mockWallet{}
set := newTxInputSet(wallet, feeRate, relayFee, maxInputs)
set := newTxInputSet(wallet, feeRate, maxInputs)
// Add a 700 sat input to the set. It yields positively, but doesn't
// Add a 500 sat input to the set. It yields positively, but doesn't
// reach the output dust limit.
if !set.add(createP2WKHInput(700), constraintsRegular) {
if !set.add(createP2WKHInput(500), constraintsRegular) {
t.Fatal("expected add of positively yielding input to succeed")
}
if set.enoughInput() {
@@ -138,13 +132,9 @@ func (r *reqInput) RequiredTxOut() *wire.TxOut {
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")
}
set := newTxInputSet(nil, feeRate, maxInputs)
// Attempt to add an input with a required txout below the dust limit.
// This should fail since we cannot trim such outputs.
@@ -152,7 +142,7 @@ func TestTxInputSetRequiredOutput(t *testing.T) {
Input: createP2WKHInput(500),
txOut: &wire.TxOut{
Value: 500,
PkScript: make([]byte, 33),
PkScript: make([]byte, input.P2PKHSize),
},
}
require.False(t, set.add(inp, constraintsRegular),
@@ -164,7 +154,7 @@ func TestTxInputSetRequiredOutput(t *testing.T) {
Input: createP2WKHInput(1000),
txOut: &wire.TxOut{
Value: 1000,
PkScript: make([]byte, 22),
PkScript: make([]byte, input.P2WPKHSize),
},
}
require.True(t, set.add(inp, constraintsRegular), "failed adding input")
+15 -10
View File
@@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
@@ -37,8 +38,8 @@ type inputSet []input.Input
// inputs are skipped. No input sets with a total value after fees below the
// dust limit are returned.
func generateInputPartitionings(sweepableInputs []txInput,
relayFeePerKW, feePerKW chainfee.SatPerKWeight,
maxInputsPerTx int, wallet Wallet) ([]inputSet, error) {
feePerKW chainfee.SatPerKWeight, maxInputsPerTx int,
wallet Wallet) ([]inputSet, error) {
// Sort input by yield. We will start constructing input sets starting
// with the highest yield inputs. This is to prevent the construction
@@ -85,9 +86,7 @@ func generateInputPartitionings(sweepableInputs []txInput,
// Start building a set of positive-yield tx inputs under the
// condition that the tx will be published with the specified
// fee rate.
txInputs := newTxInputSet(
wallet, feePerKW, relayFeePerKW, maxInputsPerTx,
)
txInputs := newTxInputSet(wallet, feePerKW, maxInputsPerTx)
// From the set of sweepable inputs, keep adding inputs to the
// input set until the tx output value no longer goes up or the
@@ -111,10 +110,12 @@ func generateInputPartitionings(sweepableInputs []txInput,
// continuing with the remaining inputs will only lead to sets
// with an even lower output value.
if !txInputs.enoughInput() {
// The change output is always a p2wpkh here.
dl := lnwallet.DustLimitForSize(input.P2WPKHSize)
log.Debugf("Set value %v (r=%v, c=%v) below dust "+
"limit of %v", txInputs.totalOutput(),
txInputs.requiredOutput, txInputs.changeOutput,
txInputs.dustLimit)
dl)
return sets, nil
}
@@ -135,8 +136,8 @@ func generateInputPartitionings(sweepableInputs []txInput,
// sending any leftover change to the change script.
func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
changePkScript []byte, currentBlockHeight uint32,
feePerKw chainfee.SatPerKWeight, dustLimit btcutil.Amount,
signer input.Signer) (*wire.MsgTx, error) {
feePerKw chainfee.SatPerKWeight, signer input.Signer) (*wire.MsgTx,
error) {
inputs, estimator := getWeightEstimate(inputs, outputs, feePerKw)
txFee := estimator.fee()
@@ -227,16 +228,20 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
// sweep tx has a change output.
changeAmt := totalInput - requiredOutput - txFee
// We'll calculate the dust limit for the given changePkScript since it
// is variable.
changeLimit := lnwallet.DustLimitForSize(len(changePkScript))
// The txn will sweep the amount after fees to the pkscript generated
// above.
if changeAmt >= dustLimit {
if changeAmt >= changeLimit {
sweepTx.AddTxOut(&wire.TxOut{
PkScript: changePkScript,
Value: int64(changeAmt),
})
} else {
log.Infof("Change amt %v below dustlimit %v, not adding "+
"change output", changeAmt, dustLimit)
"change output", changeAmt, changeLimit)
}
// We'll default to using the current block height as locktime, if none
+3 -3
View File
@@ -165,8 +165,8 @@ type DeliveryAddr struct {
// output, as specified by the change address. The sweep transaction will be
// crafted with the target fee rate, and will use the utxoSource and
// outpointLocker as sources for wallet funds.
func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount,
blockHeight uint32, deliveryAddrs []DeliveryAddr, changeAddr btcutil.Address,
func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, blockHeight uint32,
deliveryAddrs []DeliveryAddr, changeAddr btcutil.Address,
coinSelectLocker CoinSelectionLocker, utxoSource UtxoSource,
outpointLocker OutpointLocker, feeEstimator chainfee.Estimator,
signer input.Signer, minConfs int32) (*WalletSweepPackage, error) {
@@ -302,7 +302,7 @@ func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, dustLimit btcutil.Amount,
// respects our fee preference and targets all the UTXOs of the wallet.
sweepTx, err := createSweepTx(
inputsToSweep, txOuts, changePkScript, blockHeight, feeRate,
dustLimit, signer,
signer,
)
if err != nil {
unlockOutputs()
+5 -5
View File
@@ -288,8 +288,8 @@ func TestCraftSweepAllTxCoinSelectFail(t *testing.T) {
utxoLocker := newMockOutpointLocker()
_, err := CraftSweepAllTx(
0, 100, 10, nil, nil, coinSelectLocker, utxoSource,
utxoLocker, nil, nil, 0,
0, 10, nil, nil, coinSelectLocker, utxoSource, utxoLocker, nil,
nil, 0,
)
// Since we instructed the coin select locker to fail above, we should
@@ -314,8 +314,8 @@ func TestCraftSweepAllTxUnknownWitnessType(t *testing.T) {
utxoLocker := newMockOutpointLocker()
_, err := CraftSweepAllTx(
0, 100, 10, nil, nil, coinSelectLocker, utxoSource,
utxoLocker, nil, nil, 0,
0, 10, nil, nil, coinSelectLocker, utxoSource, utxoLocker, nil,
nil, 0,
)
// Since passed in a p2wsh output, which is unknown, we should fail to
@@ -349,7 +349,7 @@ func TestCraftSweepAllTx(t *testing.T) {
utxoLocker := newMockOutpointLocker()
sweepPkg, err := CraftSweepAllTx(
0, 100, 10, nil, deliveryAddr, coinSelectLocker, utxoSource,
0, 10, nil, deliveryAddr, coinSelectLocker, utxoSource,
utxoLocker, feeEstimator, signer, 0,
)
if err != nil {