diff --git a/routing/router.go b/routing/router.go index dc23fd7ff..d9d1789fb 100644 --- a/routing/router.go +++ b/routing/router.go @@ -6,6 +6,7 @@ import ( "sort" "sync" "sync/atomic" + "time" "github.com/boltdb/bolt" "github.com/davecgh/go-spew/spew" @@ -1007,8 +1008,8 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *Route ) var ( - sendError error preImage [32]byte + sendError error ) // First, we'll kick off the payment attempt by attempting to query the @@ -1022,7 +1023,6 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *Route // target payment using the multi-hop route. We'll try each route // serially until either once succeeds, or we've exhausted our set of // available paths. -routes: for _, route := range routes { log.Tracef("Attempting to send payment %x, using route: %v", payment.PaymentHash, newLogClosure(func() string { @@ -1056,50 +1056,146 @@ routes: preImage, sendError = r.cfg.SendToSwitch(firstHop, htlcAdd, circuit) if sendError != nil { + // An error occurred when attempting to send the + // payment, depending on the error type, we'll either + // continue to send using alternative routes, or simply + // terminate this attempt. log.Errorf("Attempt to send payment %x failed: %v", payment.PaymentHash, sendError) - switch sendError.(type) { - case *lnwire.FailTemporaryNodeFailure: + switch onionErr := sendError.(type) { + // If the end destination didn't know they payment + // hash, then we'll terminate immediately. + case *lnwire.FailUnknownPaymentHash: + return preImage, nil, sendError + + // If we sent the wrong amount to the destination, then + // we'll exit early. + case *lnwire.FailIncorrectPaymentAmount: + return preImage, nil, sendError + + // If the time-lock that was extended to the final node + // was incorrect, then we can't proceed. + case *lnwire.FailFinalIncorrectCltvExpiry: + return preImage, nil, sendError + + // If we crafted an invalid onion payload for the final + // node, then we'll exit early. + case *lnwire.FailFinalIncorrectHtlcAmount: + return preImage, nil, sendError + + // Similarly, if the HTLC expiry that we extended to + // the final hop expires too soon, then will fail the + // payment. + // + // TODO(roasbeef): can happen to to race condition, try + // again with recent block height + case *lnwire.FailFinalExpiryTooSoon: + return preImage, nil, sendError + + // If we erroneously attempted to cross a chain border, + // then we'll cancel the payment. + case *lnwire.FailInvalidRealm: + return preImage, nil, sendError + + // If we get a notice that the expiry was too soon for + // an intermediate node, then we'll exit early as the + // expected block height as shifted from underneath us. + case *lnwire.FailExpiryTooSoon: + update := onionErr.Update + if err := r.applyChannelUpdate(&update); err != nil { + return preImage, nil, err + } + return preImage, nil, sendError + + // If we hit an instance of onion payload corruption or + // an invalid version, then we'll exit early as this + // shouldn't happen in the typical case. + case *lnwire.FailInvalidOnionVersion: + return preImage, nil, sendError + case *lnwire.FailInvalidOnionHmac: + return preImage, nil, sendError + case *lnwire.FailInvalidOnionKey: + return preImage, nil, sendError + + // If the onion error includes a channel update, and + // isn't necessarily fatal, then we'll apply the update + // an continue with the rest of the routes. + // + // TODO(roasbeef): should re-query for routes with new updates + case *lnwire.FailAmountBelowMinimum: + update := onionErr.Update + if err := r.applyChannelUpdate(&update); err != nil { + return preImage, nil, err + } continue - case *lnwire.FailPermanentNodeFailure: + case *lnwire.FailFeeInsufficient: + update := onionErr.Update + if err := r.applyChannelUpdate(&update); err != nil { + return preImage, nil, err + } continue + case *lnwire.FailIncorrectCltvExpiry: + update := onionErr.Update + if err := r.applyChannelUpdate(&update); err != nil { + return preImage, nil, err + } + continue + case *lnwire.FailChannelDisabled: + update := onionErr.Update + if err := r.applyChannelUpdate(&update); err != nil { + return preImage, nil, err + } + continue + case *lnwire.FailTemporaryChannelFailure: + // TODO(roasbeef): remove channel from timeout period + update := onionErr.Update + if err := r.applyChannelUpdate(update); err != nil { + return preImage, nil, err + } + continue + + // If the send fail due to a node not having the + // required features, then we'll note this error and + // continue + // TODO(roasbeef): remove node from path case *lnwire.FailRequiredNodeFeatureMissing: continue - case *lnwire.FailPermanentChannelFailure: - continue + + // If the send fail due to a node not having the + // required features, then we'll note this error and + // continue + // + // TODO(roasbeef): remove channel from path case *lnwire.FailRequiredChannelFeatureMissing: - break routes + continue + + // If the next hop in the route wasn't known or + // offline, we'll note this and continue with the rest + // of the routes. + // + // TODO(roasbeef): remove unknown vertex case *lnwire.FailUnknownNextPeer: continue - case *lnwire.FailUnknownPaymentHash: - break routes - case *lnwire.FailIncorrectPaymentAmount: - break routes - case *lnwire.FailFinalExpiryTooSoon: - break routes - case *lnwire.FailInvalidOnionVersion: - break routes - case *lnwire.FailInvalidOnionHmac: - break routes - case *lnwire.FailInvalidOnionKey: - break routes - case *lnwire.FailTemporaryChannelFailure: + + // If the node wasn't able to forward for which ever + // reason, then we'll note this and continue with the + // routes. + case *lnwire.FailTemporaryNodeFailure: continue - case *lnwire.FailAmountBelowMinimum: - break routes - case *lnwire.FailFeeInsufficient: - break routes - case *lnwire.FailExpiryTooSoon: - break routes - case *lnwire.FailChannelDisabled: + + // If we get a permanent channel or node failure, then + // we'll note this (exclude the vertex/edge), and + // continue with the rest of the routes. + case *lnwire.FailPermanentChannelFailure: + // TODO(roasbeef): remove channel from path continue - case *lnwire.FailFinalIncorrectCltvExpiry: - break routes - case *lnwire.FailFinalIncorrectHtlcAmount: - break routes - case *lnwire.FailInvalidRealm: - break routes + case *lnwire.FailPermanentNodeFailure: + // TODO(rosabeef): remove node from path + continue + + default: + return preImage, nil, sendError } } @@ -1108,7 +1204,34 @@ routes: // If we're unable to successfully make a payment using any of the // routes we've found, then return an error. - return [32]byte{}, nil, sendError + return [32]byte{}, nil, fmt.Errorf("unable to route payment to "+ + "destination: %v", sendError) +} + +// applyChannelUpdate applies a channel update directly to the database, +// skipping preliminary validation. +func (r *ChannelRouter) applyChannelUpdate(msg *lnwire.ChannelUpdate) error { + // If we get passed a nil channel update (as it's optional with some + // onion errors), then we'll exit early with a nil error. + if msg == nil { + return nil + } + + err := r.UpdateEdge(&channeldb.ChannelEdgePolicy{ + Signature: msg.Signature, + ChannelID: msg.ShortChannelID.ToUint64(), + LastUpdate: time.Unix(int64(msg.Timestamp), 0), + Flags: msg.Flags, + TimeLockDelta: msg.TimeLockDelta, + MinHTLC: msg.HtlcMinimumMsat, + FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee), + FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate), + }) + if err != nil { + return fmt.Errorf("Unable to apply channel update: %v", err) + } + + return nil } // AddNode is used to add information about a node to the router database. If