routing: extract payment flow into method on paymentLifecycle

This encapsulates all state needed to resume a payment from any point of
the payment flow, and that must be shared between the different stages
of the execution. This is done to prepare for breaking the send loop
into smaller parts, and being able to resume the payment from any point
from persistent state.
This commit is contained in:
Johan T. Halseth
2019-05-23 20:05:29 +02:00
parent ae7bf2cb7b
commit 83bfaa4fb4
2 changed files with 250 additions and 158 deletions

View File

@@ -1618,166 +1618,21 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment,
timeoutChan := time.After(payAttemptTimeout)
paymentHash := payment.PaymentHash
// We'll continue until either our payment succeeds, or we encounter a
// critical error during path finding.
var lastError error
for {
// 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.
select {
case <-timeoutChan:
errStr := fmt.Sprintf("payment attempt not completed "+
"before timeout of %v", payAttemptTimeout)
return [32]byte{}, nil, newErr(
ErrPaymentAttemptTimeout, errStr,
)
case <-r.quit:
return [32]byte{}, nil, ErrRouterShuttingDown
default:
// Fall through if we haven't hit our time limit, or
// are expiring.
}
route, err := paySession.RequestRoute(
payment, uint32(currentHeight), finalCLTVDelta,
)
if err != nil {
// If we're unable to successfully make a payment using
// any of the routes we've found, then return an error.
if lastError != nil {
return [32]byte{}, nil, fmt.Errorf("unable to "+
"route payment to destination: %v",
lastError)
}
return [32]byte{}, nil, err
}
log.Tracef("Attempting to send payment %x, using route: %v",
paymentHash, newLogClosure(func() string {
return spew.Sdump(route)
}),
)
// Generate a new key to be used for this attempt.
sessionKey, err := generateNewSessionKey()
if err != nil {
return [32]byte{}, nil, err
}
// Generate the raw encoded sphinx packet to be included along
// with the htlcAdd message that we send directly to the
// switch.
onionBlob, circuit, err := generateSphinxPacket(
route, paymentHash[:], sessionKey,
)
if err != nil {
return [32]byte{}, nil, err
}
// 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: 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 := r.cfg.NextPaymentID()
if err != nil {
return [32]byte{}, nil, err
}
err = r.cfg.Payer.SendHTLC(
firstHop, paymentID, htlcAdd,
)
if err != nil {
log.Errorf("Failed sending attempt %d for payment "+
"%x to switch: %v", paymentID, paymentHash, err)
// We must inspect the error to know whether it was
// critical or not, to decide whether we should
// continue trying.
finalOutcome := r.processSendError(
paySession, route, err,
)
if finalOutcome {
return [32]byte{}, nil, err
}
lastError = err
continue
}
// 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(circuit),
}
// Now ask the switch to return the result of the payment when
// available.
resultChan, err := r.cfg.Payer.GetPaymentResult(
paymentID, errorDecryptor,
)
if err != nil {
log.Errorf("Failed getting result for paymentID %d "+
"from switch: %v", paymentID, err)
return [32]byte{}, nil, err
}
var (
result *htlcswitch.PaymentResult
ok bool
)
select {
case result, ok = <-resultChan:
if !ok {
return [32]byte{}, nil, htlcswitch.ErrSwitchExiting
}
case <-r.quit:
return [32]byte{}, nil, ErrRouterShuttingDown
}
if result.Error != nil {
log.Errorf("Attempt to send payment %x failed: %v",
paymentHash, result.Error)
finalOutcome := r.processSendError(
paySession, route, result.Error,
)
if finalOutcome {
return [32]byte{}, nil, result.Error
}
lastError = result.Error
continue
}
return result.Preimage, route, nil
// Now set up a paymentLifecycle struct with these params, such that we
// can resume the payment from the current state.
p := &paymentLifecycle{
router: r,
payment: payment,
paySession: paySession,
timeoutChan: timeoutChan,
currentHeight: currentHeight,
finalCLTVDelta: finalCLTVDelta,
circuit: nil,
lastError: nil,
}
return p.resumePayment()
}
// processSendError analyzes the error for the payment attempt received from the