@@ -58,6 +58,10 @@ const (
5858 // must assert that the output value of the selected existing output
5959 // already is above dust when using this change address type.
6060 ExistingChangeAddress ChangeAddressType = 2
61+
62+ // DefaultMaxFeeRatio is the default fee to total amount of outputs
63+ // ratio that is used to sanity check the fees of a transaction.
64+ DefaultMaxFeeRatio float64 = 0.2
6165)
6266
6367// selectInputs selects a slice of inputs necessary to meet the specified
@@ -143,16 +147,25 @@ func calculateFees(utxos []wallet.Coin, feeRate chainfee.SatPerKWeight,
143147 return requiredFeeNoChange , requiredFeeWithChange , nil
144148}
145149
146- // sanityCheckFee checks if the specified fee amounts to over 20% of the total
147- // output amount and raises an error.
148- func sanityCheckFee (totalOut , fee btcutil.Amount ) error {
149- // Fail if more than 20% goes to fees.
150- // TODO(halseth): smarter fee limit. Make configurable or dynamic wrt
151- // total funding size?
152- if fee > totalOut / 5 {
153- return fmt .Errorf ("fee (%v) exceeds 20%% of total output (%v)" ,
154- fee , totalOut )
150+ // sanityCheckFee checks if the specified fee amounts to what the provided ratio
151+ // allows.
152+ func sanityCheckFee (totalOut , fee btcutil.Amount , maxFeeRatio float64 ) error {
153+ // Sanity check the maxFeeRatio itself.
154+ if maxFeeRatio <= 0.00 || maxFeeRatio > 1.00 {
155+ return fmt .Errorf ("maxFeeRatio must be between 0.00 and 1.00 " +
156+ "got %.2f" , maxFeeRatio )
157+ }
158+
159+ maxFee := btcutil .Amount (float64 (totalOut ) * maxFeeRatio )
160+
161+ // Check that the fees do not exceed the max allowed value.
162+ if fee > maxFee {
163+ return fmt .Errorf ("fee %v exceeds max fee (%v) on total " +
164+ "output value %v with max fee ratio of %.2f" , fee ,
165+ maxFee , totalOut , maxFeeRatio )
155166 }
167+
168+ // All checks passed, we return nil to signal that the fees are valid.
156169 return nil
157170}
158171
@@ -163,7 +176,8 @@ func sanityCheckFee(totalOut, fee btcutil.Amount) error {
163176func CoinSelect (feeRate chainfee.SatPerKWeight , amt , dustLimit btcutil.Amount ,
164177 coins []wallet.Coin , strategy wallet.CoinSelectionStrategy ,
165178 existingWeight input.TxWeightEstimator ,
166- changeType ChangeAddressType ) ([]wallet.Coin , btcutil.Amount , error ) {
179+ changeType ChangeAddressType , maxFeeRatio float64 ) ([]wallet.Coin ,
180+ btcutil.Amount , error ) {
167181
168182 amtNeeded := amt
169183 for {
@@ -188,6 +202,7 @@ func CoinSelect(feeRate chainfee.SatPerKWeight, amt, dustLimit btcutil.Amount,
188202 changeAmount , newAmtNeeded , err := CalculateChangeAmount (
189203 totalSat , amt , requiredFeeNoChange ,
190204 requiredFeeWithChange , dustLimit , changeType ,
205+ maxFeeRatio ,
191206 )
192207 if err != nil {
193208 return nil , 0 , err
@@ -216,7 +231,8 @@ func CoinSelect(feeRate chainfee.SatPerKWeight, amt, dustLimit btcutil.Amount,
216231// fees and that more coins need to be selected.
217232func CalculateChangeAmount (totalInputAmt , requiredAmt , requiredFeeNoChange ,
218233 requiredFeeWithChange , dustLimit btcutil.Amount ,
219- changeType ChangeAddressType ) (btcutil.Amount , btcutil.Amount , error ) {
234+ changeType ChangeAddressType , maxFeeRatio float64 ) (btcutil.Amount ,
235+ btcutil.Amount , error ) {
220236
221237 // This is just a sanity check to make sure the function is used
222238 // correctly.
@@ -266,7 +282,8 @@ func CalculateChangeAmount(totalInputAmt, requiredAmt, requiredFeeNoChange,
266282 // Sanity check the resulting output values to make sure we
267283 // don't burn a great part to fees.
268284 totalOut := requiredAmt + changeAmt
269- err := sanityCheckFee (totalOut , totalInputAmt - totalOut )
285+
286+ err := sanityCheckFee (totalOut , totalInputAmt - totalOut , maxFeeRatio )
270287 if err != nil {
271288 return 0 , 0 , err
272289 }
@@ -280,9 +297,9 @@ func CalculateChangeAmount(totalInputAmt, requiredAmt, requiredFeeNoChange,
280297func CoinSelectSubtractFees (feeRate chainfee.SatPerKWeight , amt ,
281298 dustLimit btcutil.Amount , coins []wallet.Coin ,
282299 strategy wallet.CoinSelectionStrategy ,
283- existingWeight input.TxWeightEstimator ,
284- changeType ChangeAddressType ) ([]wallet.Coin , btcutil.Amount ,
285- btcutil. Amount , error ) {
300+ existingWeight input.TxWeightEstimator , changeType ChangeAddressType ,
301+ maxFeeRatio float64 ) ([]wallet.Coin , btcutil. Amount , btcutil.Amount ,
302+ error ) {
286303
287304 // First perform an initial round of coin selection to estimate
288305 // the required fee.
@@ -331,7 +348,7 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
331348 // Sanity check the resulting output values to make sure we
332349 // don't burn a great part to fees.
333350 totalOut := outputAmt + changeAmt
334- err = sanityCheckFee (totalOut , totalSat - totalOut )
351+ err = sanityCheckFee (totalOut , totalSat - totalOut , maxFeeRatio )
335352 if err != nil {
336353 return nil , 0 , 0 , err
337354 }
@@ -347,8 +364,8 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
347364 reserved , dustLimit btcutil.Amount , coins []wallet.Coin ,
348365 strategy wallet.CoinSelectionStrategy ,
349366 existingWeight input.TxWeightEstimator ,
350- changeType ChangeAddressType ) ([]wallet.Coin , btcutil. Amount ,
351- btcutil.Amount , error ) {
367+ changeType ChangeAddressType , maxFeeRatio float64 ) ([]wallet.Coin ,
368+ btcutil.Amount , btcutil. Amount , error ) {
352369
353370 var (
354371 // selectSubtractFee is tracking if our coin selection was
@@ -369,7 +386,7 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
369386 // maxAmount with or without a change output that covers the miner fee.
370387 selected , changeAmt , err := CoinSelect (
371388 feeRate , maxAmount , dustLimit , coins , strategy , existingWeight ,
372- changeType ,
389+ changeType , maxFeeRatio ,
373390 )
374391
375392 var errInsufficientFunds * ErrInsufficientFunds
@@ -412,7 +429,7 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
412429 if selectSubtractFee {
413430 selected , outputAmount , changeAmt , err = CoinSelectSubtractFees (
414431 feeRate , totalBalance - reserved , dustLimit , coins ,
415- strategy , existingWeight , changeType ,
432+ strategy , existingWeight , changeType , maxFeeRatio ,
416433 )
417434 if err != nil {
418435 return nil , 0 , 0 , err
@@ -430,7 +447,7 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
430447
431448 return sum
432449 }
433- err = sanityCheckFee (totalOut , sum (selected )- totalOut )
450+ err = sanityCheckFee (totalOut , sum (selected )- totalOut , maxFeeRatio )
434451 if err != nil {
435452 return nil , 0 , 0 , err
436453 }
0 commit comments