@@ -11,11 +11,13 @@ import (
1111 sphinx "github.com/lightningnetwork/lightning-onion"
1212 "github.com/lightningnetwork/lnd/channeldb"
1313 "github.com/lightningnetwork/lnd/channeldb/models"
14+ "github.com/lightningnetwork/lnd/fn"
1415 "github.com/lightningnetwork/lnd/htlcswitch"
1516 "github.com/lightningnetwork/lnd/lntypes"
1617 "github.com/lightningnetwork/lnd/lnwire"
1718 "github.com/lightningnetwork/lnd/routing/route"
1819 "github.com/lightningnetwork/lnd/routing/shards"
20+ "github.com/lightningnetwork/lnd/tlv"
1921)
2022
2123// ErrPaymentLifecycleExiting is used when waiting for htlc attempt result, but
@@ -275,6 +277,13 @@ lifecycle:
275277
276278 log .Tracef ("Found route: %s" , spew .Sdump (rt .Hops ))
277279
280+ // Allow the traffic shaper to add custom records to the
281+ // outgoing HTLC and also adjust the amount if needed.
282+ err = p .amendFirstHopData (rt )
283+ if err != nil {
284+ return exitWithErr (err )
285+ }
286+
278287 // We found a route to try, create a new HTLC attempt to try.
279288 attempt , err := p .registerAttempt (rt , ps .RemainingAmt )
280289 if err != nil {
@@ -668,8 +677,10 @@ func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
668677func (p * paymentLifecycle ) sendAttempt (
669678 attempt * channeldb.HTLCAttempt ) (* attemptResult , error ) {
670679
671- log .Debugf ("Sending HTLC attempt(id=%v, amt=%v) for payment %v" ,
672- attempt .AttemptID , attempt .Route .TotalAmount , p .identifier )
680+ log .Debugf ("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d" +
681+ ") for payment %v" , attempt .AttemptID ,
682+ attempt .Route .TotalAmount , attempt .Route .FirstHopAmount .Val ,
683+ p .identifier )
673684
674685 rt := attempt .Route
675686
@@ -680,10 +691,10 @@ func (p *paymentLifecycle) sendAttempt(
680691 // this packet will be used to route the payment through the network,
681692 // starting with the first-hop.
682693 htlcAdd := & lnwire.UpdateAddHTLC {
683- Amount : rt .TotalAmount ,
694+ Amount : rt .FirstHopAmount . Val . Int () ,
684695 Expiry : rt .TotalTimeLock ,
685696 PaymentHash : * attempt .Hash ,
686- CustomRecords : p . firstHopCustomRecords ,
697+ CustomRecords : rt . FirstHopWireCustomRecords ,
687698 }
688699
689700 // Generate the raw encoded sphinx packet to be included along
@@ -722,6 +733,75 @@ func (p *paymentLifecycle) sendAttempt(
722733 }, nil
723734}
724735
736+ // amendFirstHopData is a function that calls the traffic shaper to allow it to
737+ // add custom records to the outgoing HTLC and also adjust the amount if
738+ // needed.
739+ func (p * paymentLifecycle ) amendFirstHopData (rt * route.Route ) error {
740+ // The first hop amount on the route is the full route amount if not
741+ // overwritten by the traffic shaper. So we set the initial value now
742+ // and potentially overwrite it later.
743+ rt .FirstHopAmount = tlv.NewRecordT [tlv.TlvType0 ](
744+ tlv .NewBigSizeT (rt .TotalAmount ),
745+ )
746+
747+ // By default, we set the first hop custom records to the initial
748+ // value requested by the RPC. The traffic shaper may overwrite this
749+ // value.
750+ rt .FirstHopWireCustomRecords = p .firstHopCustomRecords
751+
752+ // extraDataRequest is a helper struct to pass the custom records and
753+ // amount back from the traffic shaper.
754+ type extraDataRequest struct {
755+ customRecords fn.Option [lnwire.CustomRecords ]
756+
757+ amount fn.Option [lnwire.MilliSatoshi ]
758+ }
759+
760+ // If a hook exists that may affect our outgoing message, we call it now
761+ // and apply its side effects to the UpdateAddHTLC message.
762+ result , err := fn .MapOptionZ (
763+ p .router .cfg .TrafficShaper ,
764+ func (ts TlvTrafficShaper ) fn.Result [extraDataRequest ] {
765+ newAmt , newRecords , err := ts .ProduceHtlcExtraData (
766+ rt .TotalAmount , p .firstHopCustomRecords ,
767+ )
768+ if err != nil {
769+ return fn.Err [extraDataRequest ](err )
770+ }
771+
772+ // Make sure we only received valid records.
773+ if err := newRecords .Validate (); err != nil {
774+ return fn.Err [extraDataRequest ](err )
775+ }
776+
777+ log .Debugf ("TLV traffic shaper returned custom " +
778+ "records %v and amount %d msat for HTLC" ,
779+ spew .Sdump (newRecords ), newAmt )
780+
781+ return fn .Ok (extraDataRequest {
782+ customRecords : fn .Some (newRecords ),
783+ amount : fn .Some (newAmt ),
784+ })
785+ },
786+ ).Unpack ()
787+ if err != nil {
788+ return fmt .Errorf ("traffic shaper failed to produce extra " +
789+ "data: %w" , err )
790+ }
791+
792+ // Apply the side effects to the UpdateAddHTLC message.
793+ result .customRecords .WhenSome (func (records lnwire.CustomRecords ) {
794+ rt .FirstHopWireCustomRecords = records
795+ })
796+ result .amount .WhenSome (func (amount lnwire.MilliSatoshi ) {
797+ rt .FirstHopAmount = tlv.NewRecordT [tlv.TlvType0 ](
798+ tlv .NewBigSizeT (amount ),
799+ )
800+ })
801+
802+ return nil
803+ }
804+
725805// failAttemptAndPayment fails both the payment and its attempt via the
726806// router's control tower, which marks the payment as failed in db.
727807func (p * paymentLifecycle ) failPaymentAndAttempt (
0 commit comments