@@ -551,6 +551,21 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
551
551
remoteOutputIndex = htlc .OutputIndex
552
552
}
553
553
554
+ customRecords := htlc .CustomRecords .Copy ()
555
+ entryType := Add
556
+
557
+ // If the noop HTLC TLV is set then change this HTLC's type to noop.
558
+ noopTLV := uint64 (NoopHtlcType .TypeVal ())
559
+
560
+ if shouldSetNoop (customRecords , lc .channelState .ChanType ) {
561
+ entryType = NoopAdd
562
+ }
563
+
564
+ // The NoopAdd HTLC is an internal construct, and isn't meant to show up
565
+ // on the wire. So we'll remove the special element from the set of
566
+ // custom records.
567
+ delete (customRecords , noopTLV )
568
+
554
569
// With the scripts reconstructed (depending on if this is our commit
555
570
// vs theirs or a pending commit for the remote party), we can now
556
571
// re-create the original payment descriptor.
@@ -559,7 +574,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
559
574
RHash : htlc .RHash ,
560
575
Timeout : htlc .RefundTimeout ,
561
576
Amount : htlc .Amt ,
562
- EntryType : Add ,
577
+ EntryType : entryType ,
563
578
HtlcIndex : htlc .HtlcIndex ,
564
579
LogIndex : htlc .LogIndex ,
565
580
OnionBlob : htlc .OnionBlob ,
@@ -570,7 +585,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
570
585
theirPkScript : theirP2WSH ,
571
586
theirWitnessScript : theirWitnessScript ,
572
587
BlindingPoint : htlc .BlindingPoint ,
573
- CustomRecords : htlc . CustomRecords . Copy () ,
588
+ CustomRecords : customRecords ,
574
589
}, nil
575
590
}
576
591
@@ -1100,6 +1115,10 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
1100
1115
},
1101
1116
}
1102
1117
1118
+ if shouldSetNoop (pd .CustomRecords , lc .channelState .ChanType ) {
1119
+ pd .EntryType = NoopAdd
1120
+ }
1121
+
1103
1122
isDustRemote := HtlcIsDust (
1104
1123
lc .channelState .ChanType , false , lntypes .Remote ,
1105
1124
feeRate , wireMsg .Amount .ToSatoshis (), remoteDustLimit ,
@@ -1336,6 +1355,10 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd
1336
1355
},
1337
1356
}
1338
1357
1358
+ if shouldSetNoop (pd .CustomRecords , lc .channelState .ChanType ) {
1359
+ pd .EntryType = NoopAdd
1360
+ }
1361
+
1339
1362
// We don't need to generate an htlc script yet. This will be
1340
1363
// done once we sign our remote commitment.
1341
1364
@@ -1736,7 +1759,7 @@ func (lc *LightningChannel) restorePendingRemoteUpdates(
1736
1759
// but this Add restoration was a no-op as every single one of
1737
1760
// these Adds was already restored since they're all incoming
1738
1761
// htlcs on the local commitment.
1739
- if payDesc .EntryType == Add {
1762
+ if payDesc .isAdd () {
1740
1763
continue
1741
1764
}
1742
1765
@@ -1881,7 +1904,7 @@ func (lc *LightningChannel) restorePendingLocalUpdates(
1881
1904
}
1882
1905
1883
1906
switch payDesc .EntryType {
1884
- case Add :
1907
+ case Add , NoopAdd :
1885
1908
// The HtlcIndex of the added HTLC _must_ be equal to
1886
1909
// the log's htlcCounter at this point. If it is not we
1887
1910
// panic to catch this.
@@ -2993,6 +3016,47 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
2993
3016
)
2994
3017
if rmvHeight == 0 {
2995
3018
switch {
3019
+ // If this a noop add, then when we settle the
3020
+ // HTLC, we actually credit the sender with the
3021
+ // amount again, thus making it a noop. Noop
3022
+ // HTLCs are only triggered by external software
3023
+ // using the AuxComponents and only for channels
3024
+ // that use the custom tapscript root.
3025
+ case entry .EntryType == Settle &&
3026
+ addEntry .EntryType == NoopAdd :
3027
+
3028
+ channel := lc .channelState
3029
+ party := party
3030
+ delta := balanceDeltas .GetForParty (
3031
+ party ,
3032
+ )
3033
+
3034
+ // If the receiver has existing balance
3035
+ // above dust then we go ahead with
3036
+ // crediting the amount back to the
3037
+ // sender. Otherwise we give the amount
3038
+ // to the receiver. We do this because
3039
+ // the receiver needs some above-dust
3040
+ // balance to anchor the AuxBlob. We
3041
+ // also pass in the so-far calculated
3042
+ // delta for the party, as that's
3043
+ // effectively part of their balance
3044
+ // within this view computation.
3045
+ if channel .BalanceAboveReserve (
3046
+ party , delta ,
3047
+ ) {
3048
+
3049
+ party = party .CounterParty ()
3050
+ }
3051
+
3052
+ d := int64 (entry .Amount )
3053
+ balanceDeltas .ModifyForParty (
3054
+ party ,
3055
+ func (acc int64 ) int64 {
3056
+ return acc + d
3057
+ },
3058
+ )
3059
+
2996
3060
// If an incoming HTLC is being settled, then
2997
3061
// this means that the preimage has been
2998
3062
// received by the settling party Therefore, we
@@ -3030,7 +3094,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
3030
3094
liveAdds := fn .Filter (
3031
3095
view .Updates .GetForParty (party ),
3032
3096
func (pd * paymentDescriptor ) bool {
3033
- isAdd := pd .EntryType == Add
3097
+ isAdd := pd .isAdd ()
3034
3098
shouldSkip := skip .GetForParty (party ).
3035
3099
Contains (pd .HtlcIndex )
3036
3100
@@ -3069,7 +3133,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
3069
3133
// corresponding to whoseCommitmentChain.
3070
3134
isUncommitted := func (update * paymentDescriptor ) bool {
3071
3135
switch update .EntryType {
3072
- case Add :
3136
+ case Add , NoopAdd :
3073
3137
return update .addCommitHeights .GetForParty (
3074
3138
whoseCommitChain ,
3075
3139
) == 0
@@ -3833,7 +3897,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
3833
3897
// Go through all updates, checking that they don't violate the
3834
3898
// channel constraints.
3835
3899
for _ , entry := range updates {
3836
- if entry .EntryType == Add {
3900
+ if entry .isAdd () {
3837
3901
// An HTLC is being added, this will add to the
3838
3902
// number and amount in flight.
3839
3903
amtInFlight += entry .Amount
@@ -4489,6 +4553,14 @@ func (lc *LightningChannel) ProcessChanSyncMsg(ctx context.Context,
4489
4553
// Next, we'll need to send over any updates we sent as part of
4490
4554
// this new proposed commitment state.
4491
4555
for _ , logUpdate := range commitDiff .LogUpdates {
4556
+ //nolint:ll
4557
+ if htlc , ok := logUpdate .UpdateMsg .(* lnwire.UpdateAddHTLC ); ok {
4558
+ delete (htlc .CustomRecords , uint64 (NoopHtlcType .TypeVal ()))
4559
+
4560
+ if len (htlc .CustomRecords ) == 0 {
4561
+ htlc .CustomRecords = nil
4562
+ }
4563
+ }
4492
4564
commitUpdates = append (
4493
4565
commitUpdates , logUpdate .UpdateMsg ,
4494
4566
)
@@ -5712,7 +5784,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
5712
5784
// don't re-forward any already processed HTLC's after a
5713
5785
// restart.
5714
5786
switch {
5715
- case pd .EntryType == Add && committedAdd && shouldFwdAdd :
5787
+ case pd .isAdd () && committedAdd && shouldFwdAdd :
5716
5788
// Construct a reference specifying the location that
5717
5789
// this forwarded Add will be written in the forwarding
5718
5790
// package constructed at this remote height.
@@ -5731,7 +5803,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
5731
5803
addUpdatesToForward , pd .toLogUpdate (),
5732
5804
)
5733
5805
5734
- case pd .EntryType != Add && committedRmv && shouldFwdRmv :
5806
+ case ! pd .isAdd () && committedRmv && shouldFwdRmv :
5735
5807
// Construct a reference specifying the location that
5736
5808
// this forwarded Settle/Fail will be written in the
5737
5809
// forwarding package constructed at this remote height.
@@ -5970,7 +6042,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
5970
6042
// Grab all of our HTLCs and evaluate against the dust limit.
5971
6043
for e := lc .updateLogs .Local .Front (); e != nil ; e = e .Next () {
5972
6044
pd := e .Value
5973
- if pd .EntryType != Add {
6045
+ if ! pd .isAdd () {
5974
6046
continue
5975
6047
}
5976
6048
@@ -5989,7 +6061,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
5989
6061
// Grab all of their HTLCs and evaluate against the dust limit.
5990
6062
for e := lc .updateLogs .Remote .Front (); e != nil ; e = e .Next () {
5991
6063
pd := e .Value
5992
- if pd .EntryType != Add {
6064
+ if ! pd .isAdd () {
5993
6065
continue
5994
6066
}
5995
6067
@@ -6062,9 +6134,15 @@ func (lc *LightningChannel) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
6062
6134
func (lc * LightningChannel ) htlcAddDescriptor (htlc * lnwire.UpdateAddHTLC ,
6063
6135
openKey * models.CircuitKey ) * paymentDescriptor {
6064
6136
6137
+ entryType := Add
6138
+ customRecords := htlc .CustomRecords .Copy ()
6139
+ if shouldSetNoop (customRecords , lc .channelState .ChanType ) {
6140
+ entryType = NoopAdd
6141
+ }
6142
+
6065
6143
return & paymentDescriptor {
6066
6144
ChanID : htlc .ChanID ,
6067
- EntryType : Add ,
6145
+ EntryType : entryType ,
6068
6146
RHash : PaymentHash (htlc .PaymentHash ),
6069
6147
Timeout : htlc .Expiry ,
6070
6148
Amount : htlc .Amount ,
@@ -6073,7 +6151,7 @@ func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC,
6073
6151
OnionBlob : htlc .OnionBlob ,
6074
6152
OpenCircuitKey : openKey ,
6075
6153
BlindingPoint : htlc .BlindingPoint ,
6076
- CustomRecords : htlc . CustomRecords . Copy () ,
6154
+ CustomRecords : customRecords ,
6077
6155
}
6078
6156
}
6079
6157
@@ -6126,17 +6204,23 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
6126
6204
lc .updateLogs .Remote .htlcCounter )
6127
6205
}
6128
6206
6207
+ entryType := Add
6208
+ customRecords := htlc .CustomRecords .Copy ()
6209
+ if shouldSetNoop (customRecords , lc .channelState .ChanType ) {
6210
+ entryType = NoopAdd
6211
+ }
6212
+
6129
6213
pd := & paymentDescriptor {
6130
6214
ChanID : htlc .ChanID ,
6131
- EntryType : Add ,
6215
+ EntryType : entryType ,
6132
6216
RHash : PaymentHash (htlc .PaymentHash ),
6133
6217
Timeout : htlc .Expiry ,
6134
6218
Amount : htlc .Amount ,
6135
6219
LogIndex : lc .updateLogs .Remote .logIndex ,
6136
6220
HtlcIndex : lc .updateLogs .Remote .htlcCounter ,
6137
6221
OnionBlob : htlc .OnionBlob ,
6138
6222
BlindingPoint : htlc .BlindingPoint ,
6139
- CustomRecords : htlc . CustomRecords . Copy () ,
6223
+ CustomRecords : customRecords ,
6140
6224
}
6141
6225
6142
6226
localACKedIndex := lc .commitChains .Remote .tail ().messageIndices .Local
@@ -9825,7 +9909,7 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex,
9825
9909
9826
9910
// We don't save add updates as they are restored from the
9827
9911
// remote commitment in restoreStateLogs.
9828
- if pd .EntryType == Add {
9912
+ if pd .isAdd () {
9829
9913
continue
9830
9914
}
9831
9915
@@ -9999,3 +10083,18 @@ func (lc *LightningChannel) ZeroConfRealScid() fn.Option[lnwire.ShortChannelID]
9999
10083
10000
10084
return fn .None [lnwire.ShortChannelID ]()
10001
10085
}
10086
+
10087
+ // shouldSetNoop checks the custom records of the entry and the channel type and
10088
+ // returns a boolean indicating whether this add entry should be converted to a
10089
+ // noop add type. This will only return true if the TLV field signalling the use
10090
+ // of a noop HTLC is set, and the channel has a custom tapscript root.
10091
+ func shouldSetNoop (records lnwire.CustomRecords ,
10092
+ chanType channeldb.ChannelType ) bool {
10093
+
10094
+ noopTLV := uint64 (NoopHtlcType .TypeVal ())
10095
+ if _ , ok := records [noopTLV ]; ok && chanType .HasTapscriptRoot () {
10096
+ return true
10097
+ }
10098
+
10099
+ return false
10100
+ }
0 commit comments