github.com/decred/dcrlnd@v0.7.6/routing/result_interpretation.go (about)

     1  package routing
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/decred/dcrlnd/channeldb"
     7  	"github.com/decred/dcrlnd/lnwire"
     8  	"github.com/decred/dcrlnd/routing/route"
     9  )
    10  
    11  // Instantiate variables to allow taking a reference from the failure reason.
    12  var (
    13  	reasonError            = channeldb.FailureReasonError
    14  	reasonIncorrectDetails = channeldb.FailureReasonPaymentDetails
    15  )
    16  
    17  // pairResult contains the result of the interpretation of a payment attempt for
    18  // a specific node pair.
    19  type pairResult struct {
    20  	// amt is the amount that was forwarded for this pair. Can be set to
    21  	// zero for failures that are amount independent.
    22  	amt lnwire.MilliAtom
    23  
    24  	// success indicates whether the payment attempt was successful through
    25  	// this pair.
    26  	success bool
    27  }
    28  
    29  // failPairResult creates a new result struct for a failure.
    30  func failPairResult(minPenalizeAmt lnwire.MilliAtom) pairResult {
    31  	return pairResult{
    32  		amt: minPenalizeAmt,
    33  	}
    34  }
    35  
    36  // newSuccessPairResult creates a new result struct for a success.
    37  func successPairResult(successAmt lnwire.MilliAtom) pairResult {
    38  	return pairResult{
    39  		success: true,
    40  		amt:     successAmt,
    41  	}
    42  }
    43  
    44  // String returns the human-readable representation of a pair result.
    45  func (p pairResult) String() string {
    46  	var resultType string
    47  	if p.success {
    48  		resultType = "success"
    49  	} else {
    50  		resultType = "failed"
    51  	}
    52  
    53  	return fmt.Sprintf("%v (amt=%v)", resultType, p.amt)
    54  }
    55  
    56  // interpretedResult contains the result of the interpretation of a payment
    57  // attempt.
    58  type interpretedResult struct {
    59  	// nodeFailure points to a node pubkey if all channels of that node are
    60  	// responsible for the result.
    61  	nodeFailure *route.Vertex
    62  
    63  	// pairResults contains a map of node pairs for which we have a result.
    64  	pairResults map[DirectedNodePair]pairResult
    65  
    66  	// finalFailureReason is set to a non-nil value if it makes no more
    67  	// sense to start another payment attempt. It will contain the reason
    68  	// why.
    69  	finalFailureReason *channeldb.FailureReason
    70  
    71  	// policyFailure is set to a node pair if there is a policy failure on
    72  	// that connection. This is used to control the second chance logic for
    73  	// policy failures.
    74  	policyFailure *DirectedNodePair
    75  }
    76  
    77  // interpretResult interprets a payment outcome and returns an object that
    78  // contains information required to update mission control.
    79  func interpretResult(rt *route.Route, success bool, failureSrcIdx *int,
    80  	failure lnwire.FailureMessage) *interpretedResult {
    81  
    82  	i := &interpretedResult{
    83  		pairResults: make(map[DirectedNodePair]pairResult),
    84  	}
    85  
    86  	if success {
    87  		i.processSuccess(rt)
    88  	} else {
    89  		i.processFail(rt, failureSrcIdx, failure)
    90  	}
    91  	return i
    92  }
    93  
    94  // processSuccess processes a successful payment attempt.
    95  func (i *interpretedResult) processSuccess(route *route.Route) {
    96  	// For successes, all nodes must have acted in the right way. Therefore
    97  	// we mark all of them with a success result.
    98  	i.successPairRange(route, 0, len(route.Hops)-1)
    99  }
   100  
   101  // processFail processes a failed payment attempt.
   102  func (i *interpretedResult) processFail(
   103  	rt *route.Route, errSourceIdx *int,
   104  	failure lnwire.FailureMessage) {
   105  
   106  	if errSourceIdx == nil {
   107  		i.processPaymentOutcomeUnknown(rt)
   108  		return
   109  	}
   110  
   111  	switch *errSourceIdx {
   112  
   113  	// We are the source of the failure.
   114  	case 0:
   115  		i.processPaymentOutcomeSelf(rt, failure)
   116  
   117  	// A failure from the final hop was received.
   118  	case len(rt.Hops):
   119  		i.processPaymentOutcomeFinal(
   120  			rt, failure,
   121  		)
   122  
   123  	// An intermediate hop failed. Interpret the outcome, update reputation
   124  	// and try again.
   125  	default:
   126  		i.processPaymentOutcomeIntermediate(
   127  			rt, *errSourceIdx, failure,
   128  		)
   129  	}
   130  }
   131  
   132  // processPaymentOutcomeSelf handles failures sent by ourselves.
   133  func (i *interpretedResult) processPaymentOutcomeSelf(
   134  	rt *route.Route, failure lnwire.FailureMessage) {
   135  
   136  	switch failure.(type) {
   137  
   138  	// We receive a malformed htlc failure from our peer. We trust ourselves
   139  	// to send the correct htlc, so our peer must be at fault.
   140  	case *lnwire.FailInvalidOnionVersion,
   141  		*lnwire.FailInvalidOnionHmac,
   142  		*lnwire.FailInvalidOnionKey:
   143  
   144  		i.failNode(rt, 1)
   145  
   146  		// If this was a payment to a direct peer, we can stop trying.
   147  		if len(rt.Hops) == 1 {
   148  			i.finalFailureReason = &reasonError
   149  		}
   150  
   151  	// Any other failure originating from ourselves should be temporary and
   152  	// caused by changing conditions between path finding and execution of
   153  	// the payment. We just retry and trust that the information locally
   154  	// available in the link has been updated.
   155  	default:
   156  		log.Warnf("Routing failure for local channel %v occurred",
   157  			rt.Hops[0].ChannelID)
   158  	}
   159  }
   160  
   161  // processPaymentOutcomeFinal handles failures sent by the final hop.
   162  func (i *interpretedResult) processPaymentOutcomeFinal(
   163  	route *route.Route, failure lnwire.FailureMessage) {
   164  
   165  	n := len(route.Hops)
   166  
   167  	// If a failure from the final node is received, we will fail the
   168  	// payment in almost all cases. Only when the penultimate node sends an
   169  	// incorrect htlc, we want to retry via another route. Invalid onion
   170  	// failures are not expected, because the final node wouldn't be able to
   171  	// encrypt that failure.
   172  	switch failure.(type) {
   173  
   174  	// Expiry or amount of the HTLC doesn't match the onion, try another
   175  	// route.
   176  	case *lnwire.FailFinalIncorrectCltvExpiry,
   177  		*lnwire.FailFinalIncorrectHtlcAmount:
   178  
   179  		// We trust ourselves. If this is a direct payment, we penalize
   180  		// the final node and fail the payment.
   181  		if n == 1 {
   182  			i.failNode(route, n)
   183  			i.finalFailureReason = &reasonError
   184  
   185  			return
   186  		}
   187  
   188  		// Otherwise penalize the last pair of the route and retry.
   189  		// Either the final node is at fault, or it gets sent a bad htlc
   190  		// from its predecessor.
   191  		i.failPair(route, n-1)
   192  
   193  		// The other hops relayed corectly, so assign those pairs a
   194  		// success result. At this point, n >= 2.
   195  		i.successPairRange(route, 0, n-2)
   196  
   197  	// We are using wrong payment hash or amount, fail the payment.
   198  	case *lnwire.FailIncorrectPaymentAmount,
   199  		*lnwire.FailIncorrectDetails:
   200  
   201  		// Assign all pairs a success result, as the payment reached the
   202  		// destination correctly.
   203  		i.successPairRange(route, 0, n-1)
   204  
   205  		i.finalFailureReason = &reasonIncorrectDetails
   206  
   207  	// The HTLC that was extended to the final hop expires too soon. Fail
   208  	// the payment, because we may be using the wrong final cltv delta.
   209  	case *lnwire.FailFinalExpiryTooSoon:
   210  		// TODO(roasbeef): can happen to to race condition, try again
   211  		// with recent block height
   212  
   213  		// TODO(joostjager): can also happen because a node delayed
   214  		// deliberately. What to penalize?
   215  		i.finalFailureReason = &reasonIncorrectDetails
   216  
   217  	case *lnwire.FailMPPTimeout:
   218  		// Assign all pairs a success result, as the payment reached the
   219  		// destination correctly. Continue the payment process.
   220  		i.successPairRange(route, 0, n-1)
   221  
   222  	default:
   223  		// All other errors are considered terminal if coming from the
   224  		// final hop. They indicate that something is wrong at the
   225  		// recipient, so we do apply a penalty.
   226  		i.failNode(route, n)
   227  
   228  		// Other channels in the route forwarded correctly.
   229  		if n >= 2 {
   230  			i.successPairRange(route, 0, n-2)
   231  		}
   232  
   233  		i.finalFailureReason = &reasonError
   234  	}
   235  }
   236  
   237  // processPaymentOutcomeIntermediate handles failures sent by an intermediate
   238  // hop.
   239  func (i *interpretedResult) processPaymentOutcomeIntermediate(
   240  	route *route.Route, errorSourceIdx int,
   241  	failure lnwire.FailureMessage) {
   242  
   243  	reportOutgoing := func() {
   244  		i.failPair(
   245  			route, errorSourceIdx,
   246  		)
   247  	}
   248  
   249  	reportOutgoingBalance := func() {
   250  		i.failPairBalance(
   251  			route, errorSourceIdx,
   252  		)
   253  
   254  		// All nodes up to the failing pair must have forwarded
   255  		// successfully.
   256  		i.successPairRange(route, 0, errorSourceIdx-1)
   257  	}
   258  
   259  	reportIncoming := func() {
   260  		// We trust ourselves. If the error comes from the first hop, we
   261  		// can penalize the whole node. In that case there is no
   262  		// uncertainty as to which node to blame.
   263  		if errorSourceIdx == 1 {
   264  			i.failNode(route, errorSourceIdx)
   265  			return
   266  		}
   267  
   268  		// Otherwise report the incoming pair.
   269  		i.failPair(
   270  			route, errorSourceIdx-1,
   271  		)
   272  
   273  		// All nodes up to the failing pair must have forwarded
   274  		// successfully.
   275  		if errorSourceIdx > 1 {
   276  			i.successPairRange(route, 0, errorSourceIdx-2)
   277  		}
   278  	}
   279  
   280  	reportNode := func() {
   281  		// Fail only the node that reported the failure.
   282  		i.failNode(route, errorSourceIdx)
   283  
   284  		// Other preceding channels in the route forwarded correctly.
   285  		if errorSourceIdx > 1 {
   286  			i.successPairRange(route, 0, errorSourceIdx-2)
   287  		}
   288  	}
   289  
   290  	reportAll := func() {
   291  		// We trust ourselves. If the error comes from the first hop, we
   292  		// can penalize the whole node. In that case there is no
   293  		// uncertainty as to which node to blame.
   294  		if errorSourceIdx == 1 {
   295  			i.failNode(route, errorSourceIdx)
   296  			return
   297  		}
   298  
   299  		// Otherwise penalize all pairs up to the error source. This
   300  		// includes our own outgoing connection.
   301  		i.failPairRange(
   302  			route, 0, errorSourceIdx-1,
   303  		)
   304  	}
   305  
   306  	switch failure.(type) {
   307  
   308  	// If a node reports onion payload corruption or an invalid version,
   309  	// that node may be responsible, but it could also be that it is just
   310  	// relaying a malformed htlc failure from it successor. By reporting the
   311  	// outgoing channel set, we will surely hit the responsible node. At
   312  	// this point, it is not possible that the node's predecessor corrupted
   313  	// the onion blob. If the predecessor would have corrupted the payload,
   314  	// the error source wouldn't have been able to encrypt this failure
   315  	// message for us.
   316  	case *lnwire.FailInvalidOnionVersion,
   317  		*lnwire.FailInvalidOnionHmac,
   318  		*lnwire.FailInvalidOnionKey:
   319  
   320  		reportOutgoing()
   321  
   322  	// If InvalidOnionPayload is received, we penalize only the reporting
   323  	// node. We know the preceding hop didn't corrupt the onion, since the
   324  	// reporting node is able to send the failure. We assume that we
   325  	// constructed a valid onion payload and that the failure is most likely
   326  	// an unknown required type or a bug in their implementation.
   327  	case *lnwire.InvalidOnionPayload:
   328  		reportNode()
   329  
   330  	// If the next hop in the route wasn't known or offline, we'll only
   331  	// penalize the channel set which we attempted to route over. This is
   332  	// conservative, and it can handle faulty channels between nodes
   333  	// properly. Additionally, this guards against routing nodes returning
   334  	// errors in order to attempt to black list another node.
   335  	case *lnwire.FailUnknownNextPeer:
   336  		reportOutgoing()
   337  
   338  	// Some implementations use this error when the next hop is offline, so we
   339  	// do the same as FailUnknownNextPeer and also process the channel update.
   340  	case *lnwire.FailChannelDisabled:
   341  
   342  		// Set the node pair for which a channel update may be out of
   343  		// date. The second chance logic uses the policyFailure field.
   344  		i.policyFailure = &DirectedNodePair{
   345  			From: route.Hops[errorSourceIdx-1].PubKeyBytes,
   346  			To:   route.Hops[errorSourceIdx].PubKeyBytes,
   347  		}
   348  
   349  		reportOutgoing()
   350  
   351  		// All nodes up to the failing pair must have forwarded
   352  		// successfully.
   353  		i.successPairRange(route, 0, errorSourceIdx-1)
   354  
   355  	// If we get a permanent channel, we'll prune the channel set in both
   356  	// directions and continue with the rest of the routes.
   357  	case *lnwire.FailPermanentChannelFailure:
   358  		reportOutgoing()
   359  
   360  	// When an HTLC parameter is incorrect, the node sending the error may
   361  	// be doing something wrong. But it could also be that its predecessor
   362  	// is intentionally modifying the htlc parameters that we instructed it
   363  	// via the hop payload. Therefore we penalize the incoming node pair. A
   364  	// third cause of this error may be that we have an out of date channel
   365  	// update. This is handled by the second chance logic up in mission
   366  	// control.
   367  	case *lnwire.FailAmountBelowMinimum,
   368  		*lnwire.FailFeeInsufficient,
   369  		*lnwire.FailIncorrectCltvExpiry:
   370  
   371  		// Set the node pair for which a channel update may be out of
   372  		// date. The second chance logic uses the policyFailure field.
   373  		i.policyFailure = &DirectedNodePair{
   374  			From: route.Hops[errorSourceIdx-1].PubKeyBytes,
   375  			To:   route.Hops[errorSourceIdx].PubKeyBytes,
   376  		}
   377  
   378  		// We report incoming channel. If a second pair is granted in
   379  		// mission control, this report is ignored.
   380  		reportIncoming()
   381  
   382  	// If the outgoing channel doesn't have enough capacity, we penalize.
   383  	// But we penalize only in a single direction and only for amounts
   384  	// greater than the attempted amount.
   385  	case *lnwire.FailTemporaryChannelFailure:
   386  		reportOutgoingBalance()
   387  
   388  	// If FailExpiryTooSoon is received, there must have been some delay
   389  	// along the path. We can't know which node is causing the delay, so we
   390  	// penalize all of them up to the error source.
   391  	//
   392  	// Alternatively it could also be that we ourselves have fallen behind
   393  	// somehow. We ignore that case for now.
   394  	case *lnwire.FailExpiryTooSoon:
   395  		reportAll()
   396  
   397  	// In all other cases, we penalize the reporting node. These are all
   398  	// failures that should not happen.
   399  	default:
   400  		i.failNode(route, errorSourceIdx)
   401  	}
   402  }
   403  
   404  // processPaymentOutcomeUnknown processes a payment outcome for which no failure
   405  // message or source is available.
   406  func (i *interpretedResult) processPaymentOutcomeUnknown(route *route.Route) {
   407  	n := len(route.Hops)
   408  
   409  	// If this is a direct payment, the destination must be at fault.
   410  	if n == 1 {
   411  		i.failNode(route, n)
   412  		i.finalFailureReason = &reasonError
   413  		return
   414  	}
   415  
   416  	// Otherwise penalize all channels in the route to make sure the
   417  	// responsible node is at least hit too. We even penalize the connection
   418  	// to our own peer, because that peer could also be responsible.
   419  	i.failPairRange(route, 0, n-1)
   420  }
   421  
   422  // failNode marks the node indicated by idx in the route as failed. It also
   423  // marks the incoming and outgoing channels of the node as failed. This function
   424  // intentionally panics when the self node is failed.
   425  func (i *interpretedResult) failNode(rt *route.Route, idx int) {
   426  	// Mark the node as failing.
   427  	i.nodeFailure = &rt.Hops[idx-1].PubKeyBytes
   428  
   429  	// Mark the incoming connection as failed for the node. We intent to
   430  	// penalize as much as we can for a node level failure, including future
   431  	// outgoing traffic for this connection. The pair as it is returned by
   432  	// getPair is penalized in the original and the reversed direction. Note
   433  	// that this will also affect the score of the failing node's peers.
   434  	// This is necessary to prevent future routes from keep going into the
   435  	// same node again.
   436  	incomingChannelIdx := idx - 1
   437  	inPair, _ := getPair(rt, incomingChannelIdx)
   438  	i.pairResults[inPair] = failPairResult(0)
   439  	i.pairResults[inPair.Reverse()] = failPairResult(0)
   440  
   441  	// If not the ultimate node, mark the outgoing connection as failed for
   442  	// the node.
   443  	if idx < len(rt.Hops) {
   444  		outgoingChannelIdx := idx
   445  		outPair, _ := getPair(rt, outgoingChannelIdx)
   446  		i.pairResults[outPair] = failPairResult(0)
   447  		i.pairResults[outPair.Reverse()] = failPairResult(0)
   448  	}
   449  }
   450  
   451  // failPairRange marks the node pairs from node fromIdx to node toIdx as failed
   452  // in both direction.
   453  func (i *interpretedResult) failPairRange(
   454  	rt *route.Route, fromIdx, toIdx int) {
   455  
   456  	for idx := fromIdx; idx <= toIdx; idx++ {
   457  		i.failPair(rt, idx)
   458  	}
   459  }
   460  
   461  // failPair marks a pair as failed in both directions.
   462  func (i *interpretedResult) failPair(
   463  	rt *route.Route, idx int) {
   464  
   465  	pair, _ := getPair(rt, idx)
   466  
   467  	// Report pair in both directions without a minimum penalization amount.
   468  	i.pairResults[pair] = failPairResult(0)
   469  	i.pairResults[pair.Reverse()] = failPairResult(0)
   470  }
   471  
   472  // failPairBalance marks a pair as failed with a minimum penalization amount.
   473  func (i *interpretedResult) failPairBalance(
   474  	rt *route.Route, channelIdx int) {
   475  
   476  	pair, amt := getPair(rt, channelIdx)
   477  
   478  	i.pairResults[pair] = failPairResult(amt)
   479  }
   480  
   481  // successPairRange marks the node pairs from node fromIdx to node toIdx as
   482  // succeeded.
   483  func (i *interpretedResult) successPairRange(
   484  	rt *route.Route, fromIdx, toIdx int) {
   485  
   486  	for idx := fromIdx; idx <= toIdx; idx++ {
   487  		pair, amt := getPair(rt, idx)
   488  
   489  		i.pairResults[pair] = successPairResult(amt)
   490  	}
   491  }
   492  
   493  // getPair returns a node pair from the route and the amount passed between that
   494  // pair.
   495  func getPair(rt *route.Route, channelIdx int) (DirectedNodePair,
   496  	lnwire.MilliAtom) {
   497  
   498  	nodeTo := rt.Hops[channelIdx].PubKeyBytes
   499  	var (
   500  		nodeFrom route.Vertex
   501  		amt      lnwire.MilliAtom
   502  	)
   503  
   504  	if channelIdx == 0 {
   505  		nodeFrom = rt.SourcePubKey
   506  		amt = rt.TotalAmount
   507  	} else {
   508  		nodeFrom = rt.Hops[channelIdx-1].PubKeyBytes
   509  		amt = rt.Hops[channelIdx-1].AmtToForward
   510  	}
   511  
   512  	pair := NewDirectedNodePair(nodeFrom, nodeTo)
   513  
   514  	return pair, amt
   515  }