diff --git a/lnrpc/walletrpc/walletkit_server_test.go b/lnrpc/walletrpc/walletkit_server_test.go index 115ccf96e0..4973a0af47 100644 --- a/lnrpc/walletrpc/walletkit_server_test.go +++ b/lnrpc/walletrpc/walletkit_server_test.go @@ -162,10 +162,18 @@ func TestFundPsbtCoinSelect(t *testing.T) { // packet in bytes. expectedFee btcutil.Amount + // maxFeeRatio is the maximum fee to total output amount ratio + // that we consider valid. + maxFeeRatio float64 + // expectedErr is the expected concrete error. If not nil, then // the error must match exactly. expectedErr error + // expectedContainedErrStr is the expected string to be + // contained in the returned error. + expectedContainedErrStr string + // expectedErrType is the expected error type. If not nil, then // the error must be of this type. expectedErrType error @@ -178,6 +186,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, expectedErrType: &chanfunding.ErrInsufficientFunds{}, }, { name: "1 p2wpkh utxo, add p2wkh change", @@ -193,6 +202,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, expectedUtxoIndexes: []int{0}, expectChangeOutputIndex: 1, expectedFee: calcFee(0, 1, 1, 1, 0), @@ -210,6 +220,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.P2TRChangeAddress, expectedUtxoIndexes: []int{0}, expectChangeOutputIndex: 1, @@ -228,6 +239,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, expectedUtxoIndexes: []int{0}, expectChangeOutputIndex: -1, expectedFee: calcFee(0, 1, 1, 0, 0), @@ -247,6 +259,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.P2WKHChangeAddress, expectedUtxoIndexes: []int{0}, expectChangeOutputIndex: -1, @@ -267,6 +280,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.P2TRChangeAddress, expectedUtxoIndexes: []int{0}, expectChangeOutputIndex: -1, @@ -285,6 +299,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: 0, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.ExistingChangeAddress, expectedUtxoIndexes: []int{0}, expectChangeOutputIndex: 0, @@ -303,6 +318,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: 0, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.ExistingChangeAddress, expectedUtxoIndexes: []int{0}, expectChangeOutputIndex: 0, @@ -321,6 +337,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: 0, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.ExistingChangeAddress, expectedUtxoIndexes: []int{0}, expectChangeOutputIndex: 0, @@ -369,6 +386,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: 0, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.ExistingChangeAddress, expectedUtxoIndexes: []int{0, 1}, expectChangeOutputIndex: 0, @@ -417,6 +435,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.P2TRChangeAddress, expectedUtxoIndexes: []int{0, 1}, expectChangeOutputIndex: 1, @@ -456,6 +475,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.P2WKHChangeAddress, expectedUtxoIndexes: []int{}, expectChangeOutputIndex: 1, @@ -497,10 +517,51 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: -1, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.P2TRChangeAddress, expectedUtxoIndexes: []int{}, expectChangeOutputIndex: -1, expectedFee: calcFee(1, 0, 1, 0, 0), + }, { + name: "1 p2wpkh utxo, existing p2wkh change, invalid fee ratio", + utxos: []*lnwallet.Utxo{ + { + Value: 250, + PkScript: p2wkhScript, + }, + }, + packet: makePacket(&wire.TxOut{ + Value: 50, + PkScript: p2wkhScript, + }), + changeIndex: 0, + feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, + changeType: chanfunding.ExistingChangeAddress, + expectedUtxoIndexes: []int{0}, + expectChangeOutputIndex: 0, + expectedFee: calcFee(0, 1, 0, 1, 0), + + expectedContainedErrStr: "with max fee ratio", + }, { + name: "1 p2wpkh utxo, existing p2wkh change, big fee ratio", + utxos: []*lnwallet.Utxo{ + { + Value: 250, + PkScript: p2wkhScript, + }, + }, + packet: makePacket(&wire.TxOut{ + Value: 50, + PkScript: p2wkhScript, + }), + changeIndex: 0, + feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: 0.85, + changeType: chanfunding.ExistingChangeAddress, + expectedUtxoIndexes: []int{0}, + expectChangeOutputIndex: 0, + expectedFee: calcFee(0, 1, 0, 1, 0), }, { name: "large existing p2tr input, fee estimation existing " + "change output", @@ -536,6 +597,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }), changeIndex: 0, feeRate: chainfee.FeePerKwFloor, + maxFeeRatio: chanfunding.DefaultMaxFeeRatio, changeType: chanfunding.ExistingChangeAddress, expectedUtxoIndexes: []int{}, expectChangeOutputIndex: 0, @@ -575,6 +637,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { "", tc.changeIndex, copiedPacket, 0, tc.changeType, tc.feeRate, rpcServer.cfg.CoinSelectionStrategy, + tc.maxFeeRatio, ) switch { @@ -588,6 +651,12 @@ func TestFundPsbtCoinSelect(t *testing.T) { require.Error(tt, err) require.ErrorAs(tt, err, &tc.expectedErr) + return + case tc.expectedContainedErrStr != "": + require.ErrorContains( + tt, err, tc.expectedContainedErrStr, + ) + return } diff --git a/lnwallet/chanfunding/coin_select_test.go b/lnwallet/chanfunding/coin_select_test.go index 26a257b37f..e090dacfd0 100644 --- a/lnwallet/chanfunding/coin_select_test.go +++ b/lnwallet/chanfunding/coin_select_test.go @@ -317,6 +317,7 @@ func TestCoinSelect(t *testing.T) { feeRate, test.outputValue, dustLimit, test.coins, wallet.CoinSelectionLargest, fundingOutputEstimate, test.changeType, + DefaultMaxFeeRatio, ) if test.expectErr { @@ -356,6 +357,7 @@ func TestCalculateChangeAmount(t *testing.T) { feeWithChange btcutil.Amount dustLimit btcutil.Amount changeType ChangeAddressType + maxFeeRatio float64 expectErr string expectChangeAmt btcutil.Amount @@ -369,6 +371,7 @@ func TestCalculateChangeAmount(t *testing.T) { totalInputAmt: 500, requiredAmt: 490, feeNoChange: 12, + maxFeeRatio: DefaultMaxFeeRatio, expectNeedMore: 502, }, { @@ -383,6 +386,7 @@ func TestCalculateChangeAmount(t *testing.T) { feeWithChange: 10, dustLimit: 100, changeType: ExistingChangeAddress, + maxFeeRatio: DefaultMaxFeeRatio, expectChangeAmt: 90, }, { @@ -392,6 +396,7 @@ func TestCalculateChangeAmount(t *testing.T) { feeNoChange: 40, feeWithChange: 50, dustLimit: 100, + maxFeeRatio: DefaultMaxFeeRatio, expectChangeAmt: 150, }, { @@ -401,6 +406,7 @@ func TestCalculateChangeAmount(t *testing.T) { requiredAmt: 460, feeNoChange: 40, feeWithChange: 50, + maxFeeRatio: DefaultMaxFeeRatio, expectChangeAmt: 0, }, { @@ -410,14 +416,36 @@ func TestCalculateChangeAmount(t *testing.T) { feeNoChange: 10, feeWithChange: 45, dustLimit: 5, + maxFeeRatio: DefaultMaxFeeRatio, expectErr: "fee 0.00000045 BTC on total output value " + "0.00000055", + }, { + name: "fee percent ok", + totalInputAmt: 100, + requiredAmt: 50, + feeNoChange: 10, + feeWithChange: 45, + dustLimit: 5, + maxFeeRatio: 0.95, + + expectChangeAmt: 5, + }, { + name: "invalid max fee ratio", + totalInputAmt: 100, + requiredAmt: 50, + feeNoChange: 10, + feeWithChange: 45, + dustLimit: 5, + maxFeeRatio: 3.14, + + expectErr: "maxFeeRatio must be between 0.00 and 1.00", }, { name: "invalid usage of function", feeNoChange: 5, feeWithChange: 10, changeType: ExistingChangeAddress, + maxFeeRatio: DefaultMaxFeeRatio, expectErr: "fees for with or without change must be the same", }} @@ -428,7 +456,7 @@ func TestCalculateChangeAmount(t *testing.T) { changeAmt, needMore, err := CalculateChangeAmount( tc.totalInputAmt, tc.requiredAmt, tc.feeNoChange, tc.feeWithChange, tc.dustLimit, - tc.changeType, + tc.changeType, tc.maxFeeRatio, ) if tc.expectErr != "" { @@ -627,6 +655,7 @@ func TestCoinSelectSubtractFees(t *testing.T) { wallet.CoinSelectionLargest, fundingOutputEstimate, defaultChanFundingChangeType, + DefaultMaxFeeRatio, ) if err != nil { switch { @@ -872,6 +901,7 @@ func TestCoinSelectUpToAmount(t *testing.T) { wallet.CoinSelectionLargest, fundingOutputEstimate, defaultChanFundingChangeType, + DefaultMaxFeeRatio, ) if len(test.expectErr) == 0 && err != nil { t.Fatal(err.Error())