@@ -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
+ 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,70 @@ 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 balanceAboveReserve (party lntypes.ChannelParty , delta int64 ,
3179
+ channel * channeldb.OpenChannel ) bool {
3180
+
3181
+ channel .RLock ()
3182
+ defer channel .RUnlock ()
3183
+
3184
+ c := channel
3185
+
3186
+ localReserve := lnwire .NewMSatFromSatoshis (c .LocalChanCfg .ChanReserve )
3187
+ remoteReserve := lnwire .NewMSatFromSatoshis (c .RemoteChanCfg .ChanReserve )
3188
+
3189
+ switch {
3190
+ case party .IsLocal ():
3191
+ totalLocal := c .LocalCommitment .LocalBalance
3192
+ if delta >= 0 {
3193
+ totalLocal += lnwire .MilliSatoshi (delta )
3194
+ } else {
3195
+ totalLocal -= lnwire .MilliSatoshi (- 1 * delta )
3196
+ }
3197
+
3198
+ return totalLocal > localReserve
3199
+
3200
+ case party .IsRemote ():
3201
+ totalRemote := c .RemoteCommitment .RemoteBalance
3202
+ if delta >= 0 {
3203
+ totalRemote += lnwire .MilliSatoshi (delta )
3204
+ } else {
3205
+ totalRemote -= lnwire .MilliSatoshi (- 1 * delta )
3206
+ }
3207
+
3208
+ return totalRemote > remoteReserve
3209
+ }
3210
+
3211
+ return false
3212
+ }
3213
+
3214
+ // evaluateNoOpHtlc applies the balance delta based on whether the NoOp HTLC is
3215
+ // considered effective. This depends on whether the receiver is already above
3216
+ // the channel reserve.
3217
+ func (lc * LightningChannel ) evaluateNoOpHtlc (entry * paymentDescriptor ,
3218
+ party lntypes.ChannelParty , balanceDeltas * lntypes.Dual [int64 ]) {
3219
+
3220
+ channel := lc .channelState
3221
+ delta := balanceDeltas .GetForParty (party )
3222
+
3223
+ // If the receiver has existing balance above reserve then we go ahead
3224
+ // with crediting the amount back to the sender. Otherwise we give the
3225
+ // amount to the receiver. We do this because the receiver needs some
3226
+ // above reserve balance to anchor the AuxBlob. We also pass in the so
3227
+ // far calculated delta for the party, as that's effectively part of
3228
+ // their balance within this view computation.
3229
+ if balanceAboveReserve (party , delta , channel ) {
3230
+ party = party .CounterParty ()
3231
+ }
3232
+
3233
+ d := int64 (entry .Amount )
3234
+ balanceDeltas .ModifyForParty (party , func (acc int64 ) int64 {
3235
+ return acc + d
3236
+ })
3237
+ }
3238
+
3148
3239
// generateRemoteHtlcSigJobs generates a series of HTLC signature jobs for the
3149
3240
// sig pool, along with a channel that if closed, will cancel any jobs after
3150
3241
// they have been submitted to the sigPool. This method is to be used when
@@ -3833,7 +3924,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
3833
3924
// Go through all updates, checking that they don't violate the
3834
3925
// channel constraints.
3835
3926
for _ , entry := range updates {
3836
- if entry .EntryType == Add {
3927
+ if entry .isAdd () {
3837
3928
// An HTLC is being added, this will add to the
3838
3929
// number and amount in flight.
3839
3930
amtInFlight += entry .Amount
@@ -5712,7 +5803,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
5712
5803
// don't re-forward any already processed HTLC's after a
5713
5804
// restart.
5714
5805
switch {
5715
- case pd .EntryType == Add && committedAdd && shouldFwdAdd :
5806
+ case pd .isAdd () && committedAdd && shouldFwdAdd :
5716
5807
// Construct a reference specifying the location that
5717
5808
// this forwarded Add will be written in the forwarding
5718
5809
// package constructed at this remote height.
@@ -5731,7 +5822,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
5731
5822
addUpdatesToForward , pd .toLogUpdate (),
5732
5823
)
5733
5824
5734
- case pd .EntryType != Add && committedRmv && shouldFwdRmv :
5825
+ case ! pd .isAdd () && committedRmv && shouldFwdRmv :
5735
5826
// Construct a reference specifying the location that
5736
5827
// this forwarded Settle/Fail will be written in the
5737
5828
// forwarding package constructed at this remote height.
@@ -5970,7 +6061,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
5970
6061
// Grab all of our HTLCs and evaluate against the dust limit.
5971
6062
for e := lc .updateLogs .Local .Front (); e != nil ; e = e .Next () {
5972
6063
pd := e .Value
5973
- if pd .EntryType != Add {
6064
+ if ! pd .isAdd () {
5974
6065
continue
5975
6066
}
5976
6067
@@ -5989,7 +6080,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
5989
6080
// Grab all of their HTLCs and evaluate against the dust limit.
5990
6081
for e := lc .updateLogs .Remote .Front (); e != nil ; e = e .Next () {
5991
6082
pd := e .Value
5992
- if pd .EntryType != Add {
6083
+ if ! pd .isAdd () {
5993
6084
continue
5994
6085
}
5995
6086
@@ -6062,9 +6153,12 @@ func (lc *LightningChannel) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
6062
6153
func (lc * LightningChannel ) htlcAddDescriptor (htlc * lnwire.UpdateAddHTLC ,
6063
6154
openKey * models.CircuitKey ) * paymentDescriptor {
6064
6155
6156
+ customRecords := htlc .CustomRecords .Copy ()
6157
+ entryType := entryTypeForHtlc (customRecords , lc .channelState .ChanType )
6158
+
6065
6159
return & paymentDescriptor {
6066
6160
ChanID : htlc .ChanID ,
6067
- EntryType : Add ,
6161
+ EntryType : entryType ,
6068
6162
RHash : PaymentHash (htlc .PaymentHash ),
6069
6163
Timeout : htlc .Expiry ,
6070
6164
Amount : htlc .Amount ,
@@ -6073,7 +6167,7 @@ func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC,
6073
6167
OnionBlob : htlc .OnionBlob ,
6074
6168
OpenCircuitKey : openKey ,
6075
6169
BlindingPoint : htlc .BlindingPoint ,
6076
- CustomRecords : htlc . CustomRecords . Copy () ,
6170
+ CustomRecords : customRecords ,
6077
6171
}
6078
6172
}
6079
6173
@@ -6126,17 +6220,20 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
6126
6220
lc .updateLogs .Remote .htlcCounter )
6127
6221
}
6128
6222
6223
+ customRecords := htlc .CustomRecords .Copy ()
6224
+ entryType := entryTypeForHtlc (customRecords , lc .channelState .ChanType )
6225
+
6129
6226
pd := & paymentDescriptor {
6130
6227
ChanID : htlc .ChanID ,
6131
- EntryType : Add ,
6228
+ EntryType : entryType ,
6132
6229
RHash : PaymentHash (htlc .PaymentHash ),
6133
6230
Timeout : htlc .Expiry ,
6134
6231
Amount : htlc .Amount ,
6135
6232
LogIndex : lc .updateLogs .Remote .logIndex ,
6136
6233
HtlcIndex : lc .updateLogs .Remote .htlcCounter ,
6137
6234
OnionBlob : htlc .OnionBlob ,
6138
6235
BlindingPoint : htlc .BlindingPoint ,
6139
- CustomRecords : htlc . CustomRecords . Copy () ,
6236
+ CustomRecords : customRecords ,
6140
6237
}
6141
6238
6142
6239
localACKedIndex := lc .commitChains .Remote .tail ().messageIndices .Local
@@ -9825,7 +9922,7 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex,
9825
9922
9826
9923
// We don't save add updates as they are restored from the
9827
9924
// remote commitment in restoreStateLogs.
9828
- if pd .EntryType == Add {
9925
+ if pd .isAdd () {
9829
9926
continue
9830
9927
}
9831
9928
@@ -9999,3 +10096,17 @@ func (lc *LightningChannel) ZeroConfRealScid() fn.Option[lnwire.ShortChannelID]
9999
10096
10000
10097
return fn .None [lnwire.ShortChannelID ]()
10001
10098
}
10099
+
10100
+ // entryTypeForHtlc returns the add type that should be used for adding this
10101
+ // HTLC to the channel. If the channel has a tapscript root and the HTLC carries
10102
+ // the NoOp bit in the custom records then we'll convert this to a NoOp add.
10103
+ func entryTypeForHtlc (records lnwire.CustomRecords ,
10104
+ chanType channeldb.ChannelType ) updateType {
10105
+
10106
+ noopTLV := uint64 (NoOpHtlcType .TypeVal ())
10107
+ if _ , ok := records [noopTLV ]; ok && chanType .HasTapscriptRoot () {
10108
+ return NoOpAdd
10109
+ }
10110
+
10111
+ return Add
10112
+ }
0 commit comments