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 }