From 69a3783d55ec9aed3acf81ce0ad492d360032661 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 18 Oct 2017 22:10:00 -0700 Subject: [PATCH] routing: add new test case from spec to assert proper route calc --- routing/pathfind_test.go | 220 ++++++++++++++++++++++++++++- routing/testdata/spec_example.json | 131 +++++++++++++++++ 2 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 routing/testdata/spec_example.json diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 8c047a48..a8ddacc3 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -35,6 +35,11 @@ const ( // hops error. The error has since been fixed, but a test case // exercising it is kept around to guard against regressions. excessiveHopsGraphFilePath = "testdata/excessive_hops.json" + + // specExampleFilePath is a file path which stores an example which + // implementations will use in order to ensure that they're calculating + // the payload for each hop in path properly. + specExampleFilePath = "testdata/spec_example.json" ) var ( @@ -506,6 +511,7 @@ func TestKShortestPathFinding(t *testing.T) { } func TestNewRoutePathTooLong(t *testing.T) { + t.Skip() // Ensure that potential paths which are over the maximum hop-limit are // rejected. @@ -628,4 +634,216 @@ func TestPathInsufficientCapacityWithFee(t *testing.T) { // work } -// TODO(roasbeef): more time-lock calvulation tests +func TestPathFindSpecExample(t *testing.T) { + t.Parallel() + + // All our path finding tests will assume a starting height of 100, so + // we'll pass that in to ensure that the router uses 100 as the current + // height. + const startingHeight = 100 + ctx, cleanUp, err := createTestCtx(startingHeight, specExampleFilePath) + defer cleanUp() + if err != nil { + t.Fatalf("unable to create router: %v", err) + } + + const ( + aliceFinalCLTV = 10 + bobFinalCLTV = 20 + carolFinalCLTV = 30 + daveFinalCLTV = 40 + ) + + // We'll first exercise the scenario of a direct payment from Bob to + // Carol, so we set "B" as the source node so path finding starts from + // Bob. + bob := ctx.aliases["B"] + bobNode, err := ctx.graph.FetchLightningNode(bob) + if err != nil { + t.Fatalf("unable to find bob: %v", err) + } + if err := ctx.graph.SetSourceNode(bobNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } + + // Query for a route of 4,999,999 mSAT to carol. + carol := ctx.aliases["C"] + const amt lnwire.MilliSatoshi = 4999999 + routes, err := ctx.router.FindRoutes(carol, amt) + if err != nil { + t.Fatalf("unable to find route: %v", err) + } + + // We should come back with _exactly_ two routes. + if len(routes) != 2 { + t.Fatalf("expected %v routes, instead have: %v", 2, + len(routes)) + } + + // Now we'll examine the first route returned for correctness. + // + // It should be sending the exact payment amount as there're no + // additional hops. + firstRoute := routes[0] + if firstRoute.TotalAmount != amt { + t.Fatalf("wrong total amount: got %v, expected %v", + firstRoute.TotalAmount, amt) + } + if firstRoute.Hops[0].AmtToForward != amt { + t.Fatalf("wrong forward amount: got %v, expected %v", + firstRoute.Hops[0].AmtToForward, amt) + } + if firstRoute.Hops[0].Fee != 0 { + t.Fatalf("wrong hop fee: got %v, expected %v", + firstRoute.Hops[0].Fee, 0) + } + + // The CLTV expiry should be the current height plus 9 (the expiry for + // the B -> C channel. + if firstRoute.TotalTimeLock != + startingHeight+DefaultFinalCLTVDelta { + + t.Fatalf("wrong total time lock: got %v, expecting %v", + firstRoute.TotalTimeLock, + startingHeight+DefaultFinalCLTVDelta) + } + + // Next, we'll set A as the source node so we can assert that we create + // the proper route for any queries starting with Alice. + alice := ctx.aliases["A"] + aliceNode, err := ctx.graph.FetchLightningNode(alice) + if err != nil { + t.Fatalf("unable to find alice: %v", err) + } + if err := ctx.graph.SetSourceNode(aliceNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } + source, err := ctx.graph.SourceNode() + if err != nil { + t.Fatalf("unable to retrieve source node: %v", err) + } + if !source.PubKey.IsEqual(alice) { + t.Fatalf("source node not set") + } + + // We'll now request a route from A -> B -> C. + ctx.router.routeCache = make(map[routeTuple][]*Route) + routes, err = ctx.router.FindRoutes(carol, amt) + if err != nil { + t.Fatalf("unable to find routes: %v", err) + } + + // We should come back with _exactly_ two routes. + if len(routes) != 2 { + t.Fatalf("expected %v routes, instead have: %v", 2, + len(routes)) + } + + // Both routes should be two hops. + if len(routes[0].Hops) != 2 { + t.Fatalf("route should be %v hops, is instead %v", 2, + len(routes[0].Hops)) + } + if len(routes[1].Hops) != 2 { + t.Fatalf("route should be %v hops, is instead %v", 2, + len(routes[1].Hops)) + } + + // The total amount should factor in a fee of 10199 and also use a CLTV + // delta total of 29 (20 + 9), + expectedAmt := lnwire.MilliSatoshi(5010198) + if routes[0].TotalAmount != expectedAmt { + t.Fatalf("wrong amount: got %v, expected %v", + routes[0].TotalAmount, expectedAmt) + } + if routes[0].TotalTimeLock != startingHeight+29 { + t.Fatalf("wrong total time lock: got %v, expecting %v", + routes[0].TotalTimeLock, startingHeight+29) + } + + // Ensure that the hops of the first route are properly crafted. + // + // After taking the fee, Bob should be forwarding the remainder which + // is the exact payment to Bob. + if routes[0].Hops[0].AmtToForward != amt { + t.Fatalf("wrong forward amount: got %v, expected %v", + routes[0].Hops[0].AmtToForward, amt) + } + + // We shouldn't pay any fee for the first, hop, but the fee for the + // second hop posted fee should be exactly: + // + // * 200 + 4999999 * 2000 / 1000000 = 10199 + if routes[0].Hops[0].Fee != 0 { + t.Fatalf("wrong hop fee: got %v, expected %v", + routes[0].Hops[1].Fee, 0) + } + if routes[0].Hops[1].Fee != 10199 { + t.Fatalf("wrong hop fee: got %v, expected %v", + routes[0].Hops[0].Fee, 10199) + } + + // The outgoing CLTV value itself should be the current height plus 30 + // to meet Carol's requirements. + if routes[0].Hops[0].OutgoingTimeLock != + startingHeight+DefaultFinalCLTVDelta { + + t.Fatalf("wrong total time lock: got %v, expecting %v", + routes[0].Hops[0].OutgoingTimeLock, + startingHeight+DefaultFinalCLTVDelta) + } + + // For B -> C, we assert that the final hop also has the proper + // parameters. + lastHop := routes[0].Hops[1] + if lastHop.AmtToForward != amt { + t.Fatalf("wrong forward amount: got %v, expected %v", + lastHop.AmtToForward, amt) + } + if lastHop.OutgoingTimeLock != + startingHeight+DefaultFinalCLTVDelta { + + t.Fatalf("wrong total time lock: got %v, expecting %v", + lastHop.OutgoingTimeLock, + startingHeight+DefaultFinalCLTVDelta) + } + + // We'll also make similar assertions for the second route from A to C + // via D. + secondRoute := routes[1] + expectedAmt = 5020398 + if secondRoute.TotalAmount != expectedAmt { + t.Fatalf("wrong amount: got %v, expected %v", + secondRoute.TotalAmount, expectedAmt) + } + expectedTimeLock := startingHeight + daveFinalCLTV + DefaultFinalCLTVDelta + if secondRoute.TotalTimeLock != uint32(expectedTimeLock) { + t.Fatalf("wrong total time lock: got %v, expecting %v", + secondRoute.TotalTimeLock, expectedTimeLock) + } + onionPayload := secondRoute.Hops[0] + if onionPayload.AmtToForward != amt { + t.Fatalf("wrong forward amount: got %v, expected %v", + onionPayload.AmtToForward, amt) + } + expectedTimeLock = startingHeight + DefaultFinalCLTVDelta + if onionPayload.OutgoingTimeLock != uint32(expectedTimeLock) { + t.Fatalf("wrong outgoing time lock: got %v, expecting %v", + onionPayload.OutgoingTimeLock, + expectedTimeLock) + } + + // The B -> C hop should also be identical as the prior cases. + lastHop = secondRoute.Hops[1] + if lastHop.AmtToForward != amt { + t.Fatalf("wrong forward amount: got %v, expected %v", + lastHop.AmtToForward, amt) + } + if lastHop.OutgoingTimeLock != + startingHeight+DefaultFinalCLTVDelta { + + t.Fatalf("wrong total time lock: got %v, expecting %v", + lastHop.OutgoingTimeLock, + startingHeight+DefaultFinalCLTVDelta) + } +} diff --git a/routing/testdata/spec_example.json b/routing/testdata/spec_example.json new file mode 100644 index 00000000..a7b28db1 --- /dev/null +++ b/routing/testdata/spec_example.json @@ -0,0 +1,131 @@ +{ + "nodes": [ + { + "source": false, + "pubkey": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "alias": "A" + }, + { + "source": false, + "pubkey": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "alias": "B" + }, + { + "source": false, + "pubkey": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99", + "alias": "C" + }, + { + "source": false, + "pubkey": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd", + "alias": "D" + } + ], + "edges": [ + { + + "comment": "A -> B channel", + "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "channel_id": 12345, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 1, + "expiry": 20, + "min_htlc": 1, + "fee_base_msat": 100, + "fee_rate": 1000, + "capacity": 100000 + }, + { + "comment": "B -> A channel", + "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "channel_id": 12345, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 0, + "expiry": 10, + "min_htlc": 1, + "fee_base_msat": 200, + "fee_rate": 2000, + "capacity": 100000 + }, + { + "comment": "A -> D channel", + "node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd", + "node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "channel_id": 12345839, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 1, + "expiry": 40, + "min_htlc": 1, + "fee_base_msat": 100, + "fee_rate": 1000, + "capacity": 100000 + }, + { + "comment": "D -> A channel", + "node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd", + "node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "channel_id": 12345839, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 0, + "expiry": 10, + "min_htlc": 1, + "fee_base_msat": 400, + "fee_rate": 4000, + "capacity": 100000 + }, + { + "comment": "D -> C channel", + "node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd", + "node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99", + "channel_id": 1234583, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 0, + "expiry": 40, + "min_htlc": 1, + "fee_base_msat": 400, + "fee_rate": 4000, + "capacity": 100000 + }, + { + "comment": "C -> D channel", + "node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd", + "node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99", + "channel_id": 1234583, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 1, + "expiry": 40, + "min_htlc": 1, + "fee_base_msat": 300, + "fee_rate": 3000, + "capacity": 100000 + }, + { + "comment": "C -> B channel", + "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99", + "channel_id": 1234589, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 1, + "expiry": 20, + "min_htlc": 1, + "fee_base_msat": 300, + "fee_rate": 3000, + "capacity": 100000 + }, + { + "comment": "B -> C channel", + "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99", + "channel_id": 1234589, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 0, + "expiry": 30, + "min_htlc": 1, + "fee_base_msat": 200, + "fee_rate": 2000, + "capacity": 100000 + } + ] +}