diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index 2f180e257..52a57b095 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -8,9 +8,11 @@ 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" + "github.com/lightningnetwork/lnd/record" ) var ( @@ -105,6 +107,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. @@ -119,6 +125,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 @@ -375,6 +389,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) @@ -617,6 +636,67 @@ 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. + switch htlc := f.packet.htlc.(type) { + case *lnwire.UpdateAddHTLC: + outgoingAmountMsat.WhenSome(func(amount lnwire.MilliSatoshi) { + htlc.Amount = amount + }) + + //nolint:lll + err := fn.MapOptionZ(customRecords, func(records record.CustomSet) error { + if len(records) == 0 { + return nil + } + + // Type cast and validate custom records. + htlc.CustomRecords = lnwire.CustomRecords(records) + err := htlc.CustomRecords.Validate() + if err != nil { + return fmt.Errorf("failed to validate custom "+ + "records: %w", err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("failed to encode custom records: %w", + err) + } + + case *lnwire.UpdateFulfillHTLC: + //nolint:lll + err := fn.MapOptionZ(customRecords, func(records record.CustomSet) error { + if len(records) == 0 { + return nil + } + + // Type cast and validate custom records. + htlc.CustomRecords = lnwire.CustomRecords(records) + err := htlc.CustomRecords.Validate() + if err != nil { + return fmt.Errorf("failed to validate custom "+ + "records: %w", err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("failed to encode custom records: %w", + err) + } + } + + // 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 bad2c353d..a8d77d6e6 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -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" @@ -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 diff --git a/intercepted_forward.go b/intercepted_forward.go index 70590d0e4..044abd3fe 100644 --- a/intercepted_forward.go +++ b/intercepted_forward.go @@ -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 ( @@ -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 {