@@ -255,7 +255,7 @@ type ChanCloser struct {
255255// calcCoopCloseFee computes an "ideal" absolute co-op close fee given the
256256// delivery scripts of both parties and our ideal fee rate.
257257func calcCoopCloseFee (chanType channeldb.ChannelType ,
258- localOutput , remoteOutput * wire.TxOut ,
258+ localOutput , remoteOutput * wire.TxOut , extraOutputs [] * wire. TxOut ,
259259 idealFeeRate chainfee.SatPerKWeight ) btcutil.Amount {
260260
261261 var weightEstimator input.TxWeightEstimator
@@ -276,6 +276,9 @@ func calcCoopCloseFee(chanType channeldb.ChannelType,
276276 if remoteOutput != nil {
277277 weightEstimator .AddTxOutput (remoteOutput )
278278 }
279+ for _ , extraOutput := range extraOutputs {
280+ weightEstimator .AddTxOutput (extraOutput )
281+ }
279282
280283 totalWeight := weightEstimator .Weight ()
281284
@@ -295,7 +298,64 @@ func (d *SimpleCoopFeeEstimator) EstimateFee(chanType channeldb.ChannelType,
295298 localTxOut , remoteTxOut * wire.TxOut ,
296299 idealFeeRate chainfee.SatPerKWeight ) btcutil.Amount {
297300
298- return calcCoopCloseFee (chanType , localTxOut , remoteTxOut , idealFeeRate )
301+ return calcCoopCloseFee (
302+ chanType , localTxOut , remoteTxOut , nil , idealFeeRate ,
303+ )
304+ }
305+
306+ // estimateCloseFee computes a close fee for the given fee rate while taking
307+ // into account any optional auxiliary close outputs.
308+ func (c * ChanCloser ) estimateCloseFee (localTxOut , remoteTxOut * wire.TxOut ,
309+ feeRate chainfee.SatPerKWeight ) (btcutil.Amount , error ) {
310+
311+ // Historical behavior uses the default channel type here and doesn't
312+ // differentiate between channel feature variants.
313+ var defaultChanType channeldb.ChannelType
314+ fee := c .cfg .FeeEstimator .EstimateFee (
315+ defaultChanType , localTxOut , remoteTxOut , feeRate ,
316+ )
317+
318+ if c .cfg .AuxCloser .IsNone () {
319+ return fee , nil
320+ }
321+
322+ // Aux output selection can depend on CloseFee. After we bump the fee to
323+ // account for extra output weight, the aux closer can return a different
324+ // output set (for example around dust thresholds). Run a second pass so
325+ // the fee and output set are self-consistent, while keeping this bounded.
326+ for range 2 {
327+ auxOutputs , err := c .auxCloseOutputs (fee )
328+ if err != nil {
329+ return 0 , err
330+ }
331+
332+ var extraOutputs []* wire.TxOut
333+ auxOutputs .WhenSome (func (outs AuxCloseOutputs ) {
334+ extraOutputs = make (
335+ []* wire.TxOut , 0 , len (outs .ExtraCloseOutputs ),
336+ )
337+ for _ , closeOutput := range outs .ExtraCloseOutputs {
338+ txOut := closeOutput .TxOut
339+ extraOutputs = append (extraOutputs , & txOut )
340+ }
341+ })
342+
343+ if len (extraOutputs ) == 0 {
344+ return fee , nil
345+ }
346+
347+ feeWithAuxOutputs := calcCoopCloseFee (
348+ defaultChanType , localTxOut , remoteTxOut ,
349+ extraOutputs , feeRate ,
350+ )
351+ if feeWithAuxOutputs <= fee {
352+ return fee , nil
353+ }
354+
355+ fee = feeWithAuxOutputs
356+ }
357+
358+ return fee , nil
299359}
300360
301361// NewChanCloser creates a new instance of the channel closure given the passed
@@ -327,7 +387,7 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript DeliveryAddrWithKey,
327387
328388// initFeeBaseline computes our ideal fee rate, and also the largest fee we'll
329389// accept given information about the delivery script of the remote party.
330- func (c * ChanCloser ) initFeeBaseline () {
390+ func (c * ChanCloser ) initFeeBaseline () error {
331391 // Depending on if a balance ends up being dust or not, we'll pass a
332392 // nil TxOut into the EstimateFee call which can handle it.
333393 var localTxOut , remoteTxOut * wire.TxOut
@@ -346,18 +406,25 @@ func (c *ChanCloser) initFeeBaseline() {
346406
347407 // Given the target fee-per-kw, we'll compute what our ideal _total_
348408 // fee will be starting at for this fee negotiation.
349- c .idealFeeSat = c .cfg .FeeEstimator .EstimateFee (
350- 0 , localTxOut , remoteTxOut , c .idealFeeRate ,
409+ var err error
410+ c .idealFeeSat , err = c .estimateCloseFee (
411+ localTxOut , remoteTxOut , c .idealFeeRate ,
351412 )
413+ if err != nil {
414+ return err
415+ }
352416
353417 // When we're the initiator, we'll want to also factor in the highest
354418 // fee we want to pay. This'll either be 3x the ideal fee, or the
355419 // specified explicit max fee.
356420 c .maxFee = c .idealFeeSat * defaultMaxFeeMultiplier
357421 if c .cfg .MaxFee > 0 {
358- c .maxFee = c .cfg . FeeEstimator . EstimateFee (
359- 0 , localTxOut , remoteTxOut , c .cfg .MaxFee ,
422+ c .maxFee , err = c .estimateCloseFee (
423+ localTxOut , remoteTxOut , c .cfg .MaxFee ,
360424 )
425+ if err != nil {
426+ return err
427+ }
361428 }
362429
363430 // TODO(ziggie): Make sure the ideal fee is not higher than the max fee.
@@ -366,6 +433,8 @@ func (c *ChanCloser) initFeeBaseline() {
366433 chancloserLog .Infof ("Ideal fee for closure of ChannelPoint(%v) " +
367434 "is: %v sat (max_fee=%v sat)" , c .cfg .Channel .ChannelPoint (),
368435 int64 (c .idealFeeSat ), int64 (c .maxFee ))
436+
437+ return nil
369438}
370439
371440// initChanShutdown begins the shutdown process by un-registering the channel,
@@ -740,14 +809,17 @@ func (c *ChanCloser) BeginNegotiation() (fn.Option[lnwire.ClosingSigned],
740809 case closeAwaitingFlush :
741810 // Now that we know their desired delivery script, we can
742811 // compute what our max/ideal fee will be.
743- c .initFeeBaseline ()
812+ err := c .initFeeBaseline ()
813+ if err != nil {
814+ return noClosingSigned , err
815+ }
744816
745817 // Before continuing, mark the channel as cooperatively closed
746818 // with a nil txn. Even though we haven't negotiated the final
747819 // txn, this guarantees that our listchannels rpc will be
748820 // externally consistent, and reflect that the channel is being
749821 // shutdown by the time the closing request returns.
750- err : = c .cfg .Channel .MarkCoopBroadcasted (
822+ err = c .cfg .Channel .MarkCoopBroadcasted (
751823 nil , c .closer ,
752824 )
753825 if err != nil {
0 commit comments