Skip to content

Commit 4ff7d77

Browse files
authored
Merge pull request #9095 from lightningnetwork/extract-part4-from-staging-branch
[custom channels 4/5]: Extract PART4 from mega staging branch
2 parents cdad5d9 + e0b4601 commit 4ff7d77

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+7325
-3839
lines changed

config_builder.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/lightningnetwork/lnd/lnrpc"
4343
"github.com/lightningnetwork/lnd/lnwallet"
4444
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
45+
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
4546
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
4647
"github.com/lightningnetwork/lnd/macaroons"
4748
"github.com/lightningnetwork/lnd/msgmux"
@@ -182,6 +183,10 @@ type AuxComponents struct {
182183
// AuxDataParser is an optional data parser that can be used to parse
183184
// auxiliary data for certain custom channel types.
184185
AuxDataParser fn.Option[AuxDataParser]
186+
187+
// AuxChanCloser is an optional channel closer that can be used to
188+
// modify the way a coop-close transaction is constructed.
189+
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
185190
}
186191

187192
// DefaultWalletImpl is the default implementation of our normal, btcwallet

contractcourt/htlc_incoming_contest_resolver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ func (h *htlcIncomingContestResolver) Resolve(
308308

309309
resolution, err := h.Registry.NotifyExitHopHtlc(
310310
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
311-
circuitKey, hodlQueue.ChanIn(), payload,
311+
circuitKey, hodlQueue.ChanIn(), nil, payload,
312312
)
313313
if err != nil {
314314
return nil, err

contractcourt/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Registry interface {
3030
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
3131
expiry uint32, currentHeight int32,
3232
circuitKey models.CircuitKey, hodlChan chan<- interface{},
33+
wireCustomRecords lnwire.CustomRecords,
3334
payload invoices.Payload) (invoices.HtlcResolution, error)
3435

3536
// HodlUnsubscribeAll unsubscribes from all htlc resolutions.

contractcourt/mock_registry_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type mockRegistry struct {
2626
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
2727
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
2828
circuitKey models.CircuitKey, hodlChan chan<- interface{},
29+
wireCustomRecords lnwire.CustomRecords,
2930
payload invoices.Payload) (invoices.HtlcResolution, error) {
3031

3132
r.notifyChan <- notifyExitHopData{

htlcswitch/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type InvoiceDatabase interface {
3333
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
3434
expiry uint32, currentHeight int32,
3535
circuitKey models.CircuitKey, hodlChan chan<- interface{},
36+
wireCustomRecords lnwire.CustomRecords,
3637
payload invoices.Payload) (invoices.HtlcResolution, error)
3738

3839
// CancelInvoice attempts to cancel the invoice corresponding to the

htlcswitch/link.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3846,7 +3846,7 @@ func (l *channelLink) processExitHop(add lnwire.UpdateAddHTLC,
38463846

38473847
event, err := l.cfg.Registry.NotifyExitHopHtlc(
38483848
invoiceHash, add.Amount, add.Expiry, int32(heightNow),
3849-
circuitKey, l.hodlQueue.ChanIn(), payload,
3849+
circuitKey, l.hodlQueue.ChanIn(), add.CustomRecords, payload,
38503850
)
38513851
if err != nil {
38523852
return err

htlcswitch/mock.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,7 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry {
10141014
panic(err)
10151015
}
10161016

1017+
modifierMock := &invoices.MockHtlcModifier{}
10171018
registry := invoices.NewRegistry(
10181019
cdb,
10191020
invoices.NewInvoiceExpiryWatcher(
@@ -1022,6 +1023,7 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry {
10221023
),
10231024
&invoices.RegistryConfig{
10241025
FinalCltvRejectDelta: 5,
1026+
HtlcInterceptor: modifierMock,
10251027
},
10261028
)
10271029
registry.Start()
@@ -1047,11 +1049,12 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(
10471049
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
10481050
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
10491051
circuitKey models.CircuitKey, hodlChan chan<- interface{},
1052+
wireCustomRecords lnwire.CustomRecords,
10501053
payload invoices.Payload) (invoices.HtlcResolution, error) {
10511054

10521055
event, err := i.registry.NotifyExitHopHtlc(
1053-
rhash, amt, expiry, currentHeight, circuitKey, hodlChan,
1054-
payload,
1056+
rhash, amt, expiry, currentHeight, circuitKey,
1057+
hodlChan, wireCustomRecords, payload,
10551058
)
10561059
if err != nil {
10571060
return nil, err

invoices/interface.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,71 @@ type InvoiceUpdater interface {
207207
// Finalize finalizes the update before it is written to the database.
208208
Finalize(updateType UpdateType) error
209209
}
210+
211+
// HtlcModifyRequest is the request that is passed to the client via callback
212+
// during a HTLC interceptor session. The request contains the invoice that the
213+
// given HTLC is attempting to settle.
214+
type HtlcModifyRequest struct {
215+
// WireCustomRecords are the custom records that were parsed from the
216+
// HTLC wire message. These are the records of the current HTLC to be
217+
// accepted/settled. All previously accepted/settled HTLCs for the same
218+
// invoice are present in the Invoice field below.
219+
WireCustomRecords lnwire.CustomRecords
220+
221+
// ExitHtlcCircuitKey is the circuit key that identifies the HTLC which
222+
// is involved in the invoice settlement.
223+
ExitHtlcCircuitKey CircuitKey
224+
225+
// ExitHtlcAmt is the amount of the HTLC which is involved in the
226+
// invoice settlement.
227+
ExitHtlcAmt lnwire.MilliSatoshi
228+
229+
// ExitHtlcExpiry is the absolute expiry height of the HTLC which is
230+
// involved in the invoice settlement.
231+
ExitHtlcExpiry uint32
232+
233+
// CurrentHeight is the current block height.
234+
CurrentHeight uint32
235+
236+
// Invoice is the invoice that is being intercepted. The HTLCs within
237+
// the invoice are only those previously accepted/settled for the same
238+
// invoice.
239+
Invoice Invoice
240+
}
241+
242+
// HtlcModifyResponse is the response that the client should send back to the
243+
// interceptor after processing the HTLC modify request.
244+
type HtlcModifyResponse struct {
245+
// AmountPaid is the amount that the client has decided the HTLC is
246+
// actually worth. This might be different from the amount that the
247+
// HTLC was originally sent with, in case additional value is carried
248+
// along with it (which might be the case in custom channels).
249+
AmountPaid lnwire.MilliSatoshi
250+
}
251+
252+
// HtlcModifyCallback is a function that is called when an invoice is
253+
// intercepted by the invoice interceptor.
254+
type HtlcModifyCallback func(HtlcModifyRequest) (*HtlcModifyResponse, error)
255+
256+
// HtlcModifier is an interface that allows an intercept client to register
257+
// itself as a modifier of HTLCs that are settling an invoice. The client can
258+
// then modify the HTLCs based on the invoice and the HTLC that is settling it.
259+
type HtlcModifier interface {
260+
// RegisterInterceptor sets the client callback function that will be
261+
// called when an invoice is intercepted. If a callback is already set,
262+
// an error is returned. The returned function must be used to reset the
263+
// callback to nil once the client is done or disconnects. The read-only
264+
// channel closes when the server stops.
265+
RegisterInterceptor(HtlcModifyCallback) (func(), <-chan struct{}, error)
266+
}
267+
268+
// HtlcInterceptor is an interface that allows the invoice registry to let
269+
// clients intercept invoices before they are settled.
270+
type HtlcInterceptor interface {
271+
// Intercept generates a new intercept session for the given invoice.
272+
// The call blocks until the client has responded to the request or an
273+
// error occurs. The response callback is only called if a session was
274+
// created in the first place, which is only the case if a client is
275+
// registered.
276+
Intercept(HtlcModifyRequest, func(HtlcModifyResponse)) error
277+
}

invoices/invoiceregistry.go

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ type RegistryConfig struct {
7474
// KeysendHoldTime indicates for how long we want to accept and hold
7575
// spontaneous keysend payments.
7676
KeysendHoldTime time.Duration
77+
78+
// HtlcInterceptor is an interface that allows the invoice registry to
79+
// let clients intercept invoices before they are settled.
80+
HtlcInterceptor HtlcInterceptor
7781
}
7882

7983
// htlcReleaseEvent describes an htlc auto-release event. It is used to release
@@ -914,6 +918,7 @@ func (i *InvoiceRegistry) processAMP(ctx invoiceUpdateCtx) error {
914918
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
915919
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
916920
circuitKey CircuitKey, hodlChan chan<- interface{},
921+
wireCustomRecords lnwire.CustomRecords,
917922
payload Payload) (HtlcResolution, error) {
918923

919924
// Create the update context containing the relevant details of the
@@ -925,6 +930,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
925930
expiry: expiry,
926931
currentHeight: currentHeight,
927932
finalCltvRejectDelta: i.cfg.FinalCltvRejectDelta,
933+
wireCustomRecords: wireCustomRecords,
928934
customRecords: payload.CustomRecords(),
929935
mpp: payload.MultiPath(),
930936
amp: payload.AMPRecord(),
@@ -1019,13 +1025,62 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
10191025
ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) (
10201026
HtlcResolution, invoiceExpiry, error) {
10211027

1028+
invoiceRef := ctx.invoiceRef()
1029+
setID := (*SetID)(ctx.setID())
1030+
1031+
// We need to look up the current state of the invoice in order to send
1032+
// the previously accepted/settled HTLCs to the interceptor.
1033+
existingInvoice, err := i.idb.LookupInvoice(
1034+
context.Background(), invoiceRef,
1035+
)
1036+
switch {
1037+
case errors.Is(err, ErrInvoiceNotFound) ||
1038+
errors.Is(err, ErrNoInvoicesCreated):
1039+
1040+
// If the invoice was not found, return a failure resolution
1041+
// with an invoice not found result.
1042+
return NewFailResolution(
1043+
ctx.circuitKey, ctx.currentHeight,
1044+
ResultInvoiceNotFound,
1045+
), nil, nil
1046+
1047+
case err != nil:
1048+
ctx.log(err.Error())
1049+
return nil, nil, err
1050+
}
1051+
1052+
// Provide the invoice to the settlement interceptor to allow
1053+
// the interceptor's client an opportunity to manipulate the
1054+
// settlement process.
1055+
err = i.cfg.HtlcInterceptor.Intercept(HtlcModifyRequest{
1056+
WireCustomRecords: ctx.wireCustomRecords,
1057+
ExitHtlcCircuitKey: ctx.circuitKey,
1058+
ExitHtlcAmt: ctx.amtPaid,
1059+
ExitHtlcExpiry: ctx.expiry,
1060+
CurrentHeight: uint32(ctx.currentHeight),
1061+
Invoice: existingInvoice,
1062+
}, func(resp HtlcModifyResponse) {
1063+
log.Debugf("Received invoice HTLC interceptor response: %v",
1064+
resp)
1065+
1066+
if resp.AmountPaid != 0 {
1067+
ctx.amtPaid = resp.AmountPaid
1068+
}
1069+
})
1070+
if err != nil {
1071+
err := fmt.Errorf("error during invoice HTLC interception: %w",
1072+
err)
1073+
ctx.log(err.Error())
1074+
1075+
return nil, nil, err
1076+
}
1077+
10221078
// We'll attempt to settle an invoice matching this rHash on disk (if
10231079
// one exists). The callback will update the invoice state and/or htlcs.
10241080
var (
10251081
resolution HtlcResolution
10261082
updateSubscribers bool
10271083
)
1028-
10291084
callback := func(inv *Invoice) (*InvoiceUpdateDesc, error) {
10301085
updateDesc, res, err := updateInvoice(ctx, inv)
10311086
if err != nil {
@@ -1042,8 +1097,6 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
10421097
return updateDesc, nil
10431098
}
10441099

1045-
invoiceRef := ctx.invoiceRef()
1046-
setID := (*SetID)(ctx.setID())
10471100
invoice, err := i.idb.UpdateInvoice(
10481101
context.Background(), invoiceRef, setID, callback,
10491102
)
@@ -1080,6 +1133,8 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
10801133

10811134
var invoiceToExpire invoiceExpiry
10821135

1136+
log.Tracef("Settlement resolution: %T %v", resolution, resolution)
1137+
10831138
switch res := resolution.(type) {
10841139
case *HtlcFailResolution:
10851140
// Inspect latest htlc state on the invoice. If it is found,
@@ -1212,7 +1267,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
12121267
}
12131268

12141269
// Now that the links have been notified of any state changes to their
1215-
// HTLCs, we'll go ahead and notify any clients wiaiting on the invoice
1270+
// HTLCs, we'll go ahead and notify any clients waiting on the invoice
12161271
// state changes.
12171272
if updateSubscribers {
12181273
// We'll add a setID onto the notification, but only if this is

0 commit comments

Comments
 (0)