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

     1  package routing
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	"github.com/davecgh/go-spew/spew"
     8  	"github.com/decred/dcrlnd/lnwire"
     9  
    10  	"github.com/decred/dcrlnd/routing/route"
    11  )
    12  
    13  var (
    14  	hops = []route.Vertex{
    15  		{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4},
    16  	}
    17  
    18  	routeOneHop = route.Route{
    19  		SourcePubKey: hops[0],
    20  		TotalAmount:  100,
    21  		Hops: []*route.Hop{
    22  			{PubKeyBytes: hops[1], AmtToForward: 99},
    23  		},
    24  	}
    25  
    26  	routeTwoHop = route.Route{
    27  		SourcePubKey: hops[0],
    28  		TotalAmount:  100,
    29  		Hops: []*route.Hop{
    30  			{PubKeyBytes: hops[1], AmtToForward: 99},
    31  			{PubKeyBytes: hops[2], AmtToForward: 97},
    32  		},
    33  	}
    34  
    35  	routeThreeHop = route.Route{
    36  		SourcePubKey: hops[0],
    37  		TotalAmount:  100,
    38  		Hops: []*route.Hop{
    39  			{PubKeyBytes: hops[1], AmtToForward: 99},
    40  			{PubKeyBytes: hops[2], AmtToForward: 97},
    41  			{PubKeyBytes: hops[3], AmtToForward: 94},
    42  		},
    43  	}
    44  
    45  	routeFourHop = route.Route{
    46  		SourcePubKey: hops[0],
    47  		TotalAmount:  100,
    48  		Hops: []*route.Hop{
    49  			{PubKeyBytes: hops[1], AmtToForward: 99},
    50  			{PubKeyBytes: hops[2], AmtToForward: 97},
    51  			{PubKeyBytes: hops[3], AmtToForward: 94},
    52  			{PubKeyBytes: hops[4], AmtToForward: 90},
    53  		},
    54  	}
    55  )
    56  
    57  func getTestPair(from, to int) DirectedNodePair {
    58  	return NewDirectedNodePair(hops[from], hops[to])
    59  }
    60  
    61  func getPolicyFailure(from, to int) *DirectedNodePair {
    62  	pair := getTestPair(from, to)
    63  	return &pair
    64  }
    65  
    66  type resultTestCase struct {
    67  	name          string
    68  	route         *route.Route
    69  	success       bool
    70  	failureSrcIdx int
    71  	failure       lnwire.FailureMessage
    72  
    73  	expectedResult *interpretedResult
    74  }
    75  
    76  var resultTestCases = []resultTestCase{
    77  	// Tests that a temporary channel failure result is properly
    78  	// interpreted.
    79  	{
    80  		name:          "fail",
    81  		route:         &routeTwoHop,
    82  		failureSrcIdx: 1,
    83  		failure:       lnwire.NewTemporaryChannelFailure(nil),
    84  
    85  		expectedResult: &interpretedResult{
    86  			pairResults: map[DirectedNodePair]pairResult{
    87  				getTestPair(0, 1): successPairResult(100),
    88  				getTestPair(1, 2): failPairResult(99),
    89  			},
    90  		},
    91  	},
    92  
    93  	// Tests that a expiry too soon failure result is properly interpreted.
    94  	{
    95  		name:          "fail expiry too soon",
    96  		route:         &routeFourHop,
    97  		failureSrcIdx: 3,
    98  		failure:       lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{}),
    99  
   100  		expectedResult: &interpretedResult{
   101  			pairResults: map[DirectedNodePair]pairResult{
   102  				getTestPair(0, 1): failPairResult(0),
   103  				getTestPair(1, 0): failPairResult(0),
   104  				getTestPair(1, 2): failPairResult(0),
   105  				getTestPair(2, 1): failPairResult(0),
   106  				getTestPair(2, 3): failPairResult(0),
   107  				getTestPair(3, 2): failPairResult(0),
   108  			},
   109  		},
   110  	},
   111  
   112  	// Tests an incorrect payment details result. This should be a final
   113  	// failure, but mark all pairs along the route as successful.
   114  	{
   115  		name:          "fail incorrect details",
   116  		route:         &routeTwoHop,
   117  		failureSrcIdx: 2,
   118  		failure:       lnwire.NewFailIncorrectDetails(97, 0),
   119  
   120  		expectedResult: &interpretedResult{
   121  			pairResults: map[DirectedNodePair]pairResult{
   122  				getTestPair(0, 1): successPairResult(100),
   123  				getTestPair(1, 2): successPairResult(99),
   124  			},
   125  			finalFailureReason: &reasonIncorrectDetails,
   126  		},
   127  	},
   128  
   129  	// Tests a successful direct payment.
   130  	{
   131  		name:    "success direct",
   132  		route:   &routeOneHop,
   133  		success: true,
   134  
   135  		expectedResult: &interpretedResult{
   136  			pairResults: map[DirectedNodePair]pairResult{
   137  				getTestPair(0, 1): successPairResult(100),
   138  			},
   139  		},
   140  	},
   141  
   142  	// Tests a successful two hop payment.
   143  	{
   144  		name:    "success",
   145  		route:   &routeTwoHop,
   146  		success: true,
   147  
   148  		expectedResult: &interpretedResult{
   149  			pairResults: map[DirectedNodePair]pairResult{
   150  				getTestPair(0, 1): successPairResult(100),
   151  				getTestPair(1, 2): successPairResult(99),
   152  			},
   153  		},
   154  	},
   155  
   156  	// Tests a malformed htlc from a direct peer.
   157  	{
   158  		name:          "fail malformed htlc from direct peer",
   159  		route:         &routeTwoHop,
   160  		failureSrcIdx: 0,
   161  		failure:       lnwire.NewInvalidOnionKey(nil),
   162  
   163  		expectedResult: &interpretedResult{
   164  			nodeFailure: &hops[1],
   165  			pairResults: map[DirectedNodePair]pairResult{
   166  				getTestPair(1, 0): failPairResult(0),
   167  				getTestPair(1, 2): failPairResult(0),
   168  				getTestPair(0, 1): failPairResult(0),
   169  				getTestPair(2, 1): failPairResult(0),
   170  			},
   171  		},
   172  	},
   173  
   174  	// Tests a malformed htlc from a direct peer that is also the final
   175  	// destination.
   176  	{
   177  		name:          "fail malformed htlc from direct final peer",
   178  		route:         &routeOneHop,
   179  		failureSrcIdx: 0,
   180  		failure:       lnwire.NewInvalidOnionKey(nil),
   181  
   182  		expectedResult: &interpretedResult{
   183  			finalFailureReason: &reasonError,
   184  			nodeFailure:        &hops[1],
   185  			pairResults: map[DirectedNodePair]pairResult{
   186  				getTestPair(1, 0): failPairResult(0),
   187  				getTestPair(0, 1): failPairResult(0),
   188  			},
   189  		},
   190  	},
   191  
   192  	// Tests that a fee insufficient failure to an intermediate hop with
   193  	// index 2 results in the first hop marked as success, and then a
   194  	// bidirectional failure for the incoming channel. It should also result
   195  	// in a policy failure for the outgoing hop.
   196  	{
   197  		name:          "fail fee insufficient intermediate",
   198  		route:         &routeFourHop,
   199  		failureSrcIdx: 2,
   200  		failure:       lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}),
   201  
   202  		expectedResult: &interpretedResult{
   203  			pairResults: map[DirectedNodePair]pairResult{
   204  				getTestPair(0, 1): {
   205  					success: true,
   206  					amt:     100,
   207  				},
   208  				getTestPair(1, 2): {},
   209  				getTestPair(2, 1): {},
   210  			},
   211  			policyFailure: getPolicyFailure(2, 3),
   212  		},
   213  	},
   214  
   215  	// Tests an invalid onion payload from a final hop. The final hop should
   216  	// be failed while the proceeding hops are reproed as successes. The
   217  	// failure is terminal since the receiver can't process our onion.
   218  	{
   219  		name:          "fail invalid onion payload final hop four",
   220  		route:         &routeFourHop,
   221  		failureSrcIdx: 4,
   222  		failure:       lnwire.NewInvalidOnionPayload(0, 0),
   223  
   224  		expectedResult: &interpretedResult{
   225  			pairResults: map[DirectedNodePair]pairResult{
   226  				getTestPair(0, 1): {
   227  					success: true,
   228  					amt:     100,
   229  				},
   230  				getTestPair(1, 2): {
   231  					success: true,
   232  					amt:     99,
   233  				},
   234  				getTestPair(2, 3): {
   235  					success: true,
   236  					amt:     97,
   237  				},
   238  				getTestPair(4, 3): {},
   239  				getTestPair(3, 4): {},
   240  			},
   241  			finalFailureReason: &reasonError,
   242  			nodeFailure:        &hops[4],
   243  		},
   244  	},
   245  
   246  	// Tests an invalid onion payload from a final hop on a three hop route.
   247  	{
   248  		name:          "fail invalid onion payload final hop three",
   249  		route:         &routeThreeHop,
   250  		failureSrcIdx: 3,
   251  		failure:       lnwire.NewInvalidOnionPayload(0, 0),
   252  
   253  		expectedResult: &interpretedResult{
   254  			pairResults: map[DirectedNodePair]pairResult{
   255  				getTestPair(0, 1): {
   256  					success: true,
   257  					amt:     100,
   258  				},
   259  				getTestPair(1, 2): {
   260  					success: true,
   261  					amt:     99,
   262  				},
   263  				getTestPair(3, 2): {},
   264  				getTestPair(2, 3): {},
   265  			},
   266  			finalFailureReason: &reasonError,
   267  			nodeFailure:        &hops[3],
   268  		},
   269  	},
   270  
   271  	// Tests an invalid onion payload from an intermediate hop. Only the
   272  	// reporting node should be failed. The failure is non-terminal since we
   273  	// can still try other paths.
   274  	{
   275  		name:          "fail invalid onion payload intermediate",
   276  		route:         &routeFourHop,
   277  		failureSrcIdx: 3,
   278  		failure:       lnwire.NewInvalidOnionPayload(0, 0),
   279  
   280  		expectedResult: &interpretedResult{
   281  			pairResults: map[DirectedNodePair]pairResult{
   282  				getTestPair(0, 1): {
   283  					success: true,
   284  					amt:     100,
   285  				},
   286  				getTestPair(1, 2): {
   287  					success: true,
   288  					amt:     99,
   289  				},
   290  				getTestPair(3, 2): {},
   291  				getTestPair(3, 4): {},
   292  				getTestPair(2, 3): {},
   293  				getTestPair(4, 3): {},
   294  			},
   295  			nodeFailure: &hops[3],
   296  		},
   297  	},
   298  
   299  	// Tests an invalid onion payload in a direct peer that is also the
   300  	// final hop. The final node should be failed and the error is terminal
   301  	// since the remote node can't process our onion.
   302  	{
   303  		name:          "fail invalid onion payload direct",
   304  		route:         &routeOneHop,
   305  		failureSrcIdx: 1,
   306  		failure:       lnwire.NewInvalidOnionPayload(0, 0),
   307  
   308  		expectedResult: &interpretedResult{
   309  			pairResults: map[DirectedNodePair]pairResult{
   310  				getTestPair(1, 0): {},
   311  				getTestPair(0, 1): {},
   312  			},
   313  			finalFailureReason: &reasonError,
   314  			nodeFailure:        &hops[1],
   315  		},
   316  	},
   317  
   318  	// Tests a single hop mpp timeout. Test that final node is not
   319  	// penalized. This is a temporary measure while we decide how to
   320  	// penalize mpp timeouts.
   321  	{
   322  		name:          "one hop mpp timeout",
   323  		route:         &routeOneHop,
   324  		failureSrcIdx: 1,
   325  		failure:       &lnwire.FailMPPTimeout{},
   326  
   327  		expectedResult: &interpretedResult{
   328  			pairResults: map[DirectedNodePair]pairResult{
   329  				getTestPair(0, 1): successPairResult(100),
   330  			},
   331  			nodeFailure: nil,
   332  		},
   333  	},
   334  
   335  	// Tests a two hop mpp timeout. Test that final node is not penalized
   336  	// and the intermediate hop is attributed the success. This is a
   337  	// temporary measure while we decide how to penalize mpp timeouts.
   338  	{
   339  		name:          "two hop mpp timeout",
   340  		route:         &routeTwoHop,
   341  		failureSrcIdx: 2,
   342  		failure:       &lnwire.FailMPPTimeout{},
   343  
   344  		expectedResult: &interpretedResult{
   345  			pairResults: map[DirectedNodePair]pairResult{
   346  				getTestPair(0, 1): successPairResult(100),
   347  				getTestPair(1, 2): successPairResult(99),
   348  			},
   349  			nodeFailure: nil,
   350  		},
   351  	},
   352  
   353  	// Test a channel disabled failure from the final hop in two hops. Only the
   354  	// disabled channel should be penalized for any amount.
   355  	{
   356  		name:          "two hop channel disabled",
   357  		route:         &routeTwoHop,
   358  		failureSrcIdx: 1,
   359  		failure:       &lnwire.FailChannelDisabled{},
   360  
   361  		expectedResult: &interpretedResult{
   362  			pairResults: map[DirectedNodePair]pairResult{
   363  				getTestPair(1, 2): failPairResult(0),
   364  				getTestPair(2, 1): failPairResult(0),
   365  				getTestPair(0, 1): successPairResult(100),
   366  			},
   367  			policyFailure: getPolicyFailure(1, 2),
   368  		},
   369  	},
   370  }
   371  
   372  // TestResultInterpretation executes a list of test cases that test the result
   373  // interpretation logic.
   374  func TestResultInterpretation(t *testing.T) {
   375  	emptyResults := make(map[DirectedNodePair]pairResult)
   376  
   377  	for _, testCase := range resultTestCases {
   378  		t.Run(testCase.name, func(t *testing.T) {
   379  			i := interpretResult(
   380  				testCase.route, testCase.success,
   381  				&testCase.failureSrcIdx, testCase.failure,
   382  			)
   383  
   384  			expected := testCase.expectedResult
   385  
   386  			// Replace nil pairResults with empty map to satisfy
   387  			// DeepEqual.
   388  			if expected.pairResults == nil {
   389  				expected.pairResults = emptyResults
   390  			}
   391  
   392  			if !reflect.DeepEqual(i, expected) {
   393  				t.Fatalf("unexpected result\nwant: %v\ngot: %v",
   394  					spew.Sdump(expected), spew.Sdump(i))
   395  			}
   396  		})
   397  	}
   398  }