routing: add new test case from spec to assert proper route calc
This commit is contained in:
parent
acd160a419
commit
69a3783d55
@ -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
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
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user