chanfunding: adds ability to fund up to some maximum amount

Allows to define a maximum amount to provision a channel
opening with using a new field `FundUpToMaxAmt` on the
`Request` struct. Also adds a new coin select function
`CoinSelectUpToAmount` to select coins up to a maximum
amount respecting a minimum amount.
This commit is contained in:
Bjarne Magnussen authored and Slyghtning committed 2023-04-05 23:05:06 +02:00
1 parent f97454d4ff
commit 0c8aa81ed5
6 files changed
+370 -7

No files matched your search

+205 -1
View File
@@ -518,15 +518,219 @@ func TestCoinSelectSubtractFees(t *testing.T) {
}
}
// Assert we got the expected change amount.
// Assert we got the expected funding amount.
if localFundingAmt != test.expectedFundingAmt {
t.Fatalf("expected %v local funding amt, got %v",
test.expectedFundingAmt, localFundingAmt)
}
// Assert we got the expected change amount.
require.EqualValues(
t, test.expectedChange, changeAmt,
)
})
}
}
// TestCoinSelectUpToAmount tests that we pick coins adding up to the
// expected amount when creating a funding transaction, and that a change
// output is created only when necessary.
func TestCoinSelectUpToAmount(t *testing.T) {
t.Parallel()
const (
feeRate = chainfee.SatPerKWeight(100)
dustLimit = btcutil.Amount(1000)
dust = btcutil.Amount(100)
coin = btcutil.SatoshiPerBitcoin
minValue = 20_000
)
type testCase struct {
name string
minValue btcutil.Amount
maxValue btcutil.Amount
coins []Coin
expectedInput []btcutil.Amount
expectedFundingAmt btcutil.Amount
expectedChange btcutil.Amount
expectErr string
}
testCases := []testCase{{
// We have 1.0 BTC available, spend them all.
// This should lead to a funding TX with one output, the rest
// goes to fees.
name: "spend exactly all",
coins: []Coin{{
TxOut: wire.TxOut{
PkScript: p2wkhScript,
Value: 1 * coin,
},
}},
minValue: minValue,
maxValue: 1 * coin,
// The one and only input will be selected.
expectedInput: []btcutil.Amount{1 * coin},
expectedFundingAmt: 1*coin - fundingFee(feeRate, 1, false),
expectedChange: 0,
}, {
// We have 1.0 BTC available and want to spend up to 2 BTC.
// This should lead to a funding TX with one output, the rest
// goes to fees.
name: "spend more",
coins: []Coin{{
TxOut: wire.TxOut{
PkScript: p2wkhScript,
Value: 1 * coin,
},
}},
minValue: minValue,
maxValue: 2 * coin,
// The one and only input will be selected.
expectedInput: []btcutil.Amount{1 * coin},
expectedFundingAmt: 1*coin - fundingFee(feeRate, 1, false),
expectedChange: 0,
}, {
// We have 1.0 BTC available and want to spend up to 0.5 BTC.
// This should lead to a funding TX with one output and a
// change to subtract the fees from.
name: "spend far below",
coins: []Coin{{
TxOut: wire.TxOut{
PkScript: p2wkhScript,
Value: 1 * coin,
},
}},
minValue: minValue,
maxValue: 0.5 * coin,
// The one and only input will be selected.
expectedInput: []btcutil.Amount{1 * coin},
expectedFundingAmt: 0.5 * coin,
expectedChange: 0.5*coin - fundingFee(feeRate, 1, true),
}, {
// We have 1.0 BTC available and want to spend just 1 Satoshi
// below that amount.
// This should lead to a funding TX with one output where the
// fee is subtracted from the total 1 BTC input value.
name: "spend little below",
coins: []Coin{{
TxOut: wire.TxOut{
PkScript: p2wkhScript,
Value: 1 * coin,
},
}},
minValue: minValue,
maxValue: 1*coin - 1,
// The one and only input will be selected.
expectedInput: []btcutil.Amount{
1 * coin,
},
expectedFundingAmt: 1*coin - fundingFee(feeRate, 1, false),
expectedChange: 0,
}, {
// The total funds available is below the dust limit after
// paying fees.
name: "dust output",
coins: []Coin{{
TxOut: wire.TxOut{
PkScript: p2wkhScript,
Value: int64(
fundingFee(feeRate, 1, false) + dust,
),
},
}},
minValue: minValue,
maxValue: fundingFee(feeRate, 1, false) + dust,
expectErr: "output amount(0.000001 BTC) after subtracting " +
"fees(0.00000048 BTC) below dust limit(0.00001 BTC)",
}, {
// If more than 20% of available wallet funds goes to fees, it
// should fail.
name: "high fee",
coins: []Coin{{
TxOut: wire.TxOut{
PkScript: p2wkhScript,
Value: int64(
20 * fundingFee(feeRate, 1, false),
),
},
}},
minValue: minValue,
maxValue: 16 * fundingFee(feeRate, 1, false),
expectErr: "fee 0.00000192 BTC on total output value " +
"0.00000768 BTC",
}, {
// This test makes sure that the implementation detail of using
// CoinSelect and CoinSelectSubtractFees is done correctly.
// CoinSelect always defaults to use a fee for single input -
// one change tx, whereas CoinSelectSubtractFees will use a fee
// of single input - no change output, which without a sanity
// check could result in a local amount higher than the maximum
// amount that was expected.
name: "sanity check for correct maximum amount",
coins: []Coin{{
TxOut: wire.TxOut{
PkScript: p2wkhScript,
Value: 1 * coin,
},
}},
minValue: minValue,
maxValue: 1*coin - fundingFee(feeRate, 1, false) - 1,
expectedInput: []btcutil.Amount{1 * coin},
expectedFundingAmt: 1*coin - fundingFee(feeRate, 1, false) - 1,
expectedChange: 0,
}}
for _, test := range testCases {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
selected, localFundingAmt, changeAmt,
err := CoinSelectUpToAmount(
feeRate, test.minValue, test.maxValue,
dustLimit, test.coins,
)
if len(test.expectErr) == 0 && err != nil {
t.Fatalf(err.Error())
}
if changeAmt != test.expectedChange {
t.Fatalf("expected %v change amt, got %v",
test.expectedChange, changeAmt)
}
if len(test.expectErr) > 0 && err == nil {
t.Fatalf("expected error: %v", test.expectErr)
}
if len(test.expectErr) > 0 && err != nil {
require.EqualError(t, err, test.expectErr)
}
// Check that the selected inputs match what we expect.
require.Equal(t, len(test.expectedInput), len(selected))
for i, coin := range selected {
require.EqualValues(
t, test.expectedInput[i], coin.Value,
)
}
// Assert we got the expected funding amount.
require.Equal(
t, test.expectedFundingAmt, localFundingAmt,
)
// Assert we got the expected change amount.
require.Equal(
t, test.expectedChange, changeAmt,
)
})
}
}