Skip to content

Commit 349e409

Browse files
committed
lnwallet: detect and handle noop HTLCs
We update the lightning channel state machine in some key areas. If the noop TLV is set in the update_add_htlc custom records then we change the entry type to noop. When settling the HTLC if the type is noop we credit the satoshi amount back to the sender.
1 parent 51a6064 commit 349e409

File tree

2 files changed

+111
-19
lines changed

2 files changed

+111
-19
lines changed

lnwallet/aux_signer.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,20 @@ import (
1010
"github.com/lightningnetwork/lnd/tlv"
1111
)
1212

13-
// htlcCustomSigType is the TLV type that is used to encode the custom HTLC
14-
// signatures within the custom data for an existing HTLC.
15-
var htlcCustomSigType tlv.TlvType65543
13+
var (
14+
// htlcCustomSigType is the TLV type that is used to encode the custom
15+
// HTLC signatures within the custom data for an existing HTLC.
16+
htlcCustomSigType tlv.TlvType65543
17+
18+
// NoopHtlcType is the TLV that that's used in the update_add_htlc
19+
// message to indicate the presence of a noop HTLC. This has no encoded
20+
// value, but is used to indicate that the HTLC is a noop.
21+
NoopHtlcType tlv.TlvType65544
22+
)
23+
24+
// NoopAddHtlcType is the (golang) type of the TLV record that's used to signal
25+
// that an HTLC should be a noop HTLC.
26+
type NoopAddHtlcType = tlv.TlvType65544
1627

1728
// AuxHtlcView is a struct that contains a safe copy of an HTLC view that can
1829
// be used by aux components.

lnwallet/channel.go

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,20 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
551551
remoteOutputIndex = htlc.OutputIndex
552552
}
553553

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+
if _, ok := customRecords[noopTLV]; ok {
560+
entryType = NoopAdd
561+
}
562+
563+
// The NoopAdd HTLC is an internal construct, and isn't meant to show up
564+
// on the wire. So we'll remove the special element from the set of
565+
// custom records.
566+
delete(customRecords, noopTLV)
567+
554568
// With the scripts reconstructed (depending on if this is our commit
555569
// vs theirs or a pending commit for the remote party), we can now
556570
// re-create the original payment descriptor.
@@ -559,7 +573,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
559573
RHash: htlc.RHash,
560574
Timeout: htlc.RefundTimeout,
561575
Amount: htlc.Amt,
562-
EntryType: Add,
576+
EntryType: entryType,
563577
HtlcIndex: htlc.HtlcIndex,
564578
LogIndex: htlc.LogIndex,
565579
OnionBlob: htlc.OnionBlob,
@@ -570,7 +584,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
570584
theirPkScript: theirP2WSH,
571585
theirWitnessScript: theirWitnessScript,
572586
BlindingPoint: htlc.BlindingPoint,
573-
CustomRecords: htlc.CustomRecords.Copy(),
587+
CustomRecords: customRecords,
574588
}, nil
575589
}
576590

@@ -1100,6 +1114,11 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
11001114
},
11011115
}
11021116

1117+
noopTLV := uint64(NoopHtlcType.TypeVal())
1118+
if _, ok := pd.CustomRecords[noopTLV]; ok {
1119+
pd.EntryType = NoopAdd
1120+
}
1121+
11031122
isDustRemote := HtlcIsDust(
11041123
lc.channelState.ChanType, false, lntypes.Remote,
11051124
feeRate, wireMsg.Amount.ToSatoshis(), remoteDustLimit,
@@ -1336,6 +1355,11 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd
13361355
},
13371356
}
13381357

1358+
noopTLV := uint64(NoopHtlcType.TypeVal())
1359+
if _, ok := pd.CustomRecords[noopTLV]; ok {
1360+
pd.EntryType = NoopAdd
1361+
}
1362+
13391363
// We don't need to generate an htlc script yet. This will be
13401364
// done once we sign our remote commitment.
13411365

@@ -1736,7 +1760,7 @@ func (lc *LightningChannel) restorePendingRemoteUpdates(
17361760
// but this Add restoration was a no-op as every single one of
17371761
// these Adds was already restored since they're all incoming
17381762
// htlcs on the local commitment.
1739-
if payDesc.EntryType == Add {
1763+
if payDesc.isAdd() {
17401764
continue
17411765
}
17421766

@@ -1881,7 +1905,7 @@ func (lc *LightningChannel) restorePendingLocalUpdates(
18811905
}
18821906

18831907
switch payDesc.EntryType {
1884-
case Add:
1908+
case Add, NoopAdd:
18851909
// The HtlcIndex of the added HTLC _must_ be equal to
18861910
// the log's htlcCounter at this point. If it is not we
18871911
// panic to catch this.
@@ -2987,6 +3011,41 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
29873011
)
29883012
if rmvHeight == 0 {
29893013
switch {
3014+
// If this a noop add, then when we settle the
3015+
// HTLC, we actually credit the sender with the
3016+
// amount again, thus making it a noop.
3017+
case entry.EntryType == Settle &&
3018+
addEntry.EntryType == NoopAdd:
3019+
channel := lc.channelState
3020+
3021+
// If the receiver has existing balance
3022+
// above dust then we go ahead with
3023+
// crediting the amount back to the
3024+
// sender. Otherwise we give the amount
3025+
// to the receiver. We do this because
3026+
// the receiver needs some above-dust
3027+
// balance to anchor the AuxBlob.
3028+
if channel.BalanceAboveDustForParty(
3029+
party,
3030+
) {
3031+
3032+
d := int64(entry.Amount)
3033+
balanceDeltas.ModifyForParty(
3034+
party.CounterParty(),
3035+
func(acc int64) int64 {
3036+
return acc + d
3037+
},
3038+
)
3039+
} else {
3040+
d := int64(entry.Amount)
3041+
balanceDeltas.ModifyForParty(
3042+
party,
3043+
func(acc int64) int64 {
3044+
return acc + d
3045+
},
3046+
)
3047+
}
3048+
29903049
// If an incoming HTLC is being settled, then
29913050
// this means that the preimage has been
29923051
// received by the settling party Therefore, we
@@ -3024,7 +3083,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
30243083
liveAdds := fn.Filter(
30253084
view.Updates.GetForParty(party),
30263085
func(pd *paymentDescriptor) bool {
3027-
isAdd := pd.EntryType == Add
3086+
isAdd := pd.isAdd()
30283087
shouldSkip := skip.GetForParty(party).
30293088
Contains(pd.HtlcIndex)
30303089

@@ -3063,7 +3122,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
30633122
// corresponding to whoseCommitmentChain.
30643123
isUncommitted := func(update *paymentDescriptor) bool {
30653124
switch update.EntryType {
3066-
case Add:
3125+
case Add, NoopAdd:
30673126
return update.addCommitHeights.GetForParty(
30683127
whoseCommitChain,
30693128
) == 0
@@ -3827,7 +3886,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
38273886
// Go through all updates, checking that they don't violate the
38283887
// channel constraints.
38293888
for _, entry := range updates {
3830-
if entry.EntryType == Add {
3889+
if entry.isAdd() {
38313890
// An HTLC is being added, this will add to the
38323891
// number and amount in flight.
38333892
amtInFlight += entry.Amount
@@ -4483,6 +4542,14 @@ func (lc *LightningChannel) ProcessChanSyncMsg(ctx context.Context,
44834542
// Next, we'll need to send over any updates we sent as part of
44844543
// this new proposed commitment state.
44854544
for _, logUpdate := range commitDiff.LogUpdates {
4545+
//nolint:ll
4546+
if htlc, ok := logUpdate.UpdateMsg.(*lnwire.UpdateAddHTLC); ok {
4547+
delete(htlc.CustomRecords, uint64(NoopHtlcType.TypeVal()))
4548+
4549+
if len(htlc.CustomRecords) == 0 {
4550+
htlc.CustomRecords = nil
4551+
}
4552+
}
44864553
commitUpdates = append(
44874554
commitUpdates, logUpdate.UpdateMsg,
44884555
)
@@ -5706,7 +5773,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
57065773
// don't re-forward any already processed HTLC's after a
57075774
// restart.
57085775
switch {
5709-
case pd.EntryType == Add && committedAdd && shouldFwdAdd:
5776+
case pd.isAdd() && committedAdd && shouldFwdAdd:
57105777
// Construct a reference specifying the location that
57115778
// this forwarded Add will be written in the forwarding
57125779
// package constructed at this remote height.
@@ -5725,7 +5792,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
57255792
addUpdatesToForward, pd.toLogUpdate(),
57265793
)
57275794

5728-
case pd.EntryType != Add && committedRmv && shouldFwdRmv:
5795+
case !pd.isAdd() && committedRmv && shouldFwdRmv:
57295796
// Construct a reference specifying the location that
57305797
// this forwarded Settle/Fail will be written in the
57315798
// forwarding package constructed at this remote height.
@@ -5964,7 +6031,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
59646031
// Grab all of our HTLCs and evaluate against the dust limit.
59656032
for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() {
59666033
pd := e.Value
5967-
if pd.EntryType != Add {
6034+
if !pd.isAdd() {
59686035
continue
59696036
}
59706037

@@ -5983,7 +6050,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
59836050
// Grab all of their HTLCs and evaluate against the dust limit.
59846051
for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() {
59856052
pd := e.Value
5986-
if pd.EntryType != Add {
6053+
if !pd.isAdd() {
59876054
continue
59886055
}
59896056

@@ -6056,9 +6123,16 @@ func (lc *LightningChannel) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
60566123
func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC,
60576124
openKey *models.CircuitKey) *paymentDescriptor {
60586125

6126+
entryType := Add
6127+
customRecords := htlc.CustomRecords.Copy()
6128+
noopTLV := uint64(NoopHtlcType.TypeVal())
6129+
if _, ok := customRecords[noopTLV]; ok {
6130+
entryType = NoopAdd
6131+
}
6132+
60596133
return &paymentDescriptor{
60606134
ChanID: htlc.ChanID,
6061-
EntryType: Add,
6135+
EntryType: entryType,
60626136
RHash: PaymentHash(htlc.PaymentHash),
60636137
Timeout: htlc.Expiry,
60646138
Amount: htlc.Amount,
@@ -6067,7 +6141,7 @@ func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC,
60676141
OnionBlob: htlc.OnionBlob,
60686142
OpenCircuitKey: openKey,
60696143
BlindingPoint: htlc.BlindingPoint,
6070-
CustomRecords: htlc.CustomRecords.Copy(),
6144+
CustomRecords: customRecords,
60716145
}
60726146
}
60736147

@@ -6120,17 +6194,24 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
61206194
lc.updateLogs.Remote.htlcCounter)
61216195
}
61226196

6197+
entryType := Add
6198+
customRecords := htlc.CustomRecords.Copy()
6199+
noopTLV := uint64(NoopHtlcType.TypeVal())
6200+
if _, ok := customRecords[noopTLV]; ok {
6201+
entryType = NoopAdd
6202+
}
6203+
61236204
pd := &paymentDescriptor{
61246205
ChanID: htlc.ChanID,
6125-
EntryType: Add,
6206+
EntryType: entryType,
61266207
RHash: PaymentHash(htlc.PaymentHash),
61276208
Timeout: htlc.Expiry,
61286209
Amount: htlc.Amount,
61296210
LogIndex: lc.updateLogs.Remote.logIndex,
61306211
HtlcIndex: lc.updateLogs.Remote.htlcCounter,
61316212
OnionBlob: htlc.OnionBlob,
61326213
BlindingPoint: htlc.BlindingPoint,
6133-
CustomRecords: htlc.CustomRecords.Copy(),
6214+
CustomRecords: customRecords,
61346215
}
61356216

61366217
localACKedIndex := lc.commitChains.Remote.tail().messageIndices.Local
@@ -9819,7 +9900,7 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex,
98199900

98209901
// We don't save add updates as they are restored from the
98219902
// remote commitment in restoreStateLogs.
9822-
if pd.EntryType == Add {
9903+
if pd.isAdd() {
98239904
continue
98249905
}
98259906

0 commit comments

Comments
 (0)