43
43
// ErrInvalidShutdownScript is returned when we receive an address from
44
44
// a peer that isn't either a p2wsh or p2tr address.
45
45
ErrInvalidShutdownScript = fmt .Errorf ("invalid shutdown script" )
46
+
47
+ // ErrCloseTypeChanged is returned when the counterparty attempts to
48
+ // change the negotiation type from fee-range to legacy or vice versa.
49
+ ErrCloseTypeChanged = fmt .Errorf ("close type changed" )
50
+
51
+ // ErrNoRangeOverlap is returned when there is no overlap between our
52
+ // and our counterparty's fee_range.
53
+ ErrNoRangeOverlap = fmt .Errorf ("no range overlap" )
54
+
55
+ // ErrFeeNotInOverlap is returned when the counterparty sends a fee that
56
+ // is not in the overlapping fee_range.
57
+ ErrFeeNotInOverlap = fmt .Errorf ("fee not in overlap" )
58
+
59
+ // ErrFeeRangeViolation is returned when the fundee receives a bad
60
+ // FeeRange from the funder after the fundee has sent their one and
61
+ // only FeeRange to the funder.
62
+ ErrFeeRangeViolation = fmt .Errorf ("fee range violation" )
46
63
)
47
64
48
65
// closeState represents all the possible states the channel closer state
@@ -219,6 +236,9 @@ type ChanCloser struct {
219
236
// idealFeeRate is our ideal fee rate.
220
237
idealFeeRate chainfee.SatPerKWeight
221
238
239
+ // idealFeeRange is our ideal fee range.
240
+ idealFeeRange * lnwire.FeeRange
241
+
222
242
// lastFeeProposal is the last fee that we proposed to the remote party.
223
243
// We'll use this as a pivot point to ratchet our next offer up, down, or
224
244
// simply accept the remote party's prior offer.
@@ -271,6 +291,16 @@ type ChanCloser struct {
271
291
// waiting for the peer's Shutdown and will call ChannelClean when we
272
292
// receive it. This is only used when we restart the connection.
273
293
cleanOnRecv bool
294
+
295
+ // legacyNegotiation means that legacy negotiation has been initiated.
296
+ // This is used so that the remote can't change from legacy to range
297
+ // based negotiation.
298
+ legacyNegotiation bool
299
+
300
+ // rangeNegotiation means that range-based negotiation has been
301
+ // initiated. This is used so the remote can't change from range-based
302
+ // to legacy negotiation.
303
+ rangeNegotiation bool
274
304
}
275
305
276
306
// calcCoopCloseFee computes an "ideal" absolute co-op close fee given the
@@ -371,6 +401,24 @@ func (c *ChanCloser) initFeeBaseline() {
371
401
)
372
402
}
373
403
404
+ // Calculate the minimum fee we'll accept for the fee range.
405
+ minFeeSats := c .cfg .FeeEstimator .EstimateFee (
406
+ 0 , localTxOut , remoteTxOut , chainfee .FeePerKwFloor ,
407
+ )
408
+
409
+ // Populate the fee range. If minFeeSats is greater than idealFeeSat,
410
+ // use idealFeeSat as the minimum. This may happen since FeePerKwFloor
411
+ // uses 253 sat/kw instead of 250 sat/kw.
412
+ c .idealFeeRange = & lnwire.FeeRange {
413
+ MaxFeeSats : c .maxFee ,
414
+ }
415
+
416
+ if minFeeSats > c .idealFeeSat {
417
+ c .idealFeeRange .MinFeeSats = c .idealFeeSat
418
+ } else {
419
+ c .idealFeeRange .MinFeeSats = minFeeSats
420
+ }
421
+
374
422
chancloserLog .Infof ("Ideal fee for closure of ChannelPoint(%v) " +
375
423
"is: %v sat (max_fee=%v sat)" , c .cfg .Channel .ChannelPoint (),
376
424
int64 (c .idealFeeSat ), int64 (c .maxFee ))
@@ -695,34 +743,20 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message, remote bool) (
695
743
// we'll attempt to ratchet the fee closer to
696
744
remoteProposedFee := closeSignedMsg .FeeSatoshis
697
745
if _ , ok := c .priorFeeOffers [remoteProposedFee ]; ! ok {
698
- // We'll now attempt to ratchet towards a fee deemed acceptable by
699
- // both parties, factoring in our ideal fee rate, and the last
700
- // proposed fee by both sides.
701
- feeProposal := calcCompromiseFee (c .chanPoint , c .idealFeeSat ,
702
- c .lastFeeProposal , remoteProposedFee ,
703
- )
704
- if c .cfg .Channel .IsInitiator () && feeProposal > c .maxFee {
705
- return nil , false , fmt .Errorf ("%w: %v > %v" ,
706
- ErrProposalExeceedsMaxFee , feeProposal ,
707
- c .maxFee )
708
- }
709
-
710
- // With our new fee proposal calculated, we'll craft a new close
711
- // signed signature to send to the other party so we can continue
712
- // the fee negotiation process.
713
- closeSigned , err := c .proposeCloseSigned (feeProposal )
746
+ response , err := c .handleRemoteProposal (closeSignedMsg )
714
747
if err != nil {
748
+ // If an error was returned, no message was
749
+ // returned. Bubble up the error.
715
750
return nil , false , err
716
751
}
717
752
718
- // If the compromise fee doesn't match what the peer proposed, then
719
- // we'll return this latest close signed message so we can continue
720
- // negotiation.
721
- if feeProposal != remoteProposedFee {
722
- chancloserLog .Debugf ("ChannelPoint(%v): close tx fee " +
723
- "disagreement, continuing negotiation" , c .chanPoint )
724
- return []lnwire.Message {closeSigned }, false , nil
753
+ // If a response was returned, bubble up the response.
754
+ if response != nil {
755
+ return []lnwire.Message {response }, false , nil
725
756
}
757
+
758
+ // Else, no response or error was returned so we can
759
+ // finish up negotiation.
726
760
}
727
761
728
762
chancloserLog .Infof ("ChannelPoint(%v) fee of %v accepted, ending " +
@@ -828,7 +862,9 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign
828
862
// We'll assemble a ClosingSigned message using this information and return
829
863
// it to the caller so we can kick off the final stage of the channel
830
864
// closure process.
831
- closeSignedMsg := lnwire .NewClosingSigned (c .cid , fee , parsedSig , nil )
865
+ closeSignedMsg := lnwire .NewClosingSigned (
866
+ c .cid , fee , parsedSig , c .idealFeeRange ,
867
+ )
832
868
833
869
// We'll also save this close signed, in the case that the remote party
834
870
// accepts our offer. This way, we don't have to re-sign.
@@ -837,6 +873,175 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign
837
873
return closeSignedMsg , nil
838
874
}
839
875
876
+ // handleRemoteProposal sanity checks the remote's ClosingSigned message and
877
+ // tries to determine an acceptable fee to reply with. It may return:
878
+ // - a message and no error (continue negotiation)
879
+ // - no message and an error (negotiation failed)
880
+ // - no message and no error (indicating we agree with the remote's fee)
881
+ func (c * ChanCloser ) handleRemoteProposal (
882
+ remoteMsg * lnwire.ClosingSigned ) (lnwire.Message , error ) {
883
+
884
+ isFunder := c .cfg .Channel .IsInitiator ()
885
+
886
+ // If FeeRange is set, perform FeeRange-specific checks.
887
+ if remoteMsg .FeeRange != nil {
888
+ // If legacyNegotiation is already set, fail outright.
889
+ if c .legacyNegotiation {
890
+ return nil , ErrCloseTypeChanged
891
+ }
892
+
893
+ // Set rangeNegotiation to true.
894
+ c .rangeNegotiation = true
895
+
896
+ // Get the intersection of our two FeeRanges if one exists.
897
+ overlap := c .idealFeeRange .GetOverlap (remoteMsg .FeeRange )
898
+
899
+ if isFunder {
900
+ // If the fundee replies with a FeeRange, there must be
901
+ // overlap with our FeeRange.
902
+ //
903
+ // BOLT#02:
904
+ // - otherwise (it is not the funder)
905
+ // - ...
906
+ // - otherwise
907
+ // - MUST propose a fee_satoshis in the overlap
908
+ // between received and (about-to-be) sent
909
+ // fee_range.
910
+ if overlap == nil {
911
+ return nil , ErrNoRangeOverlap
912
+ }
913
+
914
+ // This is included in the above requirement.
915
+ if ! overlap .InRange (remoteMsg .FeeSatoshis ) {
916
+ return nil , ErrFeeNotInOverlap
917
+ }
918
+
919
+ // If the above checks pass, the funder must reply with
920
+ // the same fee_satoshis.
921
+ //
922
+ // BOLT#02:
923
+ // - if it is the funder:
924
+ // - ...
925
+ // - otherwise:
926
+ // - MUST reply with the same fee_satoshis.
927
+ _ , err := c .proposeCloseSigned (remoteMsg .FeeSatoshis )
928
+ if err != nil {
929
+ return nil , err
930
+ }
931
+
932
+ // Return nil values to indicate we are done with
933
+ // negotiation.
934
+ return nil , nil
935
+ }
936
+
937
+ // If we are the fundee and we have already sent a ClosingSigned
938
+ // to the funder, we should not be calling this function.
939
+ //
940
+ // BOLT#02:
941
+ // - if it is the funder:
942
+ // - ...
943
+ // - otherwise:
944
+ // - MUST reply with the same fee_satoshis.
945
+ //
946
+ // Since the funder must reply with the same fee_satoshis, the
947
+ // calling function should not call into this negotiation
948
+ // function.
949
+ if c .lastFeeProposal != 0 {
950
+ return nil , ErrFeeRangeViolation
951
+ }
952
+
953
+ // If we are the fundee and there is no overlap between their
954
+ // fee_range and our yet-to-be-sent fee_range, send a warning.
955
+ //
956
+ // BOLT#02:
957
+ // - if there is no overlap between that and its own fee_range
958
+ // - SHOULD send a warning.
959
+ //
960
+ // NOTE: The above SHOULD will probably be changed to MUST.
961
+ if overlap == nil {
962
+ warning := lnwire .NewWarning ()
963
+ warning .ChanID = remoteMsg .ChannelID
964
+ warning .Data = lnwire .WarningData ("ClosingSigned: no " +
965
+ "fee_range overlap" )
966
+ return warning , nil
967
+ }
968
+
969
+ // If we've reached this point, then we have to propose a fee
970
+ // in the overlap.
971
+ //
972
+ // BOLT#02:
973
+ // - otherwise (it is not the funder)
974
+ // - ...
975
+ // - otherwise
976
+ // - MUST propose a fee_satoshis in the overlap between
977
+ // received and (about-to-be) sent fee_range.
978
+ //
979
+ // If our ideal fee is in the overlap, use that. If it's not in
980
+ // the overlap, use the upper bound of the overlap.
981
+ var feeProposal btcutil.Amount
982
+ if overlap .InRange (c .idealFeeSat ) {
983
+ feeProposal = c .idealFeeSat
984
+ } else {
985
+ feeProposal = overlap .MaxFeeSats
986
+ }
987
+
988
+ closeSigned , err := c .proposeCloseSigned (feeProposal )
989
+ if err != nil {
990
+ return nil , err
991
+ }
992
+
993
+ // If the feeProposal is not equal to the remote's FeeSatoshis,
994
+ // negotiation isn't done.
995
+ if feeProposal != remoteMsg .FeeSatoshis {
996
+ return closeSigned , nil
997
+ }
998
+
999
+ // Otherwise, negotiation is done.
1000
+ return nil , nil
1001
+ }
1002
+
1003
+ // Else, do the legacy negotiation. If rangeNegotiation is already set,
1004
+ // fail outright.
1005
+ if c .rangeNegotiation {
1006
+ return nil , ErrCloseTypeChanged
1007
+ }
1008
+
1009
+ // Set legacyNegotiation to true.
1010
+ c .legacyNegotiation = true
1011
+
1012
+ // We'll now attempt to ratchet towards a fee deemed acceptable by both
1013
+ // parties, factoring in our ideal fee rate, and the last proposed fee
1014
+ // by both sides.
1015
+ feeProposal := calcCompromiseFee (
1016
+ c .chanPoint , c .idealFeeSat , c .lastFeeProposal ,
1017
+ remoteMsg .FeeSatoshis ,
1018
+ )
1019
+ if isFunder && feeProposal > c .maxFee {
1020
+ return nil , fmt .Errorf ("%w: %v > %v" , ErrProposalExeceedsMaxFee ,
1021
+ feeProposal , c .maxFee )
1022
+ }
1023
+
1024
+ // With our new fee proposal calculated, we'll craft a new close signed
1025
+ // signature to send to the other party so we can continue the fee
1026
+ // negotiation process.
1027
+ closeSigned , err := c .proposeCloseSigned (feeProposal )
1028
+ if err != nil {
1029
+ return nil , err
1030
+ }
1031
+
1032
+ // If the compromise fee doesn't match what the peer proposed, then
1033
+ // we'll return this latest close signed message so we can continue
1034
+ // negotiation.
1035
+ if feeProposal != remoteMsg .FeeSatoshis {
1036
+ chancloserLog .Debugf ("ChannelPoint(%v): close tx fee " +
1037
+ "disagreement, continuing negotiation" , c .chanPoint )
1038
+ return closeSigned , nil
1039
+ }
1040
+
1041
+ // We are done with negotiation.
1042
+ return nil , nil
1043
+ }
1044
+
840
1045
// feeInAcceptableRange returns true if the passed remote fee is deemed to be
841
1046
// in an "acceptable" range to our local fee. This is an attempt at a
842
1047
// compromise and to ensure that the fee negotiation has a stopping point. We
0 commit comments