Skip to content
Closed
65 changes: 65 additions & 0 deletions htlcswitch/interceptable_switch.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package htlcswitch

import (
"bytes"
"crypto/sha256"
"fmt"
"sync"

"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)

var (
Expand Down Expand Up @@ -105,6 +109,10 @@ const (

// FwdActionFail fails the intercepted packet back to the sender.
FwdActionFail

// FwdActionResumeModified forwards the intercepted packet to the switch
// with modifications.
FwdActionResumeModified
)

// FwdResolution defines the action to be taken on an intercepted packet.
Expand All @@ -119,6 +127,14 @@ type FwdResolution struct {
// FwdActionSettle.
Preimage lntypes.Preimage

// OutgoingAmountMsat is the amount that is to be used for forwarding if
// Action is FwdActionResumeModified.
OutgoingAmountMsat fn.Option[lnwire.MilliSatoshi]

// CustomRecords is the custom records that are to be used for
// forwarding if Action is FwdActionResumeModified.
CustomRecords fn.Option[record.CustomSet]

// FailureMessage is the encrypted failure message that is to be passed
// back to the sender if action is FwdActionFail.
FailureMessage []byte
Expand Down Expand Up @@ -363,6 +379,8 @@ func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) {
})
}

// resolve processes a HTLC given the resolution type specified by the
// intercepting client.
func (s *InterceptableSwitch) resolve(res *FwdResolution) error {
intercepted, err := s.heldHtlcSet.pop(res.Key)
if err != nil {
Expand All @@ -373,6 +391,11 @@ func (s *InterceptableSwitch) resolve(res *FwdResolution) error {
case FwdActionResume:
return intercepted.Resume()

case FwdActionResumeModified:
return intercepted.ResumeModified(
res.OutgoingAmountMsat, res.CustomRecords,
)

case FwdActionSettle:
return intercepted.Settle(res.Preimage)

Expand Down Expand Up @@ -615,6 +638,48 @@ func (f *interceptedForward) Resume() error {
return f.htlcSwitch.ForwardPackets(nil, f.packet)
}

// ResumeModified resumes the default behavior with field modifications.
func (f *interceptedForward) ResumeModified(
outgoingAmountMsat fn.Option[lnwire.MilliSatoshi],
customRecords fn.Option[record.CustomSet]) error {

// Modify the wire message contained in the packet.
htlc, ok := f.packet.htlc.(*lnwire.UpdateAddHTLC)
if ok {
outgoingAmountMsat.WhenSome(func(amount lnwire.MilliSatoshi) {
htlc.Amount = amount
})

var customRecordsErr error
customRecords.WhenSome(func(records record.CustomSet) {
if len(records) == 0 {
return
}

// Encode the custom records to bytes.
var buf bytes.Buffer
if err := records.Encode(&buf); err != nil {
customRecordsErr = err
return
}
recordsBytes := buf.Bytes()

// Create a new custom records blob TLV record.
//nolint:lll
tlvRecord := tlv.NewPrimitiveRecord[lnwire.CustomRecordsBlobTlvType](recordsBytes)
htlc.CustomRecordsBlob = tlv.SomeRecordT(tlvRecord)
})
if customRecordsErr != nil {
return fmt.Errorf("failed to encode custom records: %w",
customRecordsErr)
}
}

// Forward to the switch. A link quit channel isn't needed, because we
// are on a different thread now.
return f.htlcSwitch.ForwardPackets(nil, f.packet)
}

// Fail notifies the intention to Fail an existing hold forward with an
// encrypted failure reason.
func (f *interceptedForward) Fail(reason []byte) error {
Expand Down
8 changes: 7 additions & 1 deletion htlcswitch/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
Expand Down Expand Up @@ -323,7 +324,7 @@ type InterceptableHtlcForwarder interface {
type ForwardInterceptor func(InterceptedPacket) error

// InterceptedPacket contains the relevant information for the interceptor about
// an htlc.
// a HTLC.
type InterceptedPacket struct {
// IncomingCircuit contains the incoming channel and htlc id of the
// packet.
Expand Down Expand Up @@ -375,6 +376,11 @@ type InterceptedForward interface {
// this htlc which usually means forward it.
Resume() error

// ResumeModified notifies the intention to resume an existing hold
// forward with modified fields.
ResumeModified(outgoingAmountMsat fn.Option[lnwire.MilliSatoshi],
customRecords fn.Option[record.CustomSet]) error

// Settle notifies the intention to settle an existing hold
// forward with a given preimage.
Settle(lntypes.Preimage) error
Expand Down
10 changes: 10 additions & 0 deletions intercepted_forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package lnd
import (
"errors"

"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
)

var (
Expand Down Expand Up @@ -51,6 +53,14 @@ func (f *interceptedForward) Resume() error {
return ErrCannotResume
}

// ResumeModified notifies the intention to resume an existing hold forward with
// a modified htlc.
func (f *interceptedForward) ResumeModified(_ fn.Option[lnwire.MilliSatoshi],
_ fn.Option[record.CustomSet]) error {

return ErrCannotResume
}

// Fail notifies the intention to fail an existing hold forward with an
// encrypted failure reason.
func (f *interceptedForward) Fail(_ []byte) error {
Expand Down
8 changes: 8 additions & 0 deletions itest/list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,14 @@ var allTestCases = []*lntest.TestCase{
Name: "forward interceptor",
TestFunc: testForwardInterceptorBasic,
},
{
Name: "forward interceptor modified htlc",
TestFunc: testForwardInterceptorModifiedHtlc,
},
{
Name: "forward interceptor first hop records",
TestFunc: testForwardInterceptorFirstHopRecords,
},
{
Name: "zero conf channel open",
TestFunc: testZeroConfChannelOpen,
Expand Down
Loading