Skip to content
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

[custom channels 4/5]: Extract PART4 from mega staging branch #9095

Merged
merged 20 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b8c8774
invoices: add invoice htlc interceptor service
ffranr Apr 23, 2024
c58b6a2
invoices: integrate settlement interceptor with invoice registry
ffranr Apr 23, 2024
481dfe2
lnd: initialize invoice settlement interceptor at server startup
ffranr Apr 23, 2024
008d265
invoicesrpc: add HTLC modifier to invoices RPC server
ffranr Apr 23, 2024
1975fa6
invoicesrpc: add `HtlcModifier` RPC endpoint and modifier RPC server
ffranr Apr 23, 2024
0c6a155
lntest: add `HtlcModifier` support to node RPC harness
ffranr Apr 30, 2024
83923c7
lnwire: add MergedCopy method to CustomRecords
guggero Sep 12, 2024
bbae714
multi: pass `UpdateAddHtlc` message custom records to invoice modifier
ffranr May 8, 2024
d37df75
lnrpc+rpcserver: encode custom records as custom channel data
guggero Sep 12, 2024
9a972e1
itest: add basic invoice HTLC modifier integration test
ffranr Apr 30, 2024
099f556
lnwire: add CustomRecords to shutdown message
Roasbeef May 29, 2024
7b396f4
lnwallet: add FundingBlob method to LightningChannel
Roasbeef May 29, 2024
517608c
lnwallet: add ability to add extra co-op close outputs
Roasbeef May 29, 2024
117a144
lnwallet: add ability to do custom sort for coop close txn
Roasbeef May 29, 2024
7ff251c
lnwallet/chancloser: add new AuxChanCloser interface
Roasbeef May 29, 2024
8d651b9
lnwallet/chancloser: add aux chan closer, use in coop flow
Roasbeef May 29, 2024
625d426
lnwallet: modify CoopCloseBalance to not depend on chan commit
Roasbeef May 29, 2024
44ab7e6
server+peer: init peer struct w/ AuxChanCloser if present
Roasbeef May 29, 2024
8dee76a
peer: decorate delivery addr w/ internal key
Roasbeef May 29, 2024
e0b4601
multi: add co-op close custom data to close update
guggero May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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
ellemouton marked this conversation as resolved.
Show resolved Hide resolved
}
})
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
Loading