routing: use first hop records on path finding

This commit is contained in:
George Tsagkarelis
2024-05-02 18:48:28 +02:00
committed by Oliver Gugger
parent 4804cbf139
commit aa17543d23
4 changed files with 128 additions and 15 deletions

View File

@ -466,6 +466,10 @@ type RestrictParams struct {
// BlindedPaymentPathSet is necessary to determine the hop size of the // BlindedPaymentPathSet is necessary to determine the hop size of the
// last/exit hop. // last/exit hop.
BlindedPaymentPathSet *BlindedPaymentPathSet BlindedPaymentPathSet *BlindedPaymentPathSet
// FirstHopCustomRecords includes any records that should be included in
// the update_add_htlc message towards our peer.
FirstHopCustomRecords lnwire.CustomRecords
} }
// PathFindingConfig defines global parameters that control the trade-off in // PathFindingConfig defines global parameters that control the trade-off in
@ -524,7 +528,16 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
max = bandwidth max = bandwidth
} }
total += bandwidth var overflow bool
total, overflow = overflowSafeAdd(total, bandwidth)
if overflow {
// If the current total and the bandwidth would
// overflow the maximum value, we set the total to the
// maximum value. Which is more milli-satoshis than are
// in existence anyway, so the actual value is
// irrelevant.
total = lnwire.MilliSatoshi(math.MaxUint64)
}
return nil return nil
} }
@ -1446,3 +1459,15 @@ func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
// The final hop does not have a short chanID set. // The final hop does not have a short chanID set.
return finalHop.PayloadSize(0) return finalHop.PayloadSize(0)
} }
// overflowSafeAdd adds two MilliSatoshi values and returns the result. If an
// overflow could occur, zero is returned instead and the boolean is set to
// true.
func overflowSafeAdd(x, y lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
if y > math.MaxUint64-x {
// Overflow would occur, return 0 and set overflow flag.
return 0, true
}
return x + y, false
}

View File

@ -11,11 +11,13 @@ import (
sphinx "github.com/lightningnetwork/lightning-onion" sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/routing/shards" "github.com/lightningnetwork/lnd/routing/shards"
"github.com/lightningnetwork/lnd/tlv"
) )
// ErrPaymentLifecycleExiting is used when waiting for htlc attempt result, but // ErrPaymentLifecycleExiting is used when waiting for htlc attempt result, but
@ -275,6 +277,13 @@ lifecycle:
log.Tracef("Found route: %s", spew.Sdump(rt.Hops)) log.Tracef("Found route: %s", spew.Sdump(rt.Hops))
// Allow the traffic shaper to add custom records to the
// outgoing HTLC and also adjust the amount if needed.
err = p.amendFirstHopData(rt)
if err != nil {
return exitWithErr(err)
}
// We found a route to try, create a new HTLC attempt to try. // We found a route to try, create a new HTLC attempt to try.
attempt, err := p.registerAttempt(rt, ps.RemainingAmt) attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
if err != nil { if err != nil {
@ -668,8 +677,10 @@ func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
func (p *paymentLifecycle) sendAttempt( func (p *paymentLifecycle) sendAttempt(
attempt *channeldb.HTLCAttempt) (*attemptResult, error) { attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
log.Debugf("Sending HTLC attempt(id=%v, amt=%v) for payment %v", log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
attempt.AttemptID, attempt.Route.TotalAmount, p.identifier) ") for payment %v", attempt.AttemptID,
attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
p.identifier)
rt := attempt.Route rt := attempt.Route
@ -680,10 +691,10 @@ func (p *paymentLifecycle) sendAttempt(
// this packet will be used to route the payment through the network, // this packet will be used to route the payment through the network,
// starting with the first-hop. // starting with the first-hop.
htlcAdd := &lnwire.UpdateAddHTLC{ htlcAdd := &lnwire.UpdateAddHTLC{
Amount: rt.TotalAmount, Amount: rt.FirstHopAmount.Val.Int(),
Expiry: rt.TotalTimeLock, Expiry: rt.TotalTimeLock,
PaymentHash: *attempt.Hash, PaymentHash: *attempt.Hash,
CustomRecords: p.firstHopCustomRecords, CustomRecords: rt.FirstHopWireCustomRecords,
} }
// Generate the raw encoded sphinx packet to be included along // Generate the raw encoded sphinx packet to be included along
@ -722,6 +733,75 @@ func (p *paymentLifecycle) sendAttempt(
}, nil }, nil
} }
// amendFirstHopData is a function that calls the traffic shaper to allow it to
// add custom records to the outgoing HTLC and also adjust the amount if
// needed.
func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error {
// The first hop amount on the route is the full route amount if not
// overwritten by the traffic shaper. So we set the initial value now
// and potentially overwrite it later.
rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
tlv.NewBigSizeT(rt.TotalAmount),
)
// By default, we set the first hop custom records to the initial
// value requested by the RPC. The traffic shaper may overwrite this
// value.
rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
// extraDataRequest is a helper struct to pass the custom records and
// amount back from the traffic shaper.
type extraDataRequest struct {
customRecords fn.Option[lnwire.CustomRecords]
amount fn.Option[lnwire.MilliSatoshi]
}
// If a hook exists that may affect our outgoing message, we call it now
// and apply its side effects to the UpdateAddHTLC message.
result, err := fn.MapOptionZ(
p.router.cfg.TrafficShaper,
func(ts TlvTrafficShaper) fn.Result[extraDataRequest] {
newAmt, newRecords, err := ts.ProduceHtlcExtraData(
rt.TotalAmount, p.firstHopCustomRecords,
)
if err != nil {
return fn.Err[extraDataRequest](err)
}
// Make sure we only received valid records.
if err := newRecords.Validate(); err != nil {
return fn.Err[extraDataRequest](err)
}
log.Debugf("TLV traffic shaper returned custom "+
"records %v and amount %d msat for HTLC",
spew.Sdump(newRecords), newAmt)
return fn.Ok(extraDataRequest{
customRecords: fn.Some(newRecords),
amount: fn.Some(newAmt),
})
},
).Unpack()
if err != nil {
return fmt.Errorf("traffic shaper failed to produce extra "+
"data: %w", err)
}
// Apply the side effects to the UpdateAddHTLC message.
result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
rt.FirstHopWireCustomRecords = records
})
result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
tlv.NewBigSizeT(amount),
)
})
return nil
}
// failAttemptAndPayment fails both the payment and its attempt via the // failAttemptAndPayment fails both the payment and its attempt via the
// router's control tower, which marks the payment as failed in db. // router's control tower, which marks the payment as failed in db.
func (p *paymentLifecycle) failPaymentAndAttempt( func (p *paymentLifecycle) failPaymentAndAttempt(

View File

@ -268,16 +268,17 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
// to our destination, respecting the recommendations from // to our destination, respecting the recommendations from
// MissionControl. // MissionControl.
restrictions := &RestrictParams{ restrictions := &RestrictParams{
ProbabilitySource: p.missionControl.GetProbability, ProbabilitySource: p.missionControl.GetProbability,
FeeLimit: feeLimit, FeeLimit: feeLimit,
OutgoingChannelIDs: p.payment.OutgoingChannelIDs, OutgoingChannelIDs: p.payment.OutgoingChannelIDs,
LastHop: p.payment.LastHop, LastHop: p.payment.LastHop,
CltvLimit: cltvLimit, CltvLimit: cltvLimit,
DestCustomRecords: p.payment.DestCustomRecords, DestCustomRecords: p.payment.DestCustomRecords,
DestFeatures: p.payment.DestFeatures, DestFeatures: p.payment.DestFeatures,
PaymentAddr: p.payment.PaymentAddr, PaymentAddr: p.payment.PaymentAddr,
Amp: p.payment.amp, Amp: p.payment.amp,
Metadata: p.payment.Metadata, Metadata: p.payment.Metadata,
FirstHopCustomRecords: firstHopCustomRecords,
} }
finalHtlcExpiry := int32(height) + int32(finalCltvDelta) finalHtlcExpiry := int32(height) + int32(finalCltvDelta)

View File

@ -1188,6 +1188,13 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
firstHopCustomRecords, firstHopCustomRecords,
) )
// Allow the traffic shaper to add custom records to the outgoing HTLC
// and also adjust the amount if needed.
err = p.amendFirstHopData(rt)
if err != nil {
return nil, err
}
// We found a route to try, create a new HTLC attempt to try. // We found a route to try, create a new HTLC attempt to try.
// //
// NOTE: we use zero `remainingAmt` here to simulate the same effect of // NOTE: we use zero `remainingAmt` here to simulate the same effect of