Skip to content

Commit

Permalink
Merge pull request #9095 from lightningnetwork/extract-part4-from-sta…
Browse files Browse the repository at this point in the history
…ging-branch

[custom channels 4/5]: Extract PART4 from mega staging branch
  • Loading branch information
guggero authored Sep 19, 2024
2 parents cdad5d9 + e0b4601 commit 4ff7d77
Show file tree
Hide file tree
Showing 54 changed files with 7,325 additions and 3,839 deletions.
5 changes: 5 additions & 0 deletions config_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/msgmux"
Expand Down Expand Up @@ -182,6 +183,10 @@ type AuxComponents struct {
// AuxDataParser is an optional data parser that can be used to parse
// auxiliary data for certain custom channel types.
AuxDataParser fn.Option[AuxDataParser]

// AuxChanCloser is an optional channel closer that can be used to
// modify the way a coop-close transaction is constructed.
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
}

// DefaultWalletImpl is the default implementation of our normal, btcwallet
Expand Down
2 changes: 1 addition & 1 deletion contractcourt/htlc_incoming_contest_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func (h *htlcIncomingContestResolver) Resolve(

resolution, err := h.Registry.NotifyExitHopHtlc(
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
circuitKey, hodlQueue.ChanIn(), payload,
circuitKey, hodlQueue.ChanIn(), nil, payload,
)
if err != nil {
return nil, err
Expand Down
1 change: 1 addition & 0 deletions contractcourt/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Registry interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32,
circuitKey models.CircuitKey, hodlChan chan<- interface{},
wireCustomRecords lnwire.CustomRecords,
payload invoices.Payload) (invoices.HtlcResolution, error)

// HodlUnsubscribeAll unsubscribes from all htlc resolutions.
Expand Down
1 change: 1 addition & 0 deletions contractcourt/mock_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type mockRegistry struct {
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey models.CircuitKey, hodlChan chan<- interface{},
wireCustomRecords lnwire.CustomRecords,
payload invoices.Payload) (invoices.HtlcResolution, error) {

r.notifyChan <- notifyExitHopData{
Expand Down
1 change: 1 addition & 0 deletions htlcswitch/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type InvoiceDatabase interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32,
circuitKey models.CircuitKey, hodlChan chan<- interface{},
wireCustomRecords lnwire.CustomRecords,
payload invoices.Payload) (invoices.HtlcResolution, error)

// CancelInvoice attempts to cancel the invoice corresponding to the
Expand Down
2 changes: 1 addition & 1 deletion htlcswitch/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -3846,7 +3846,7 @@ func (l *channelLink) processExitHop(add lnwire.UpdateAddHTLC,

event, err := l.cfg.Registry.NotifyExitHopHtlc(
invoiceHash, add.Amount, add.Expiry, int32(heightNow),
circuitKey, l.hodlQueue.ChanIn(), payload,
circuitKey, l.hodlQueue.ChanIn(), add.CustomRecords, payload,
)
if err != nil {
return err
Expand Down
7 changes: 5 additions & 2 deletions htlcswitch/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry {
panic(err)
}

modifierMock := &invoices.MockHtlcModifier{}
registry := invoices.NewRegistry(
cdb,
invoices.NewInvoiceExpiryWatcher(
Expand All @@ -1022,6 +1023,7 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry {
),
&invoices.RegistryConfig{
FinalCltvRejectDelta: 5,
HtlcInterceptor: modifierMock,
},
)
registry.Start()
Expand All @@ -1047,11 +1049,12 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey models.CircuitKey, hodlChan chan<- interface{},
wireCustomRecords lnwire.CustomRecords,
payload invoices.Payload) (invoices.HtlcResolution, error) {

event, err := i.registry.NotifyExitHopHtlc(
rhash, amt, expiry, currentHeight, circuitKey, hodlChan,
payload,
rhash, amt, expiry, currentHeight, circuitKey,
hodlChan, wireCustomRecords, payload,
)
if err != nil {
return nil, err
Expand Down
68 changes: 68 additions & 0 deletions invoices/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,71 @@ type InvoiceUpdater interface {
// Finalize finalizes the update before it is written to the database.
Finalize(updateType UpdateType) error
}

// HtlcModifyRequest is the request that is passed to the client via callback
// during a HTLC interceptor session. The request contains the invoice that the
// given HTLC is attempting to settle.
type HtlcModifyRequest struct {
// WireCustomRecords are the custom records that were parsed from the
// HTLC wire message. These are the records of the current HTLC to be
// accepted/settled. All previously accepted/settled HTLCs for the same
// invoice are present in the Invoice field below.
WireCustomRecords lnwire.CustomRecords

// ExitHtlcCircuitKey is the circuit key that identifies the HTLC which
// is involved in the invoice settlement.
ExitHtlcCircuitKey CircuitKey

// ExitHtlcAmt is the amount of the HTLC which is involved in the
// invoice settlement.
ExitHtlcAmt lnwire.MilliSatoshi

// ExitHtlcExpiry is the absolute expiry height of the HTLC which is
// involved in the invoice settlement.
ExitHtlcExpiry uint32

// CurrentHeight is the current block height.
CurrentHeight uint32

// Invoice is the invoice that is being intercepted. The HTLCs within
// the invoice are only those previously accepted/settled for the same
// invoice.
Invoice Invoice
}

// HtlcModifyResponse is the response that the client should send back to the
// interceptor after processing the HTLC modify request.
type HtlcModifyResponse struct {
// AmountPaid is the amount that the client has decided the HTLC is
// actually worth. This might be different from the amount that the
// HTLC was originally sent with, in case additional value is carried
// along with it (which might be the case in custom channels).
AmountPaid lnwire.MilliSatoshi
}

// HtlcModifyCallback is a function that is called when an invoice is
// intercepted by the invoice interceptor.
type HtlcModifyCallback func(HtlcModifyRequest) (*HtlcModifyResponse, error)

// HtlcModifier is an interface that allows an intercept client to register
// itself as a modifier of HTLCs that are settling an invoice. The client can
// then modify the HTLCs based on the invoice and the HTLC that is settling it.
type HtlcModifier interface {
// RegisterInterceptor sets the client callback function that will be
// called when an invoice is intercepted. If a callback is already set,
// an error is returned. The returned function must be used to reset the
// callback to nil once the client is done or disconnects. The read-only
// channel closes when the server stops.
RegisterInterceptor(HtlcModifyCallback) (func(), <-chan struct{}, error)
}

// HtlcInterceptor is an interface that allows the invoice registry to let
// clients intercept invoices before they are settled.
type HtlcInterceptor interface {
// Intercept generates a new intercept session for the given invoice.
// The call blocks until the client has responded to the request or an
// error occurs. The response callback is only called if a session was
// created in the first place, which is only the case if a client is
// registered.
Intercept(HtlcModifyRequest, func(HtlcModifyResponse)) error
}
63 changes: 59 additions & 4 deletions invoices/invoiceregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ type RegistryConfig struct {
// KeysendHoldTime indicates for how long we want to accept and hold
// spontaneous keysend payments.
KeysendHoldTime time.Duration

// HtlcInterceptor is an interface that allows the invoice registry to
// let clients intercept invoices before they are settled.
HtlcInterceptor HtlcInterceptor
}

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

// Create the update context containing the relevant details of the
Expand All @@ -925,6 +930,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
expiry: expiry,
currentHeight: currentHeight,
finalCltvRejectDelta: i.cfg.FinalCltvRejectDelta,
wireCustomRecords: wireCustomRecords,
customRecords: payload.CustomRecords(),
mpp: payload.MultiPath(),
amp: payload.AMPRecord(),
Expand Down Expand Up @@ -1019,13 +1025,62 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) (
HtlcResolution, invoiceExpiry, error) {

invoiceRef := ctx.invoiceRef()
setID := (*SetID)(ctx.setID())

// We need to look up the current state of the invoice in order to send
// the previously accepted/settled HTLCs to the interceptor.
existingInvoice, err := i.idb.LookupInvoice(
context.Background(), invoiceRef,
)
switch {
case errors.Is(err, ErrInvoiceNotFound) ||
errors.Is(err, ErrNoInvoicesCreated):

// If the invoice was not found, return a failure resolution
// with an invoice not found result.
return NewFailResolution(
ctx.circuitKey, ctx.currentHeight,
ResultInvoiceNotFound,
), nil, nil

case err != nil:
ctx.log(err.Error())
return nil, nil, err
}

// Provide the invoice to the settlement interceptor to allow
// the interceptor's client an opportunity to manipulate the
// settlement process.
err = i.cfg.HtlcInterceptor.Intercept(HtlcModifyRequest{
WireCustomRecords: ctx.wireCustomRecords,
ExitHtlcCircuitKey: ctx.circuitKey,
ExitHtlcAmt: ctx.amtPaid,
ExitHtlcExpiry: ctx.expiry,
CurrentHeight: uint32(ctx.currentHeight),
Invoice: existingInvoice,
}, func(resp HtlcModifyResponse) {
log.Debugf("Received invoice HTLC interceptor response: %v",
resp)

if resp.AmountPaid != 0 {
ctx.amtPaid = resp.AmountPaid
}
})
if err != nil {
err := fmt.Errorf("error during invoice HTLC interception: %w",
err)
ctx.log(err.Error())

return nil, nil, err
}

// We'll attempt to settle an invoice matching this rHash on disk (if
// one exists). The callback will update the invoice state and/or htlcs.
var (
resolution HtlcResolution
updateSubscribers bool
)

callback := func(inv *Invoice) (*InvoiceUpdateDesc, error) {
updateDesc, res, err := updateInvoice(ctx, inv)
if err != nil {
Expand All @@ -1042,8 +1097,6 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
return updateDesc, nil
}

invoiceRef := ctx.invoiceRef()
setID := (*SetID)(ctx.setID())
invoice, err := i.idb.UpdateInvoice(
context.Background(), invoiceRef, setID, callback,
)
Expand Down Expand Up @@ -1080,6 +1133,8 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(

var invoiceToExpire invoiceExpiry

log.Tracef("Settlement resolution: %T %v", resolution, resolution)

switch res := resolution.(type) {
case *HtlcFailResolution:
// Inspect latest htlc state on the invoice. If it is found,
Expand Down Expand Up @@ -1212,7 +1267,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
}

// Now that the links have been notified of any state changes to their
// HTLCs, we'll go ahead and notify any clients wiaiting on the invoice
// HTLCs, we'll go ahead and notify any clients waiting on the invoice
// state changes.
if updateSubscribers {
// We'll add a setID onto the notification, but only if this is
Expand Down
Loading

0 comments on commit 4ff7d77

Please sign in to comment.