Merge pull request #4546 from bjarnemagnussen/fix-coinselect-tests

chanfunding: Add/improve tests for CoinSelectSubtractFees
This commit is contained in:
Johan T. Halseth 2020-11-05 10:21:30 +01:00 committed by GitHub
commit 1c96cc7e10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 16 deletions

@ -208,7 +208,7 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
// TODO(halseth): smarter fee limit. Make configurable or dynamic wrt // TODO(halseth): smarter fee limit. Make configurable or dynamic wrt
// total funding size? // total funding size?
if fee > totalOut/5 { if fee > totalOut/5 {
return nil, 0, 0, fmt.Errorf("fee %v on total output"+ return nil, 0, 0, fmt.Errorf("fee %v on total output "+
"value %v", fee, totalOut) "value %v", fee, totalOut)
} }

@ -2,6 +2,7 @@ package chanfunding
import ( import (
"encoding/hex" "encoding/hex"
"regexp"
"testing" "testing"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
@ -205,18 +206,26 @@ func TestCoinSelectSubtractFees(t *testing.T) {
t.Parallel() t.Parallel()
const feeRate = chainfee.SatPerKWeight(100) const feeRate = chainfee.SatPerKWeight(100)
const highFeeRate = chainfee.SatPerKWeight(1000)
const dustLimit = btcutil.Amount(1000) const dustLimit = btcutil.Amount(1000)
const dust = btcutil.Amount(100) const dust = btcutil.Amount(100)
// removeAmounts replaces any amounts in string with "<amt>".
removeAmounts := func(s string) string {
re := regexp.MustCompile(`[[:digit:]]+\.?[[:digit:]]*`)
return re.ReplaceAllString(s, "<amt>")
}
type testCase struct { type testCase struct {
name string name string
highFee bool
spendValue btcutil.Amount spendValue btcutil.Amount
coins []Coin coins []Coin
expectedInput []btcutil.Amount expectedInput []btcutil.Amount
expectedFundingAmt btcutil.Amount expectedFundingAmt btcutil.Amount
expectedChange btcutil.Amount expectedChange btcutil.Amount
expectErr bool expectErr string
} }
testCases := []testCase{ testCases := []testCase{
@ -242,6 +251,27 @@ func TestCoinSelectSubtractFees(t *testing.T) {
expectedFundingAmt: 1*btcutil.SatoshiPerBitcoin - fundingFee(feeRate, 1, false), expectedFundingAmt: 1*btcutil.SatoshiPerBitcoin - fundingFee(feeRate, 1, false),
expectedChange: 0, expectedChange: 0,
}, },
{
// We have 1.0 BTC available and spend half of it. This
// should lead to a funding TX with a change output.
name: "spend with change",
coins: []Coin{
{
TxOut: wire.TxOut{
PkScript: p2wkhScript,
Value: 1 * btcutil.SatoshiPerBitcoin,
},
},
},
spendValue: 0.5 * btcutil.SatoshiPerBitcoin,
// The one and only input will be selected.
expectedInput: []btcutil.Amount{
1 * btcutil.SatoshiPerBitcoin,
},
expectedFundingAmt: 0.5*btcutil.SatoshiPerBitcoin - fundingFee(feeRate, 1, true),
expectedChange: 0.5 * btcutil.SatoshiPerBitcoin,
},
{ {
// The total funds available is below the dust limit // The total funds available is below the dust limit
// after paying fees. // after paying fees.
@ -250,13 +280,14 @@ func TestCoinSelectSubtractFees(t *testing.T) {
{ {
TxOut: wire.TxOut{ TxOut: wire.TxOut{
PkScript: p2wkhScript, PkScript: p2wkhScript,
Value: int64(fundingFee(feeRate, 1, false) + dust), Value: int64(fundingFee(feeRate, 1, false) + dustLimit),
}, },
}, },
}, },
spendValue: fundingFee(feeRate, 1, false) + dust, spendValue: fundingFee(feeRate, 1, false) + dust,
expectErr: true, expectErr: "output amount(<amt> BTC) after subtracting " +
"fees(<amt> BTC) below dust limit(<amt> BTC)",
}, },
{ {
// After subtracting fees, the resulting change output // After subtracting fees, the resulting change output
@ -320,18 +351,19 @@ func TestCoinSelectSubtractFees(t *testing.T) {
}, },
{ {
// If more than 20% of funds goes to fees, it should fail. // If more than 20% of funds goes to fees, it should fail.
name: "high fee", name: "high fee",
highFee: true,
coins: []Coin{ coins: []Coin{
{ {
TxOut: wire.TxOut{ TxOut: wire.TxOut{
PkScript: p2wkhScript, PkScript: p2wkhScript,
Value: int64(5 * fundingFee(feeRate, 1, false)), Value: int64(5 * fundingFee(highFeeRate, 1, false)),
}, },
}, },
}, },
spendValue: 5 * fundingFee(feeRate, 1, false), spendValue: 5 * fundingFee(highFeeRate, 1, false),
expectErr: true, expectErr: "fee <amt> BTC on total output value <amt> BTC",
}, },
} }
@ -339,22 +371,36 @@ func TestCoinSelectSubtractFees(t *testing.T) {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
feeRate := feeRate
if test.highFee {
feeRate = highFeeRate
}
selected, localFundingAmt, changeAmt, err := CoinSelectSubtractFees( selected, localFundingAmt, changeAmt, err := CoinSelectSubtractFees(
feeRate, test.spendValue, dustLimit, test.coins, feeRate, test.spendValue, dustLimit, test.coins,
) )
if !test.expectErr && err != nil { if err != nil {
t.Fatalf(err.Error()) switch {
case test.expectErr == "":
t.Fatalf(err.Error())
case test.expectErr != removeAmounts(err.Error()):
t.Fatalf("expected error '%v', got '%v'",
test.expectErr,
removeAmounts(err.Error()))
// If we got an expected error, there is
// nothing more to test.
default:
return
}
} }
if test.expectErr && err == nil { // Check that there was no expected error we missed.
if test.expectErr != "" {
t.Fatalf("expected error") t.Fatalf("expected error")
} }
// If we got an expected error, there is nothing more to test.
if test.expectErr {
return
}
// Check that the selected inputs match what we expect. // Check that the selected inputs match what we expect.
if len(selected) != len(test.expectedInput) { if len(selected) != len(test.expectedInput) {
t.Fatalf("expected %v inputs, got %v", t.Fatalf("expected %v inputs, got %v",