2019-05-23 21:05:29 +03:00
|
|
|
package routing
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
|
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
2019-05-23 21:05:29 +03:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
2019-05-23 21:05:29 +03:00
|
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
|
|
)
|
|
|
|
|
2019-05-23 22:17:16 +03:00
|
|
|
// errNoRoute is returned when all routes from the payment session have been
|
|
|
|
// attempted.
|
|
|
|
type errNoRoute struct {
|
|
|
|
// lastError is the error encountered during the last payment attempt,
|
|
|
|
// if at least one attempt has been made.
|
2019-06-19 12:12:10 +03:00
|
|
|
lastError error
|
2019-05-23 22:17:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Error returns a string representation of the error.
|
|
|
|
func (e errNoRoute) Error() string {
|
|
|
|
return fmt.Sprintf("unable to route payment to destination: %v",
|
|
|
|
e.lastError)
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// paymentLifecycle holds all information about the current state of a payment
|
|
|
|
// needed to resume if from any point.
|
|
|
|
type paymentLifecycle struct {
|
|
|
|
router *ChannelRouter
|
|
|
|
payment *LightningPayment
|
2019-05-23 21:05:30 +03:00
|
|
|
paySession PaymentSession
|
2019-05-23 21:05:29 +03:00
|
|
|
timeoutChan <-chan time.Time
|
|
|
|
currentHeight int32
|
|
|
|
finalCLTVDelta uint16
|
2020-02-07 12:31:27 +03:00
|
|
|
attempt *channeldb.HTLCAttemptInfo
|
2019-05-23 21:05:29 +03:00
|
|
|
circuit *sphinx.Circuit
|
2019-06-19 12:12:10 +03:00
|
|
|
lastError error
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// resumePayment resumes the paymentLifecycle from the current state.
|
|
|
|
func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) {
|
|
|
|
// We'll continue until either our payment succeeds, or we encounter a
|
|
|
|
// critical error during path finding.
|
|
|
|
for {
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// If this payment had no existing payment attempt, we create
|
|
|
|
// and send one now.
|
|
|
|
if p.attempt == nil {
|
|
|
|
firstHop, htlcAdd, err := p.createNewPaymentAttempt()
|
|
|
|
if err != nil {
|
|
|
|
return [32]byte{}, nil, err
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// Now that the attempt is created and checkpointed to
|
|
|
|
// the DB, we send it.
|
|
|
|
sendErr := p.sendPaymentAttempt(firstHop, htlcAdd)
|
|
|
|
if sendErr != nil {
|
|
|
|
// We must inspect the error to know whether it
|
|
|
|
// was critical or not, to decide whether we
|
|
|
|
// should continue trying.
|
|
|
|
err := p.handleSendError(sendErr)
|
|
|
|
if err != nil {
|
|
|
|
return [32]byte{}, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error was handled successfully, reset the
|
|
|
|
// attempt to indicate we want to make a new
|
|
|
|
// attempt.
|
|
|
|
p.attempt = nil
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If this was a resumed attempt, we must regenerate the
|
2019-12-11 10:56:41 +03:00
|
|
|
// circuit. We don't need to check for errors resulting
|
|
|
|
// from an invalid route, because the sphinx packet has
|
|
|
|
// been successfully generated before.
|
2019-05-23 21:05:29 +03:00
|
|
|
_, c, err := generateSphinxPacket(
|
|
|
|
&p.attempt.Route, p.payment.PaymentHash[:],
|
|
|
|
p.attempt.SessionKey,
|
|
|
|
)
|
|
|
|
if err != nil {
|
2019-05-23 21:05:29 +03:00
|
|
|
return [32]byte{}, nil, err
|
|
|
|
}
|
2019-05-23 21:05:29 +03:00
|
|
|
p.circuit = c
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Using the created circuit, initialize the error decrypter so we can
|
|
|
|
// parse+decode any failures incurred by this payment within the
|
|
|
|
// switch.
|
|
|
|
errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
|
|
|
|
OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(p.circuit),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now ask the switch to return the result of the payment when
|
|
|
|
// available.
|
|
|
|
resultChan, err := p.router.cfg.Payer.GetPaymentResult(
|
2020-02-07 12:31:27 +03:00
|
|
|
p.attempt.AttemptID, p.payment.PaymentHash, errorDecryptor,
|
2019-05-23 21:05:29 +03:00
|
|
|
)
|
|
|
|
switch {
|
|
|
|
|
2020-02-07 12:31:27 +03:00
|
|
|
// If this attempt ID is unknown to the Switch, it means it was
|
2019-05-23 21:05:29 +03:00
|
|
|
// never checkpointed and forwarded by the switch before a
|
|
|
|
// restart. In this case we can safely send a new payment
|
|
|
|
// attempt, and wait for its result to be available.
|
|
|
|
case err == htlcswitch.ErrPaymentIDNotFound:
|
|
|
|
log.Debugf("Payment ID %v for hash %x not found in "+
|
2020-02-07 12:31:27 +03:00
|
|
|
"the Switch, retrying.", p.attempt.AttemptID,
|
2019-05-23 21:05:29 +03:00
|
|
|
p.payment.PaymentHash)
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// Reset the attempt to indicate we want to make a new
|
|
|
|
// attempt.
|
|
|
|
p.attempt = nil
|
2019-05-23 21:05:29 +03:00
|
|
|
continue
|
|
|
|
|
|
|
|
// A critical, unexpected error was encountered.
|
|
|
|
case err != nil:
|
2020-02-07 12:31:27 +03:00
|
|
|
log.Errorf("Failed getting result for attemptID %d "+
|
|
|
|
"from switch: %v", p.attempt.AttemptID, err)
|
2019-05-23 21:05:29 +03:00
|
|
|
|
|
|
|
return [32]byte{}, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// The switch knows about this payment, we'll wait for a result
|
|
|
|
// to be available.
|
|
|
|
var (
|
|
|
|
result *htlcswitch.PaymentResult
|
|
|
|
ok bool
|
|
|
|
)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case result, ok = <-resultChan:
|
|
|
|
if !ok {
|
|
|
|
return [32]byte{}, nil, htlcswitch.ErrSwitchExiting
|
|
|
|
}
|
|
|
|
|
|
|
|
case <-p.router.quit:
|
|
|
|
return [32]byte{}, nil, ErrRouterShuttingDown
|
|
|
|
}
|
|
|
|
|
|
|
|
// In case of a payment failure, we use the error to decide
|
|
|
|
// whether we should retry.
|
|
|
|
if result.Error != nil {
|
|
|
|
log.Errorf("Attempt to send payment %x failed: %v",
|
|
|
|
p.payment.PaymentHash, result.Error)
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// We must inspect the error to know whether it was
|
|
|
|
// critical or not, to decide whether we should
|
|
|
|
// continue trying.
|
2019-05-23 21:05:29 +03:00
|
|
|
if err := p.handleSendError(result.Error); err != nil {
|
2019-05-23 21:05:29 +03:00
|
|
|
return [32]byte{}, nil, err
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// Error was handled successfully, reset the attempt to
|
|
|
|
// indicate we want to make a new attempt.
|
|
|
|
p.attempt = nil
|
2019-05-23 21:05:29 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// We successfully got a payment result back from the switch.
|
|
|
|
log.Debugf("Payment %x succeeded with pid=%v",
|
2020-02-07 12:31:27 +03:00
|
|
|
p.payment.PaymentHash, p.attempt.AttemptID)
|
2019-05-23 21:05:29 +03:00
|
|
|
|
2019-07-29 15:20:06 +03:00
|
|
|
// Report success to mission control.
|
|
|
|
err = p.router.cfg.MissionControl.ReportPaymentSuccess(
|
2020-02-07 12:31:27 +03:00
|
|
|
p.attempt.AttemptID, &p.attempt.Route,
|
2019-07-29 15:20:06 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error reporting payment success to mc: %v",
|
|
|
|
err)
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// In case of success we atomically store the db payment and
|
|
|
|
// move the payment to the success state.
|
|
|
|
err = p.router.cfg.Control.Success(p.payment.PaymentHash, result.Preimage)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Unable to succeed payment "+
|
|
|
|
"attempt: %v", err)
|
|
|
|
return [32]byte{}, nil, err
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// Terminal state, return the preimage and the route
|
|
|
|
// taken.
|
2019-05-23 21:05:29 +03:00
|
|
|
return result.Preimage, &p.attempt.Route, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-11-21 14:08:09 +03:00
|
|
|
// errorToPaymentFailure takes a path finding error and converts it into a
|
|
|
|
// payment-level failure.
|
|
|
|
func errorToPaymentFailure(err error) channeldb.FailureReason {
|
|
|
|
switch err {
|
2019-12-19 10:54:31 +03:00
|
|
|
case
|
|
|
|
errNoTlvPayload,
|
|
|
|
errNoPaymentAddr,
|
|
|
|
errNoPathFound,
|
2019-11-21 14:08:09 +03:00
|
|
|
errPrebuiltRouteTried:
|
|
|
|
|
|
|
|
return channeldb.FailureReasonNoRoute
|
2019-11-21 13:59:17 +03:00
|
|
|
|
|
|
|
case errInsufficientBalance:
|
|
|
|
return channeldb.FailureReasonInsufficientBalance
|
2019-11-21 14:08:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return channeldb.FailureReasonError
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// createNewPaymentAttempt creates and stores a new payment attempt to the
|
|
|
|
// database.
|
|
|
|
func (p *paymentLifecycle) createNewPaymentAttempt() (lnwire.ShortChannelID,
|
|
|
|
*lnwire.UpdateAddHTLC, error) {
|
|
|
|
|
2019-06-07 12:27:55 +03:00
|
|
|
// Before we attempt this next payment, we'll check to see if either
|
|
|
|
// we've gone past the payment attempt timeout, or the router is
|
|
|
|
// exiting. In either case, we'll stop this payment attempt short. If a
|
|
|
|
// timeout is not applicable, timeoutChan will be nil.
|
2019-05-23 21:05:29 +03:00
|
|
|
select {
|
|
|
|
case <-p.timeoutChan:
|
2019-05-23 21:05:29 +03:00
|
|
|
// Mark the payment as failed because of the
|
|
|
|
// timeout.
|
|
|
|
err := p.router.cfg.Control.Fail(
|
2019-05-23 21:05:30 +03:00
|
|
|
p.payment.PaymentHash, channeldb.FailureReasonTimeout,
|
2019-05-23 21:05:29 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return lnwire.ShortChannelID{}, nil, err
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
errStr := fmt.Sprintf("payment attempt not completed " +
|
|
|
|
"before timeout")
|
|
|
|
|
|
|
|
return lnwire.ShortChannelID{}, nil,
|
|
|
|
newErr(ErrPaymentAttemptTimeout, errStr)
|
|
|
|
|
|
|
|
case <-p.router.quit:
|
|
|
|
// The payment will be resumed from the current state
|
|
|
|
// after restart.
|
|
|
|
return lnwire.ShortChannelID{}, nil, ErrRouterShuttingDown
|
|
|
|
|
|
|
|
default:
|
|
|
|
// Fall through if we haven't hit our time limit, or
|
|
|
|
// are expiring.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new payment attempt from the given payment session.
|
2019-12-11 10:41:59 +03:00
|
|
|
rt, err := p.paySession.RequestRoute(
|
2019-05-23 21:05:29 +03:00
|
|
|
p.payment, uint32(p.currentHeight), p.finalCLTVDelta,
|
|
|
|
)
|
|
|
|
if err != nil {
|
2019-06-21 10:32:51 +03:00
|
|
|
log.Warnf("Failed to find route for payment %x: %v",
|
|
|
|
p.payment.PaymentHash, err)
|
|
|
|
|
2019-11-21 14:08:09 +03:00
|
|
|
// Convert error to payment-level failure.
|
|
|
|
failure := errorToPaymentFailure(err)
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// If we're unable to successfully make a payment using
|
|
|
|
// any of the routes we've found, then mark the payment
|
|
|
|
// as permanently failed.
|
|
|
|
saveErr := p.router.cfg.Control.Fail(
|
2019-11-21 14:08:09 +03:00
|
|
|
p.payment.PaymentHash, failure,
|
2019-05-23 21:05:29 +03:00
|
|
|
)
|
|
|
|
if saveErr != nil {
|
|
|
|
return lnwire.ShortChannelID{}, nil, saveErr
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// If there was an error already recorded for this
|
|
|
|
// payment, we'll return that.
|
|
|
|
if p.lastError != nil {
|
|
|
|
return lnwire.ShortChannelID{}, nil,
|
2019-05-23 22:17:16 +03:00
|
|
|
errNoRoute{lastError: p.lastError}
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
// Terminal state, return.
|
|
|
|
return lnwire.ShortChannelID{}, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a new key to be used for this attempt.
|
|
|
|
sessionKey, err := generateNewSessionKey()
|
|
|
|
if err != nil {
|
|
|
|
return lnwire.ShortChannelID{}, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate the raw encoded sphinx packet to be included along
|
|
|
|
// with the htlcAdd message that we send directly to the
|
|
|
|
// switch.
|
|
|
|
onionBlob, c, err := generateSphinxPacket(
|
2019-12-11 10:41:59 +03:00
|
|
|
rt, p.payment.PaymentHash[:], sessionKey,
|
2019-05-23 21:05:29 +03:00
|
|
|
)
|
2019-12-11 10:56:41 +03:00
|
|
|
|
|
|
|
// With SendToRoute, it can happen that the route exceeds protocol
|
|
|
|
// constraints. Mark the payment as failed with an internal error.
|
|
|
|
if err == route.ErrMaxRouteHopsExceeded ||
|
|
|
|
err == sphinx.ErrMaxRoutingInfoSizeExceeded {
|
|
|
|
|
|
|
|
log.Debugf("Invalid route provided for payment %x: %v",
|
|
|
|
p.payment.PaymentHash, err)
|
|
|
|
|
|
|
|
controlErr := p.router.cfg.Control.Fail(
|
|
|
|
p.payment.PaymentHash, channeldb.FailureReasonError,
|
|
|
|
)
|
|
|
|
if controlErr != nil {
|
|
|
|
return lnwire.ShortChannelID{}, nil, controlErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// In any case, don't continue if there is an error.
|
2019-05-23 21:05:29 +03:00
|
|
|
if err != nil {
|
|
|
|
return lnwire.ShortChannelID{}, nil, err
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// Update our cached circuit with the newly generated
|
|
|
|
// one.
|
|
|
|
p.circuit = c
|
|
|
|
|
|
|
|
// Craft an HTLC packet to send to the layer 2 switch. The
|
|
|
|
// metadata within this packet will be used to route the
|
|
|
|
// payment through the network, starting with the first-hop.
|
|
|
|
htlcAdd := &lnwire.UpdateAddHTLC{
|
2019-12-11 10:41:59 +03:00
|
|
|
Amount: rt.TotalAmount,
|
|
|
|
Expiry: rt.TotalTimeLock,
|
2019-05-23 21:05:29 +03:00
|
|
|
PaymentHash: p.payment.PaymentHash,
|
|
|
|
}
|
|
|
|
copy(htlcAdd.OnionBlob[:], onionBlob)
|
|
|
|
|
|
|
|
// Attempt to send this payment through the network to complete
|
|
|
|
// the payment. If this attempt fails, then we'll continue on
|
|
|
|
// to the next available route.
|
|
|
|
firstHop := lnwire.NewShortChanIDFromInt(
|
2019-12-11 10:41:59 +03:00
|
|
|
rt.Hops[0].ChannelID,
|
2019-05-23 21:05:29 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// We generate a new, unique payment ID that we will use for
|
|
|
|
// this HTLC.
|
2020-02-07 12:31:27 +03:00
|
|
|
attemptID, err := p.router.cfg.NextPaymentID()
|
2019-05-23 21:05:29 +03:00
|
|
|
if err != nil {
|
|
|
|
return lnwire.ShortChannelID{}, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We now have all the information needed to populate
|
|
|
|
// the current attempt information.
|
2020-02-07 12:31:27 +03:00
|
|
|
p.attempt = &channeldb.HTLCAttemptInfo{
|
|
|
|
AttemptID: attemptID,
|
2019-05-23 21:05:29 +03:00
|
|
|
SessionKey: sessionKey,
|
2019-12-11 10:41:59 +03:00
|
|
|
Route: *rt,
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// Before sending this HTLC to the switch, we checkpoint the
|
2020-02-07 12:31:27 +03:00
|
|
|
// fresh attemptID and route to the DB. This lets us know on
|
2019-05-23 21:05:29 +03:00
|
|
|
// startup the ID of the payment that we attempted to send,
|
|
|
|
// such that we can query the Switch for its whereabouts. The
|
|
|
|
// route is needed to handle the result when it eventually
|
|
|
|
// comes back.
|
|
|
|
err = p.router.cfg.Control.RegisterAttempt(p.payment.PaymentHash, p.attempt)
|
|
|
|
if err != nil {
|
|
|
|
return lnwire.ShortChannelID{}, nil, err
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
return firstHop, htlcAdd, nil
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
2019-05-23 21:05:29 +03:00
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// sendPaymentAttempt attempts to send the current attempt to the switch.
|
|
|
|
func (p *paymentLifecycle) sendPaymentAttempt(firstHop lnwire.ShortChannelID,
|
|
|
|
htlcAdd *lnwire.UpdateAddHTLC) error {
|
|
|
|
|
|
|
|
log.Tracef("Attempting to send payment %x (pid=%v), "+
|
2020-02-07 12:31:27 +03:00
|
|
|
"using route: %v", p.payment.PaymentHash, p.attempt.AttemptID,
|
2019-05-23 21:05:29 +03:00
|
|
|
newLogClosure(func() string {
|
|
|
|
return spew.Sdump(p.attempt.Route)
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
|
|
|
|
// Send it to the Switch. When this method returns we assume
|
|
|
|
// the Switch successfully has persisted the payment attempt,
|
|
|
|
// such that we can resume waiting for the result after a
|
|
|
|
// restart.
|
|
|
|
err := p.router.cfg.Payer.SendHTLC(
|
2020-02-07 12:31:27 +03:00
|
|
|
firstHop, p.attempt.AttemptID, htlcAdd,
|
2019-05-23 21:05:29 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed sending attempt %d for payment "+
|
2020-02-07 12:31:27 +03:00
|
|
|
"%x to switch: %v", p.attempt.AttemptID,
|
2019-05-23 21:05:29 +03:00
|
|
|
p.payment.PaymentHash, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-11-11 14:27:24 +03:00
|
|
|
log.Debugf("Payment %x (pid=%v) successfully sent to switch, route: %v",
|
2020-02-07 12:31:27 +03:00
|
|
|
p.payment.PaymentHash, p.attempt.AttemptID, &p.attempt.Route)
|
2019-05-23 21:05:29 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2019-05-23 21:05:29 +03:00
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// handleSendError inspects the given error from the Switch and determines
|
|
|
|
// whether we should make another payment attempt.
|
|
|
|
func (p *paymentLifecycle) handleSendError(sendErr error) error {
|
2019-05-23 22:17:16 +03:00
|
|
|
|
2019-08-05 13:13:58 +03:00
|
|
|
reason := p.router.processSendError(
|
2020-02-07 12:31:27 +03:00
|
|
|
p.attempt.AttemptID, &p.attempt.Route, sendErr,
|
2019-06-19 12:12:10 +03:00
|
|
|
)
|
2019-08-05 13:13:58 +03:00
|
|
|
if reason == nil {
|
2019-06-19 12:12:10 +03:00
|
|
|
// Save the forwarding error so it can be returned if
|
|
|
|
// this turns out to be the last attempt.
|
|
|
|
p.lastError = sendErr
|
2019-05-23 21:05:29 +03:00
|
|
|
|
2019-06-19 12:12:10 +03:00
|
|
|
return nil
|
2019-06-04 12:22:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("Payment %x failed: final_outcome=%v, raw_err=%v",
|
2019-08-05 13:13:58 +03:00
|
|
|
p.payment.PaymentHash, *reason, sendErr)
|
2019-05-23 21:05:29 +03:00
|
|
|
|
2019-06-04 12:22:23 +03:00
|
|
|
// Mark the payment failed with no route.
|
|
|
|
//
|
|
|
|
// TODO(halseth): make payment codes for the actual reason we don't
|
|
|
|
// continue path finding.
|
|
|
|
err := p.router.cfg.Control.Fail(
|
2019-08-05 13:13:58 +03:00
|
|
|
p.payment.PaymentHash, *reason,
|
2019-06-04 12:22:23 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
2019-06-04 12:22:23 +03:00
|
|
|
// Terminal state, return the error we encountered.
|
|
|
|
return sendErr
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|