routing: add new test case from spec to assert proper route calc

This commit is contained in:
Olaoluwa Osuntokun 2017-10-18 22:10:00 -07:00
parent acd160a419
commit 69a3783d55
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21
2 changed files with 350 additions and 1 deletions

@ -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)
}
}

131
routing/testdata/spec_example.json vendored Normal file

@ -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
}
]
}