@@ -551,6 +551,12 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
551
551
remoteOutputIndex = htlc .OutputIndex
552
552
}
553
553
554
+ customRecords := htlc .CustomRecords .Copy ()
555
+
556
+ entryType := entryTypeForHtlc (
557
+ htlc .CustomRecords , lc .channelState .ChanType ,
558
+ )
559
+
554
560
// With the scripts reconstructed (depending on if this is our commit
555
561
// vs theirs or a pending commit for the remote party), we can now
556
562
// re-create the original payment descriptor.
@@ -559,7 +565,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
559
565
RHash : htlc .RHash ,
560
566
Timeout : htlc .RefundTimeout ,
561
567
Amount : htlc .Amt ,
562
- EntryType : Add ,
568
+ EntryType : entryType ,
563
569
HtlcIndex : htlc .HtlcIndex ,
564
570
LogIndex : htlc .LogIndex ,
565
571
OnionBlob : htlc .OnionBlob ,
@@ -570,7 +576,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
570
576
theirPkScript : theirP2WSH ,
571
577
theirWitnessScript : theirWitnessScript ,
572
578
BlindingPoint : htlc .BlindingPoint ,
573
- CustomRecords : htlc . CustomRecords . Copy () ,
579
+ CustomRecords : customRecords ,
574
580
}, nil
575
581
}
576
582
@@ -1100,6 +1106,10 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
1100
1106
},
1101
1107
}
1102
1108
1109
+ pd .EntryType = entryTypeForHtlc (
1110
+ pd .CustomRecords , lc .channelState .ChanType ,
1111
+ )
1112
+
1103
1113
isDustRemote := HtlcIsDust (
1104
1114
lc .channelState .ChanType , false , lntypes .Remote ,
1105
1115
feeRate , wireMsg .Amount .ToSatoshis (), remoteDustLimit ,
@@ -1336,6 +1346,10 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd
1336
1346
},
1337
1347
}
1338
1348
1349
+ pd .EntryType = entryTypeForHtlc (
1350
+ pd .CustomRecords , lc .channelState .ChanType ,
1351
+ )
1352
+
1339
1353
// We don't need to generate an htlc script yet. This will be
1340
1354
// done once we sign our remote commitment.
1341
1355
@@ -1736,7 +1750,7 @@ func (lc *LightningChannel) restorePendingRemoteUpdates(
1736
1750
// but this Add restoration was a no-op as every single one of
1737
1751
// these Adds was already restored since they're all incoming
1738
1752
// htlcs on the local commitment.
1739
- if payDesc .EntryType == Add {
1753
+ if payDesc .isAdd () {
1740
1754
continue
1741
1755
}
1742
1756
@@ -1881,7 +1895,7 @@ func (lc *LightningChannel) restorePendingLocalUpdates(
1881
1895
}
1882
1896
1883
1897
switch payDesc .EntryType {
1884
- case Add :
1898
+ case Add , NoOpAdd :
1885
1899
// The HtlcIndex of the added HTLC _must_ be equal to
1886
1900
// the log's htlcCounter at this point. If it is not we
1887
1901
// panic to catch this.
@@ -2993,6 +3007,19 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
2993
3007
)
2994
3008
if rmvHeight == 0 {
2995
3009
switch {
3010
+ // If this a noop add, then when we settle the
3011
+ // HTLC, we actually credit the sender with the
3012
+ // amount again, thus making it a noop. Noop
3013
+ // HTLCs are only triggered by external software
3014
+ // using the AuxComponents and only for channels
3015
+ // that use the custom tapscript root.
3016
+ case entry .EntryType == Settle &&
3017
+ addEntry .EntryType == NoOpAdd :
3018
+
3019
+ lc .evaluateNoOpHtlc (
3020
+ entry , party , & balanceDeltas ,
3021
+ )
3022
+
2996
3023
// If an incoming HTLC is being settled, then
2997
3024
// this means that the preimage has been
2998
3025
// received by the settling party Therefore, we
@@ -3030,7 +3057,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
3030
3057
liveAdds := fn .Filter (
3031
3058
view .Updates .GetForParty (party ),
3032
3059
func (pd * paymentDescriptor ) bool {
3033
- isAdd := pd .EntryType == Add
3060
+ isAdd := pd .isAdd ()
3034
3061
shouldSkip := skip .GetForParty (party ).
3035
3062
Contains (pd .HtlcIndex )
3036
3063
@@ -3069,7 +3096,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
3069
3096
// corresponding to whoseCommitmentChain.
3070
3097
isUncommitted := func (update * paymentDescriptor ) bool {
3071
3098
switch update .EntryType {
3072
- case Add :
3099
+ case Add , NoOpAdd :
3073
3100
return update .addCommitHeights .GetForParty (
3074
3101
whoseCommitChain ,
3075
3102
) == 0
@@ -3145,6 +3172,55 @@ func (lc *LightningChannel) fetchParent(entry *paymentDescriptor,
3145
3172
return addEntry , nil
3146
3173
}
3147
3174
3175
+ // balanceAboveReserve checks if the balance for the provided party is above the
3176
+ // configured reserve. It also uses the balance delta for the party, to account
3177
+ // for entry amounts that have been processed already.
3178
+ func (lc * LightningChannel ) balanceAboveReserve (party lntypes.ChannelParty ,
3179
+ delta btcutil.Amount , channel * channeldb.OpenChannel ) bool {
3180
+
3181
+ channel .RLock ()
3182
+ defer channel .RUnlock ()
3183
+
3184
+ c := channel
3185
+
3186
+ switch {
3187
+ case party .IsLocal ():
3188
+ return c .LocalCommitment .LocalBalance .ToSatoshis ()+ delta >
3189
+ c .LocalChanCfg .ChanReserve
3190
+
3191
+ case party .IsRemote ():
3192
+ return c .RemoteCommitment .RemoteBalance .ToSatoshis ()+ delta >
3193
+ c .RemoteChanCfg .ChanReserve
3194
+ }
3195
+
3196
+ return false
3197
+ }
3198
+
3199
+ // evaluateNoOpHtlc applies the balance delta based on whether the NoOp HTLC is
3200
+ // considered effective. This depends on whether the receiver is already above
3201
+ // the channel reserve.
3202
+ func (lc * LightningChannel ) evaluateNoOpHtlc (entry * paymentDescriptor ,
3203
+ party lntypes.ChannelParty , balanceDeltas * lntypes.Dual [int64 ]) {
3204
+
3205
+ channel := lc .channelState
3206
+ delta := btcutil .Amount (balanceDeltas .GetForParty (party ))
3207
+
3208
+ // If the receiver has existing balance above dust then we go ahead with
3209
+ // crediting the amount back to the sender. Otherwise we give the amount
3210
+ // to the receiver. We do this because the receiver needs some above
3211
+ // dust balance to anchor the AuxBlob. We also pass in the so-far
3212
+ // calculated delta for the party, as that's effectively part of their
3213
+ // balance within this view computation.
3214
+ if lc .balanceAboveReserve (party , delta , channel ) {
3215
+ party = party .CounterParty ()
3216
+ }
3217
+
3218
+ d := int64 (entry .Amount )
3219
+ balanceDeltas .ModifyForParty (party , func (acc int64 ) int64 {
3220
+ return acc + d
3221
+ })
3222
+ }
3223
+
3148
3224
// generateRemoteHtlcSigJobs generates a series of HTLC signature jobs for the
3149
3225
// sig pool, along with a channel that if closed, will cancel any jobs after
3150
3226
// they have been submitted to the sigPool. This method is to be used when
@@ -3833,7 +3909,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
3833
3909
// Go through all updates, checking that they don't violate the
3834
3910
// channel constraints.
3835
3911
for _ , entry := range updates {
3836
- if entry .EntryType == Add {
3912
+ if entry .isAdd () {
3837
3913
// An HTLC is being added, this will add to the
3838
3914
// number and amount in flight.
3839
3915
amtInFlight += entry .Amount
@@ -5712,7 +5788,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
5712
5788
// don't re-forward any already processed HTLC's after a
5713
5789
// restart.
5714
5790
switch {
5715
- case pd .EntryType == Add && committedAdd && shouldFwdAdd :
5791
+ case pd .isAdd () && committedAdd && shouldFwdAdd :
5716
5792
// Construct a reference specifying the location that
5717
5793
// this forwarded Add will be written in the forwarding
5718
5794
// package constructed at this remote height.
@@ -5731,7 +5807,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
5731
5807
addUpdatesToForward , pd .toLogUpdate (),
5732
5808
)
5733
5809
5734
- case pd .EntryType != Add && committedRmv && shouldFwdRmv :
5810
+ case ! pd .isAdd () && committedRmv && shouldFwdRmv :
5735
5811
// Construct a reference specifying the location that
5736
5812
// this forwarded Settle/Fail will be written in the
5737
5813
// forwarding package constructed at this remote height.
@@ -5970,7 +6046,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
5970
6046
// Grab all of our HTLCs and evaluate against the dust limit.
5971
6047
for e := lc .updateLogs .Local .Front (); e != nil ; e = e .Next () {
5972
6048
pd := e .Value
5973
- if pd .EntryType != Add {
6049
+ if ! pd .isAdd () {
5974
6050
continue
5975
6051
}
5976
6052
@@ -5989,7 +6065,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
5989
6065
// Grab all of their HTLCs and evaluate against the dust limit.
5990
6066
for e := lc .updateLogs .Remote .Front (); e != nil ; e = e .Next () {
5991
6067
pd := e .Value
5992
- if pd .EntryType != Add {
6068
+ if ! pd .isAdd () {
5993
6069
continue
5994
6070
}
5995
6071
@@ -6062,9 +6138,12 @@ func (lc *LightningChannel) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
6062
6138
func (lc * LightningChannel ) htlcAddDescriptor (htlc * lnwire.UpdateAddHTLC ,
6063
6139
openKey * models.CircuitKey ) * paymentDescriptor {
6064
6140
6141
+ customRecords := htlc .CustomRecords .Copy ()
6142
+ entryType := entryTypeForHtlc (customRecords , lc .channelState .ChanType )
6143
+
6065
6144
return & paymentDescriptor {
6066
6145
ChanID : htlc .ChanID ,
6067
- EntryType : Add ,
6146
+ EntryType : entryType ,
6068
6147
RHash : PaymentHash (htlc .PaymentHash ),
6069
6148
Timeout : htlc .Expiry ,
6070
6149
Amount : htlc .Amount ,
@@ -6073,7 +6152,7 @@ func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC,
6073
6152
OnionBlob : htlc .OnionBlob ,
6074
6153
OpenCircuitKey : openKey ,
6075
6154
BlindingPoint : htlc .BlindingPoint ,
6076
- CustomRecords : htlc . CustomRecords . Copy () ,
6155
+ CustomRecords : customRecords ,
6077
6156
}
6078
6157
}
6079
6158
@@ -6126,17 +6205,20 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
6126
6205
lc .updateLogs .Remote .htlcCounter )
6127
6206
}
6128
6207
6208
+ customRecords := htlc .CustomRecords .Copy ()
6209
+ entryType := entryTypeForHtlc (customRecords , lc .channelState .ChanType )
6210
+
6129
6211
pd := & paymentDescriptor {
6130
6212
ChanID : htlc .ChanID ,
6131
- EntryType : Add ,
6213
+ EntryType : entryType ,
6132
6214
RHash : PaymentHash (htlc .PaymentHash ),
6133
6215
Timeout : htlc .Expiry ,
6134
6216
Amount : htlc .Amount ,
6135
6217
LogIndex : lc .updateLogs .Remote .logIndex ,
6136
6218
HtlcIndex : lc .updateLogs .Remote .htlcCounter ,
6137
6219
OnionBlob : htlc .OnionBlob ,
6138
6220
BlindingPoint : htlc .BlindingPoint ,
6139
- CustomRecords : htlc . CustomRecords . Copy () ,
6221
+ CustomRecords : customRecords ,
6140
6222
}
6141
6223
6142
6224
localACKedIndex := lc .commitChains .Remote .tail ().messageIndices .Local
@@ -9825,7 +9907,7 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex,
9825
9907
9826
9908
// We don't save add updates as they are restored from the
9827
9909
// remote commitment in restoreStateLogs.
9828
- if pd .EntryType == Add {
9910
+ if pd .isAdd () {
9829
9911
continue
9830
9912
}
9831
9913
@@ -9999,3 +10081,17 @@ func (lc *LightningChannel) ZeroConfRealScid() fn.Option[lnwire.ShortChannelID]
9999
10081
10000
10082
return fn .None [lnwire.ShortChannelID ]()
10001
10083
}
10084
+
10085
+ // entryTypeForHtlc returns the add type that should be used for adding this
10086
+ // HTLC to the channel. If the channel has a tapscript root and the HTLC carries
10087
+ // the NoOp bit in the custom records then we'll convert this to a NoOp add.
10088
+ func entryTypeForHtlc (records lnwire.CustomRecords ,
10089
+ chanType channeldb.ChannelType ) updateType {
10090
+
10091
+ noopTLV := uint64 (NoOpHtlcType .TypeVal ())
10092
+ if _ , ok := records [noopTLV ]; ok && chanType .HasTapscriptRoot () {
10093
+ return NoOpAdd
10094
+ }
10095
+
10096
+ return Add
10097
+ }
0 commit comments