-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lnwallet: add new NoopAdd payDesc entry type #9430
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -552,6 +552,20 @@ | |
remoteOutputIndex = htlc.OutputIndex | ||
} | ||
|
||
customRecords := htlc.CustomRecords.Copy() | ||
|
||
entryType := Add | ||
|
||
noopTLV := uint64(noopHtlcType.TypeVal()) | ||
if _, ok := customRecords[noopTLV]; ok { | ||
entryType = NoopAdd | ||
} | ||
|
||
// The NoopAdd HTLC is an internal construct, and isn't meant to show up | ||
// on the wire. So we'll remove the special element from the set of | ||
// custom records. | ||
delete(customRecords, noopTLV) | ||
|
||
// With the scripts reconstructed (depending on if this is our commit | ||
// vs theirs or a pending commit for the remote party), we can now | ||
// re-create the original payment descriptor. | ||
|
@@ -560,7 +574,7 @@ | |
RHash: htlc.RHash, | ||
Timeout: htlc.RefundTimeout, | ||
Amount: htlc.Amt, | ||
EntryType: Add, | ||
EntryType: entryType, | ||
HtlcIndex: htlc.HtlcIndex, | ||
LogIndex: htlc.LogIndex, | ||
OnionBlob: htlc.OnionBlob, | ||
|
@@ -571,7 +585,7 @@ | |
theirPkScript: theirP2WSH, | ||
theirWitnessScript: theirWitnessScript, | ||
BlindingPoint: htlc.BlindingPoint, | ||
CustomRecords: htlc.CustomRecords.Copy(), | ||
CustomRecords: customRecords, | ||
}, nil | ||
} | ||
|
||
|
@@ -1101,6 +1115,11 @@ | |
}, | ||
} | ||
|
||
noopTLV := uint64(noopHtlcType.TypeVal()) | ||
if _, ok := pd.CustomRecords[noopTLV]; ok { | ||
pd.EntryType = NoopAdd | ||
} | ||
|
||
isDustRemote := HtlcIsDust( | ||
lc.channelState.ChanType, false, lntypes.Remote, | ||
feeRate, wireMsg.Amount.ToSatoshis(), remoteDustLimit, | ||
|
@@ -1336,6 +1355,11 @@ | |
Local: commitHeight, | ||
}, | ||
} | ||
noopTLV := uint64(noopHtlcType.TypeVal()) | ||
|
||
if _, ok := pd.CustomRecords[noopTLV]; ok { | ||
pd.EntryType = NoopAdd | ||
} | ||
|
||
// We don't need to generate an htlc script yet. This will be | ||
// done once we sign our remote commitment. | ||
|
@@ -1737,7 +1761,7 @@ | |
// but this Add restoration was a no-op as every single one of | ||
// these Adds was already restored since they're all incoming | ||
// htlcs on the local commitment. | ||
if payDesc.EntryType == Add { | ||
if payDesc.isAdd() { | ||
continue | ||
} | ||
|
||
|
@@ -1882,7 +1906,7 @@ | |
} | ||
|
||
switch payDesc.EntryType { | ||
case Add: | ||
case Add, NoopAdd: | ||
// The HtlcIndex of the added HTLC _must_ be equal to | ||
// the log's htlcCounter at this point. If it is not we | ||
// panic to catch this. | ||
|
@@ -2974,6 +2998,20 @@ | |
) | ||
if rmvHeight == 0 { | ||
switch { | ||
// If this a noop add, then when we settle the | ||
// HTLC, we actually credit the sender with the | ||
// amount again, thus making it a noop. | ||
case entry.EntryType == Settle && | ||
addEntry.EntryType == NoopAdd: | ||
|
||
delta := int64(entry.Amount) | ||
balanceDeltas.ModifyForParty( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also important to mention that parties could disagree here, with receiver potentially trying out malicious things (like going to chain, like @ZZiigguurraatt mentioned), and try to "grab" both the custom data and the btc amount That's a good reason why I believe we should not convert the entry type to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I follow, but if they disagree, a force close occurs. Which doesn't benefit either party, as they need to pay for chain fees, and now the receiver will likely end up with nothing as it'll cost more to spend the output than it's actually worth. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean that we should not allow significant btc amounts being carried by HTLCs of type That breaks the incentive alignment and someone could actually want to misbehave and force-close/pay fees |
||
party.CounterParty(), | ||
func(acc int64) int64 { | ||
return acc + delta | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 So by default the custom aux blob is being updated / preserved in the background, so the only diff required is the one that pays back the sats, didn't expect this to be that lean 💯 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, was great how small it turned out to be in the end. This area of the code has been refactored a bit also, which served to simplify the addition of this new feature. |
||
}, | ||
) | ||
|
||
// If an incoming HTLC is being settled, then | ||
// this means that the preimage has been | ||
// received by the settling party Therefore, we | ||
|
@@ -3011,7 +3049,7 @@ | |
liveAdds := fn.Filter( | ||
view.Updates.GetForParty(party), | ||
func(pd *paymentDescriptor) bool { | ||
isAdd := pd.EntryType == Add | ||
isAdd := pd.isAdd() | ||
shouldSkip := skip.GetForParty(party). | ||
Contains(pd.HtlcIndex) | ||
|
||
|
@@ -3050,7 +3088,7 @@ | |
// corresponding to whoseCommitmentChain. | ||
isUncommitted := func(update *paymentDescriptor) bool { | ||
switch update.EntryType { | ||
case Add: | ||
case Add, NoopAdd: | ||
return update.addCommitHeights.GetForParty( | ||
whoseCommitChain, | ||
) == 0 | ||
|
@@ -3814,7 +3852,7 @@ | |
// Go through all updates, checking that they don't violate the | ||
// channel constraints. | ||
for _, entry := range updates { | ||
if entry.EntryType == Add { | ||
if entry.isAdd() { | ||
// An HTLC is being added, this will add to the | ||
// number and amount in flight. | ||
amtInFlight += entry.Amount | ||
|
@@ -4470,6 +4508,13 @@ | |
// Next, we'll need to send over any updates we sent as part of | ||
// this new proposed commitment state. | ||
for _, logUpdate := range commitDiff.LogUpdates { | ||
if htlc, ok := logUpdate.UpdateMsg.(*lnwire.UpdateAddHTLC); ok { | ||
delete(htlc.CustomRecords, uint64(noopHtlcType.TypeVal())) | ||
|
||
if len(htlc.CustomRecords) == 0 { | ||
htlc.CustomRecords = nil | ||
} | ||
} | ||
commitUpdates = append( | ||
commitUpdates, logUpdate.UpdateMsg, | ||
) | ||
|
@@ -4501,8 +4546,8 @@ | |
// updates comes after the LogUpdates+CommitSig. | ||
// | ||
// ---logupdates---> | ||
// ---commitsig----> | ||
// ---revocation---> | ||
// ---commitsig----> | ||
updates = append(commitUpdates, updates...) | ||
} else { | ||
// Otherwise, the revocation should come before LogUpdates | ||
|
@@ -5693,7 +5738,7 @@ | |
// don't re-forward any already processed HTLC's after a | ||
// restart. | ||
switch { | ||
case pd.EntryType == Add && committedAdd && shouldFwdAdd: | ||
case pd.isAdd() && committedAdd && shouldFwdAdd: | ||
// Construct a reference specifying the location that | ||
// this forwarded Add will be written in the forwarding | ||
// package constructed at this remote height. | ||
|
@@ -5712,7 +5757,7 @@ | |
addUpdatesToForward, pd.toLogUpdate(), | ||
) | ||
|
||
case pd.EntryType != Add && committedRmv && shouldFwdRmv: | ||
case !pd.isAdd() && committedRmv && shouldFwdRmv: | ||
// Construct a reference specifying the location that | ||
// this forwarded Settle/Fail will be written in the | ||
// forwarding package constructed at this remote height. | ||
|
@@ -5951,7 +5996,7 @@ | |
// Grab all of our HTLCs and evaluate against the dust limit. | ||
for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() { | ||
pd := e.Value | ||
if pd.EntryType != Add { | ||
if !pd.isAdd() { | ||
continue | ||
} | ||
|
||
|
@@ -5970,7 +6015,7 @@ | |
// Grab all of their HTLCs and evaluate against the dust limit. | ||
for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() { | ||
pd := e.Value | ||
if pd.EntryType != Add { | ||
if !pd.isAdd() { | ||
continue | ||
} | ||
|
||
|
@@ -6043,9 +6088,25 @@ | |
func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC, | ||
openKey *models.CircuitKey) *paymentDescriptor { | ||
|
||
// TODO(roasbeef): can use push amt to simplify logic, not have to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wdym in this todo: always push some sats amount on channel open, to make sure remote side can always anchor? if that's the case There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Would need to double check, but I think the starting value of a
Yes, so we'd have the APIs set up to always push an amount over to ensure they can anchor for as starting state. Otherwise, we'll need to make sure that we only ever push for the "first" HTLC, which can be a bit tricky if you have many pending HTLCs in a new commitment transaction. |
||
// detect if remote party has enough funds for anchor | ||
entryType := Add | ||
|
||
customRecords := htlc.CustomRecords.Copy() | ||
if lc.channelState.ChanType.HasTapscriptRoot() { | ||
entryType = NoopAdd | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a nice way to bootstrap things, but I think We could be sending raw btc HTLCs over the channel, or we could be sending HTLCs carrying custom data and also carry a significant amount of btc. In both of the previous cases we definitely don't want to give the amount back to sender upon settlement From the top of my head: we could also check for this HTLC's custom records, plus the actual amount of the btc delta and then decide whether we're in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. I think instead we may want to add a new aux interface, or re-use an existing one, to know when we should be using this or not. |
||
|
||
// We'll add an internal custom record here to make sure that | ||
// across restarts, we recognize this as a noop HTLC. | ||
if customRecords == nil { | ||
customRecords = make(lnwire.CustomRecords) | ||
} | ||
customRecords[uint64(noopHtlcType.TypeVal())] = nil | ||
} | ||
|
||
return &paymentDescriptor{ | ||
ChanID: htlc.ChanID, | ||
EntryType: Add, | ||
EntryType: entryType, | ||
RHash: PaymentHash(htlc.PaymentHash), | ||
Timeout: htlc.Expiry, | ||
Amount: htlc.Amount, | ||
|
@@ -6054,7 +6115,7 @@ | |
OnionBlob: htlc.OnionBlob, | ||
OpenCircuitKey: openKey, | ||
BlindingPoint: htlc.BlindingPoint, | ||
CustomRecords: htlc.CustomRecords.Copy(), | ||
CustomRecords: customRecords, | ||
} | ||
} | ||
|
||
|
@@ -6107,17 +6168,32 @@ | |
lc.updateLogs.Remote.htlcCounter) | ||
} | ||
|
||
entryType := Add | ||
|
||
customRecords := htlc.CustomRecords.Copy() | ||
if lc.channelState.ChanType.HasTapscriptRoot() { | ||
if customRecords == nil { | ||
customRecords = make(lnwire.CustomRecords) | ||
} | ||
|
||
// We'll add an internal custom record here to make sure that | ||
// across restarts, we recognize this as a noop HTLC. | ||
customRecords[uint64(noopHtlcType.TypeVal())] = nil | ||
|
||
entryType = NoopAdd | ||
} | ||
|
||
pd := &paymentDescriptor{ | ||
ChanID: htlc.ChanID, | ||
EntryType: Add, | ||
EntryType: entryType, | ||
RHash: PaymentHash(htlc.PaymentHash), | ||
Timeout: htlc.Expiry, | ||
Amount: htlc.Amount, | ||
LogIndex: lc.updateLogs.Remote.logIndex, | ||
HtlcIndex: lc.updateLogs.Remote.htlcCounter, | ||
OnionBlob: htlc.OnionBlob, | ||
BlindingPoint: htlc.BlindingPoint, | ||
CustomRecords: htlc.CustomRecords.Copy(), | ||
CustomRecords: customRecords, | ||
} | ||
|
||
localACKedIndex := lc.commitChains.Remote.tail().messageIndices.Local | ||
|
@@ -9765,7 +9841,7 @@ | |
|
||
// We don't save add updates as they are restored from the | ||
// remote commitment in restoreStateLogs. | ||
if pd.EntryType == Add { | ||
if pd.isAdd() { | ||
continue | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC this wouldn't really matter, as the receiver of the wire message would just ignore this unknown type, are we just saving wire space here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ended up doing this as we have some tests that assert what an HTLC should look like for transmission. If we continue to thread these over, then those start to fail.
On the other hand, maybe it makes sense to expose this, as then the receiver just accepts the noop if the sender does, vs the current logic of assuming that it's always noop when the tapscript bit is set.