package routing import ( "fmt" "time" "github.com/davecgh/go-spew/spew" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) // 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. lastError error } // 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) } // 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 paySession PaymentSession timeoutChan <-chan time.Time currentHeight int32 finalCLTVDelta uint16 attempt *channeldb.PaymentAttemptInfo circuit *sphinx.Circuit lastError error } // 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 { // 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 } // 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 // circuit. _, c, err := generateSphinxPacket( &p.attempt.Route, p.payment.PaymentHash[:], p.attempt.SessionKey, ) if err != nil { return [32]byte{}, nil, err } p.circuit = c } // 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( p.attempt.PaymentID, p.payment.PaymentHash, errorDecryptor, ) switch { // If this payment ID is unknown to the Switch, it means it was // 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 "+ "the Switch, retrying.", p.attempt.PaymentID, p.payment.PaymentHash) // Reset the attempt to indicate we want to make a new // attempt. p.attempt = nil continue // A critical, unexpected error was encountered. case err != nil: log.Errorf("Failed getting result for paymentID %d "+ "from switch: %v", p.attempt.PaymentID, err) 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) // We must inspect the error to know whether it was // critical or not, to decide whether we should // continue trying. if err := p.handleSendError(result.Error); 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 } // We successfully got a payment result back from the switch. log.Debugf("Payment %x succeeded with pid=%v", p.payment.PaymentHash, p.attempt.PaymentID) // 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 } // Terminal state, return the preimage and the route // taken. return result.Preimage, &p.attempt.Route, nil } } // createNewPaymentAttempt creates and stores a new payment attempt to the // database. func (p *paymentLifecycle) createNewPaymentAttempt() (lnwire.ShortChannelID, *lnwire.UpdateAddHTLC, error) { // 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. select { case <-p.timeoutChan: // Mark the payment as failed because of the // timeout. err := p.router.cfg.Control.Fail( p.payment.PaymentHash, channeldb.FailureReasonTimeout, ) if err != nil { return lnwire.ShortChannelID{}, nil, err } 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. route, err := p.paySession.RequestRoute( p.payment, uint32(p.currentHeight), p.finalCLTVDelta, ) if err != nil { log.Warnf("Failed to find route for payment %x: %v", p.payment.PaymentHash, err) // 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( p.payment.PaymentHash, channeldb.FailureReasonNoRoute, ) if saveErr != nil { return lnwire.ShortChannelID{}, nil, saveErr } // If there was an error already recorded for this // payment, we'll return that. if p.lastError != nil { return lnwire.ShortChannelID{}, nil, errNoRoute{lastError: p.lastError} } // 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( route, p.payment.PaymentHash[:], sessionKey, ) if err != nil { return lnwire.ShortChannelID{}, nil, err } // 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{ Amount: route.TotalAmount, Expiry: route.TotalTimeLock, 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( route.Hops[0].ChannelID, ) // We generate a new, unique payment ID that we will use for // this HTLC. paymentID, err := p.router.cfg.NextPaymentID() if err != nil { return lnwire.ShortChannelID{}, nil, err } // We now have all the information needed to populate // the current attempt information. p.attempt = &channeldb.PaymentAttemptInfo{ PaymentID: paymentID, SessionKey: sessionKey, Route: *route, } // Before sending this HTLC to the switch, we checkpoint the // fresh paymentID and route to the DB. This lets us know on // 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 } return firstHop, htlcAdd, nil } // 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), "+ "using route: %v", p.payment.PaymentHash, p.attempt.PaymentID, 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( firstHop, p.attempt.PaymentID, htlcAdd, ) if err != nil { log.Errorf("Failed sending attempt %d for payment "+ "%x to switch: %v", p.attempt.PaymentID, p.payment.PaymentHash, err) return err } log.Debugf("Payment %x (pid=%v) successfully sent to switch", p.payment.PaymentHash, p.attempt.PaymentID) return nil } // handleSendError inspects the given error from the Switch and determines // whether we should make another payment attempt. func (p *paymentLifecycle) handleSendError(sendErr error) error { reason := p.router.processSendError( p.attempt.PaymentID, &p.attempt.Route, sendErr, ) if reason == nil { // Save the forwarding error so it can be returned if // this turns out to be the last attempt. p.lastError = sendErr return nil } log.Debugf("Payment %x failed: final_outcome=%v, raw_err=%v", p.payment.PaymentHash, *reason, sendErr) // 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( p.payment.PaymentHash, *reason, ) if err != nil { return err } // Terminal state, return the error we encountered. return sendErr }