diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index 5517eb82d..5a1fd807e 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -9,6 +9,7 @@ import ( "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" @@ -109,6 +110,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. @@ -123,6 +128,14 @@ type FwdResolution struct { // FwdActionSettle. Preimage lntypes.Preimage + // OutAmountMsat is the amount that is to be used for forwarding if + // Action is FwdActionResumeModified. + OutAmountMsat fn.Option[lnwire.MilliSatoshi] + + // OutWireCustomRecords is the custom records that are to be used for + // forwarding if Action is FwdActionResumeModified. + OutWireCustomRecords fn.Option[lnwire.CustomRecords] + // FailureMessage is the encrypted failure message that is to be passed // back to the sender if action is FwdActionFail. FailureMessage []byte @@ -399,6 +412,11 @@ func (s *InterceptableSwitch) resolve(res *FwdResolution) error { case FwdActionResume: return intercepted.Resume() + case FwdActionResumeModified: + return intercepted.ResumeModified( + res.OutAmountMsat, res.OutWireCustomRecords, + ) + case FwdActionSettle: return intercepted.Settle(res.Preimage) @@ -641,6 +659,64 @@ func (f *interceptedForward) Resume() error { return f.htlcSwitch.ForwardPackets(nil, f.packet) } +// ResumeModified resumes the default behavior with field modifications. The +// input amount (if provided) specifies that the value of the inbound HTLC +// should be interpreted differently from the on-chain amount during further +// validation. The presence of an output amount and/or custom records indicates +// that those values should be modified on the outgoing HTLC. +func (f *interceptedForward) ResumeModified( + outAmountMsat fn.Option[lnwire.MilliSatoshi], + outWireCustomRecords fn.Option[lnwire.CustomRecords]) error { + + // Convert the optional custom records to the correct type and validate + // them. + validatedRecords, err := fn.MapOptionZ( + outWireCustomRecords, + func(cr lnwire.CustomRecords) fn.Result[lnwire.CustomRecords] { + if len(cr) == 0 { + return fn.Ok[lnwire.CustomRecords](nil) + } + + // Type cast and validate custom records. + err := cr.Validate() + if err != nil { + return fn.Err[lnwire.CustomRecords]( + fmt.Errorf("failed to validate "+ + "custom records: %w", err), + ) + } + + return fn.Ok(cr) + }, + ).Unpack() + if err != nil { + return fmt.Errorf("failed to encode custom records: %w", + err) + } + + // Modify the wire message contained in the packet. + switch htlc := f.packet.htlc.(type) { + case *lnwire.UpdateAddHTLC: + outAmountMsat.WhenSome(func(amount lnwire.MilliSatoshi) { + f.packet.amount = amount + htlc.Amount = amount + }) + + if len(validatedRecords) > 0 { + htlc.CustomRecords = validatedRecords + } + + case *lnwire.UpdateFulfillHTLC: + if len(validatedRecords) > 0 { + htlc.CustomRecords = validatedRecords + } + } + + // 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 { diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 246c76902..99b3ce673 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -383,6 +383,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[lnwire.CustomRecords]) error + // Settle notifies the intention to settle an existing hold // forward with a given preimage. Settle(lntypes.Preimage) error diff --git a/intercepted_forward.go b/intercepted_forward.go index 70590d0e4..c3ffbb4fb 100644 --- a/intercepted_forward.go +++ b/intercepted_forward.go @@ -3,6 +3,7 @@ package lnd import ( "errors" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -51,6 +52,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[lnwire.CustomRecords]) error { + + return ErrCannotResume +} + // Fail notifies the intention to fail an existing hold forward with an // encrypted failure reason. func (f *interceptedForward) Fail(_ []byte) error { diff --git a/invoices/invoices_test.go b/invoices/invoices_test.go index 800261355..b6efd6e55 100644 --- a/invoices/invoices_test.go +++ b/invoices/invoices_test.go @@ -176,7 +176,7 @@ func TestInvoices(t *testing.T) { test: testQueryInvoices, }, { - name: "CustomRecords", + name: "OutWireCustomRecords", test: testCustomRecords, }, {