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:
1 parent
f97454d4ff
commit
0c8aa81ed5
6 files changed
+370
-7
No files matched your search
@@ -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,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user