mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-19 13:17:32 +02:00
In this commit, we sync the run lineage data the v58 bundle shipped with (code_hybrid1, 28 candidates), so the committed site matches what Litbucket serves.
472 lines
No EOL
937 KiB
JSON
472 lines
No EOL
937 KiB
JSON
{
|
|
"run_id": "code_hybrid1",
|
|
"reflection_lm": "codex:gpt-5.6-sol",
|
|
"mode": "generalization",
|
|
"status": "complete",
|
|
"seed_score": 0.5278,
|
|
"best_score": 0.9962,
|
|
"iterations": [
|
|
{
|
|
"i": 0,
|
|
"candidate_score": 0.5278,
|
|
"best_score": 0.5278,
|
|
"note": "seed"
|
|
},
|
|
{
|
|
"i": 1,
|
|
"candidate_score": 0.1704,
|
|
"best_score": 0.5278,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 2,
|
|
"candidate_score": 0.5081,
|
|
"best_score": 0.5278,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 3,
|
|
"candidate_score": 0.4535,
|
|
"best_score": 0.5278,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 4,
|
|
"candidate_score": 0.5278,
|
|
"best_score": 0.5278,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 5,
|
|
"candidate_score": 0.1788,
|
|
"best_score": 0.5278,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 6,
|
|
"candidate_score": 0.6318,
|
|
"best_score": 0.5278,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 7,
|
|
"candidate_score": 0.4576,
|
|
"best_score": 0.5278,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 8,
|
|
"candidate_score": 0.3677,
|
|
"best_score": 0.5278,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 9,
|
|
"candidate_score": 0.3505,
|
|
"best_score": 0.5278,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 10,
|
|
"candidate_score": 0.403,
|
|
"best_score": 0.5278,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 11,
|
|
"candidate_score": 0.6306,
|
|
"best_score": 0.6306,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 12,
|
|
"candidate_score": 0.0,
|
|
"best_score": 0.6306,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 13,
|
|
"candidate_score": 0.7194,
|
|
"best_score": 0.7194,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 14,
|
|
"candidate_score": 0.5112,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 15,
|
|
"candidate_score": 0.4349,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 16,
|
|
"candidate_score": 0.5821,
|
|
"best_score": 0.7194,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 17,
|
|
"candidate_score": 0.1215,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 18,
|
|
"candidate_score": 0.0,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 19,
|
|
"candidate_score": 0.408,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 20,
|
|
"candidate_score": 0.3409,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 21,
|
|
"candidate_score": 0.3321,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 22,
|
|
"candidate_score": 0.7006,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 23,
|
|
"candidate_score": 0.5843,
|
|
"best_score": 0.7194,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 24,
|
|
"candidate_score": 0.0229,
|
|
"best_score": 0.7194,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 25,
|
|
"candidate_score": 0.5705,
|
|
"best_score": 0.7194,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 26,
|
|
"candidate_score": 0.4501,
|
|
"best_score": 0.7194,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 27,
|
|
"candidate_score": 0.4195,
|
|
"best_score": 0.7194,
|
|
"note": "accepted"
|
|
}
|
|
],
|
|
"seed_params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tplanReusePenalty float64\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*4 + 12\n\tif r.attemptLimit < 28 {\n\t\tr.attemptLimit = 28\n\t}\n\tif r.attemptLimit > 80 {\n\t\tr.attemptLimit = 80\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probabilityWithTotal(edge *candidateEdge,\n\thtlcAmt, total lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(htlcAmt) || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tretryCeiling := failure.upper * 3 / 4\n\t\tif retryCeiling == 0 {\n\t\t\tretryCeiling = 1\n\t\t}\n\t\tif total > retryCeiling {\n\t\t\treturn 0\n\t\t}\n\n\t\tretryScale = 0.60 / math.Sqrt(float64(failure.count))\n\t\tif retryScale < 0.15 {\n\t\t\tretryScale = 0.15\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retryScale\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995 * retryScale\n\t}\n\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\tif p > 0.012 {\n\t\t\tp = 0.012\n\t\t}\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.985 - 0.97*pos\n\t\tp = 0.18*p + 0.82*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.50 {\n\t\t\tscale := 1 - 0.92*(ratio-0.50)/0.50\n\t\t\tif scale < 0.08 {\n\t\t\t\tscale = 0.08\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.48 * math.Exp(-distance/0.18)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.11 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tp = 0.68*p + 0.32*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.002 {\n\t\treturn 0.002\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treturn r.probabilityWithTotal(\n\t\tedge, amt, amt+r.reserved[edge.key],\n\t)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 420_000.0\n\t\thopPenalty = 220.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 18_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 230_000\n\n\t\t\tif r.reserved[edge.key] > 0 {\n\t\t\t\tedgeScore += r.planReusePenalty\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmax lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > max {\n\t\tvalue = max\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\ntype candidatePlanEdgeLoad struct {\n\ttotal lnwire.MilliSatoshi\n\tmax lnwire.MilliSatoshi\n\tuses uint32\n}\n\nfunc (r *candidateRouter) scorePlan(plan []*route.Route,\n\tbaseReservations map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tloads := make(map[candidateEdgeKey]candidatePlanEdgeLoad)\n\tvar fees lnwire.MilliSatoshi\n\n\tfor _, rt := range plan {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tfees += rt.TotalAmount - finalAmt\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\n\t\t\tload := loads[key]\n\t\t\tload.total += amt\n\t\t\tif amt > load.max {\n\t\t\t\tload.max = amt\n\t\t\t}\n\t\t\tload.uses++\n\t\t\tloads[key] = load\n\t\t}\n\t}\n\n\tscore := -float64(fees)/4_000_000 - 0.025*float64(len(plan))\n\n\tfor key, load := range loads {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\ttotal := baseReservations[key] + load.total\n\t\tp := r.probabilityWithTotal(edge, load.max, total)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\n\t\tif load.uses > 1 && edge.key.from != r.source {\n\t\t\tscore -= 0.22 * float64(load.uses-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) planOnce(total lnwire.MilliSatoshi,\n\tparts uint32, sizeBias, reusePenalty float64) (\n\t[]*route.Route, bool) {\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tsavedPenalty := r.planReusePenalty\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t\tr.planReusePenalty = savedPenalty\n\t}()\n\n\tr.planReusePenalty = reusePenalty\n\tremaining := total\n\tslots := parts\n\tvar plan []*route.Route\n\n\tfor remaining > 0 && slots > 0 {\n\t\tif slots == 1 {\n\t\t\trt, _, err := r.findRoute(remaining)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tplan = append(plan, rt)\n\t\t\tr.reserveRoute(rt)\n\t\t\tremaining = 0\n\t\t\tbreak\n\t\t}\n\n\t\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tvar sizes []lnwire.MilliSatoshi\n\t\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\t\tcandidateAddAmount(&sizes, seen, base/2, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*3/4, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*7/8, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*5/4, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*3/2, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*2, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*3, remaining)\n\t\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\n\t\tif r.lastFailedShard > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, r.lastFailedShard/2, remaining,\n\t\t\t)\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, r.lastFailedShard*5/8, remaining,\n\t\t\t)\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, r.lastFailedShard*3/4, remaining,\n\t\t\t)\n\t\t}\n\n\t\tvar bestRoute *route.Route\n\t\tbestSize := lnwire.MilliSatoshi(0)\n\t\tbestUtility := math.Inf(-1)\n\n\t\tfor _, size := range sizes {\n\t\t\trt, logProb, err := r.findRoute(size)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfees := rt.TotalAmount - size\n\t\t\tsizeReward := math.Log(\n\t\t\t\tfloat64(size) / float64(base),\n\t\t\t)\n\t\t\tutility := logProb + sizeBias*sizeReward -\n\t\t\t\tfloat64(fees)/4_000_000\n\n\t\t\tif bestRoute == nil || utility > bestUtility {\n\t\t\t\tbestRoute = rt\n\t\t\t\tbestSize = size\n\t\t\t\tbestUtility = utility\n\t\t\t}\n\t\t}\n\n\t\tif bestRoute == nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tplan = append(plan, bestRoute)\n\t\tr.reserveRoute(bestRoute)\n\t\tremaining -= bestSize\n\t\tslots--\n\t}\n\n\tif remaining != 0 || len(plan) == 0 {\n\t\treturn nil, false\n\t}\n\treturn plan, true\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tparts uint32) (*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\treturn rt, err\n\t}\n\n\tbase := (total + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&sizes, seen, total, total)\n\tcandidateAddAmount(&sizes, seen, total*3/4, total)\n\tcandidateAddAmount(&sizes, seen, total/2, total)\n\tcandidateAddAmount(&sizes, seen, base*3/2, total)\n\tcandidateAddAmount(&sizes, seen, base, total)\n\tcandidateAddAmount(&sizes, seen, base*3/4, total)\n\tcandidateAddAmount(&sizes, seen, base/2, total)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*3/4, total,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8, total,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2, total,\n\t\t)\n\t}\n\n\tvar best *route.Route\n\tbestUtility := math.Inf(-1)\n\n\tfor _, size := range sizes {\n\t\trt, logProb, err := r.findRoute(size)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfees := rt.TotalAmount - size\n\t\tprogress := math.Log(float64(size) / float64(base))\n\t\tutility := logProb + 0.62*progress -\n\t\t\tfloat64(fees)/4_000_000\n\n\t\tif best == nil || utility > bestUtility {\n\t\t\tbest = rt\n\t\t\tbestUtility = utility\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32, inFlight uint32) ([]*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tif inFlight == 0 && r.lastFailedShard == 0 {\n\t\tfull, logProb, err := r.findRoute(total)\n\t\tif err == nil && logProb >= math.Log(0.24) {\n\t\t\treturn []*route.Route{full}, nil\n\t\t}\n\t}\n\n\ttype planConfig struct {\n\t\tsizeBias float64\n\t\treusePenalty float64\n\t}\n\n\tconfigs := []planConfig{\n\t\t{sizeBias: 0.18, reusePenalty: 180_000},\n\t\t{sizeBias: 0.40, reusePenalty: 520_000},\n\t\t{sizeBias: 0.68, reusePenalty: 1_050_000},\n\t\t{sizeBias: 0.95, reusePenalty: 1_700_000},\n\t}\n\n\tbaseReservations := candidateCopyReservations(r.reserved)\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor _, config := range configs {\n\t\tplan, ok := r.planOnce(\n\t\t\ttotal, parts, config.sizeBias, config.reusePenalty,\n\t\t)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tscore := r.scorePlan(plan, baseReservations)\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan != nil {\n\t\treturn bestPlan, nil\n\t}\n\n\trt, err := r.fallbackShard(total, parts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{rt}, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft, inFlightHtlcs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 5 / 8\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t}\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
},
|
|
"best_candidate": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts) + 6\n\tif r.attemptLimit < 10 {\n\t\tr.attemptLimit = 10\n\t}\n\tif r.attemptLimit > 28 {\n\t\tr.attemptLimit = 28\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.052)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.034))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.30:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.48:\n\t\t\treturn 0\n\t\tcase ratio > 0.74:\n\t\t\treturn 0\n\t\tcase ratio > 0.60:\n\t\t\tretryScale = 0.18\n\t\tcase ratio > 0.45:\n\t\t\tretryScale = 0.40\n\t\tcase ratio > 0.30:\n\t\t\tretryScale = 0.65\n\t\tdefault:\n\t\t\tretryScale = 0.84\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.70\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.004\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence := 0.994 - 0.988*pos\n\t\tp = 0.13*p + 0.87*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.22 {\n\t\t\tscale := 1 - 0.91*(ratio-0.22)/0.78\n\t\t\tif scale < 0.09 {\n\t\t\t\tscale = 0.09\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.60 * math.Exp(-distance/0.19)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.075 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.16\n\t\tcount := belief.successes + belief.failures\n\t\tif count >= 2 {\n\t\t\tweight = 0.24\n\t\t}\n\t\tif count >= 4 {\n\t\t\tweight = 0.32\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\ttotalP := r.liquidityProbability(edge, reserved+amt)\n\tif totalP <= 0 {\n\t\treturn 0\n\t}\n\tif reserved == 0 {\n\t\treturn totalP\n\t}\n\n\tbaseP := r.liquidityProbability(edge, reserved)\n\tif baseP <= 0 {\n\t\treturn 0\n\t}\n\n\tp := totalP / baseP\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\treturn p\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tedge *candidateEdge\n\tnext *candidatePathLabel\n\thops int\n\tactive bool\n\tidx int\n}\n\ntype candidatePathQueue []*candidatePathLabel\n\nfunc (q candidatePathQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidatePathQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidatePathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidatePathQueue) Push(x any) {\n\titem := x.(*candidatePathLabel)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidatePathQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidatePathContains(label *candidatePathLabel,\n\tnode route.Vertex) bool {\n\n\tfor cur := label; cur != nil; cur = cur.next {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(labels map[route.Vertex][]*candidatePathLabel,\n\tlabel *candidatePathLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif old.active && old.score <= label.score &&\n\t\t\told.required <= label.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := current[:0]\n\tfor _, old := range current {\n\t\tif old.active && label.score <= old.score &&\n\t\t\tlabel.required <= old.required {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tif old.active {\n\t\t\tkept = append(kept, old)\n\t\t}\n\t}\n\n\tkept = append(kept, label)\n\tif len(kept) > 5 {\n\t\tsort.Slice(kept, func(i, j int) bool {\n\t\t\tli := kept[i].score +\n\t\t\t\tfloat64(kept[i].required)/10_000_000\n\t\t\tlj := kept[j].score +\n\t\t\t\tfloat64(kept[j].required)/10_000_000\n\t\t\treturn li < lj\n\t\t})\n\t\tfor _, old := range kept[5:] {\n\t\t\told.active = false\n\t\t}\n\t\tkept = kept[:5]\n\t}\n\n\tlabels[label.node] = kept\n\treturn label.active\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64,\n\terror) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 500_000.0\n\t\thopPenalty = 25_000.0\n\t\tmaxHops = 30\n\t\tmaxExpand = 30000\n\t)\n\n\ttarget := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidatePathLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tpq := &candidatePathQueue{}\n\theap.Push(pq, target)\n\n\texpanded := 0\n\tvar sourceLabel *candidatePathLabel\n\n\tfor pq.Len() > 0 && expanded < maxExpand {\n\t\titem := heap.Pop(pq).(*candidatePathLabel)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLabel = item\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\texpanded++\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 220_000\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\n\t\t\tlabel := &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tedge: edge,\n\t\t\t\tnext: item,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tactive: true,\n\t\t\t}\n\t\t\tif candidateInsertLabel(labels, label) {\n\t\t\t\theap.Push(pq, label)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor cur := sourceLabel; cur != nil && cur.edge != nil; cur = cur.next {\n\t\tpath = append(path, cur.edge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLabel.logProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tnode := r.source\n\tfor _, edge := range path {\n\t\tif edge.key.from != node {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, errors.New(\"path does not reach target\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations, base map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\toldAmount := base[key]\n\t\tif amount <= oldAmount {\n\t\t\tcontinue\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tif oldAmount > 0 {\n\t\t\toldP := r.liquidityProbability(edge, oldAmount)\n\t\t\tif oldP <= 0 {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\t\t\tp /= oldP\n\t\t}\n\t\tif p > 0.999 {\n\t\t\tp = 0.999\n\t\t}\n\t\tif p < 0.001 {\n\t\t\tp = 0.001\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmt {\n\t\t\tfees += rt.TotalAmount - finalAmt\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 28_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.025 * float64(len(routes)-1)\n\t}\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.018 * float64(count-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tavg := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tavg / 2,\n\t\tavg * 2 / 3,\n\t\tavg * 4 / 5,\n\t\tavg,\n\t\tavg * 6 / 5,\n\t\tavg * 3 / 2,\n\t\tavg * 2,\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailedShard * 3 / 10,\n\t\t\tr.lastFailedShard * 2 / 5,\n\t\t\tr.lastFailedShard / 2,\n\t\t\tr.lastFailedShard * 3 / 5,\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor key, edge := range r.edges {\n\t\treserved := r.reserved[key]\n\t\tbelief := r.beliefs[key]\n\n\t\tif belief.lowerOK > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen,\n\t\t\t\t(belief.lowerOK-reserved)*19/20,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > reserved {\n\t\t\tavailable := belief.upperFail - reserved\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*2/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*11/20,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen,\n\t\t\t\t(belief.estimate-reserved)*4/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\n\t\tavailable := edge.capacity - reserved\n\t\tif edge.key.from == r.source {\n\t\t\tif balance, ok := r.localBalances[edge.key.chanID]; ok {\n\t\t\t\tavailable = balance - reserved\n\t\t\t}\n\t\t}\n\t\tif available > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*4/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\tdi := math.Abs(math.Log(\n\t\t\tfloat64(sizes[i]) / float64(avg),\n\t\t))\n\t\tdj := math.Abs(math.Log(\n\t\t\tfloat64(sizes[j]) / float64(avg),\n\t\t))\n\t\tif di == dj {\n\t\t\treturn sizes[i] > sizes[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\tif len(sizes) > 12 {\n\t\tsizes = sizes[:12]\n\t}\n\n\treturn sizes\n}\n\nfunc candidateRouteSignature(rt *route.Route) uint64 {\n\tvar h uint64 = 1469598103934665603\n\tfor i, hop := range rt.Hops {\n\t\th ^= candidateMix64(\n\t\t\thop.ChannelID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateDiversityPenalty(rt *route.Route) map[candidateEdgeKey]float64 {\n\tpenalty := make(map[candidateEdgeKey]float64)\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == rt.SourcePubKey {\n\t\t\tpenalty[key] += 80_000\n\t\t} else {\n\t\t\tpenalty[key] += 450_000\n\t\t}\n\t}\n\treturn penalty\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 8\n\n\tbase := candidateCopyReservations(r.reserved)\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(base),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\tstate.reservations,\n\t\t\t)\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\tfirst, _, err := r.findRoute(size, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\toptions := []*route.Route{first}\n\t\t\t\tif depth < 3 || len(states) <= 3 {\n\t\t\t\t\talternate, _, altErr := r.findRoute(\n\t\t\t\t\t\tsize, candidateDiversityPenalty(first),\n\t\t\t\t\t)\n\t\t\t\t\tif altErr == nil &&\n\t\t\t\t\t\tcandidateRouteSignature(alternate) !=\n\t\t\t\t\t\t\tcandidateRouteSignature(first) {\n\n\t\t\t\t\t\toptions = append(options, alternate)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, rt := range options {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\trt,\n\t\t\t\t\t)\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, base,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(next.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\tif expanded[i].score == expanded[j].score {\n\t\t\t\treturn len(expanded[i].routes) <\n\t\t\t\t\tlen(expanded[j].routes)\n\t\t\t}\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\treturn nil, 0, false\n}\n\nfunc candidatePartCounts(maxParts int) []int {\n\tseen := make(map[int]bool)\n\tvar counts []int\n\tadd := func(v int) {\n\t\tif v >= 2 && v <= maxParts && !seen[v] {\n\t\t\tseen[v] = true\n\t\t\tcounts = append(counts, v)\n\t\t}\n\t}\n\n\tif maxParts <= 8 {\n\t\tfor i := 2; i <= maxParts; i++ {\n\t\t\tadd(i)\n\t\t}\n\t} else {\n\t\tfor _, value := range []int{2, 3, 4, 6, 8, 12, maxParts} {\n\t\t\tadd(value)\n\t\t}\n\t}\n\tsort.Ints(counts)\n\treturn counts\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.965) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tbase := candidateCopyReservations(r.reserved)\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(base)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(bestPlan, reservations, base)\n\t}\n\n\tfor _, count := range candidatePartCounts(int(parts)) {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil && edge.capacity > 0 {\n\t\tratio := float64(amt) / float64(edge.capacity)\n\t\tswitch {\n\t\tcase ratio >= 0.08:\n\t\t\testimate = edge.capacity * 7 / 8\n\t\tcase ratio >= 0.035:\n\t\t\testimate = edge.capacity * 3 / 4\n\t\tdefault:\n\t\t\testimate = amt * 2\n\t\t}\n\t\tif estimate > edge.capacity {\n\t\t\testimate = edge.capacity\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 18\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t\tif r.suspect[key] > 0 {\n\t\t\t\tr.suspect[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 4\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
},
|
|
"stats": {
|
|
"evals_done": 352,
|
|
"distinct_candidates": 28
|
|
},
|
|
"candidates": [
|
|
{
|
|
"id": 0,
|
|
"parent": null,
|
|
"score": 0.5278,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"role": "seed",
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tplanReusePenalty float64\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*4 + 12\n\tif r.attemptLimit < 28 {\n\t\tr.attemptLimit = 28\n\t}\n\tif r.attemptLimit > 80 {\n\t\tr.attemptLimit = 80\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probabilityWithTotal(edge *candidateEdge,\n\thtlcAmt, total lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(htlcAmt) || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tretryCeiling := failure.upper * 3 / 4\n\t\tif retryCeiling == 0 {\n\t\t\tretryCeiling = 1\n\t\t}\n\t\tif total > retryCeiling {\n\t\t\treturn 0\n\t\t}\n\n\t\tretryScale = 0.60 / math.Sqrt(float64(failure.count))\n\t\tif retryScale < 0.15 {\n\t\t\tretryScale = 0.15\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retryScale\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995 * retryScale\n\t}\n\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\tif p > 0.012 {\n\t\t\tp = 0.012\n\t\t}\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.985 - 0.97*pos\n\t\tp = 0.18*p + 0.82*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.50 {\n\t\t\tscale := 1 - 0.92*(ratio-0.50)/0.50\n\t\t\tif scale < 0.08 {\n\t\t\t\tscale = 0.08\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.48 * math.Exp(-distance/0.18)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.11 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tp = 0.68*p + 0.32*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.002 {\n\t\treturn 0.002\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treturn r.probabilityWithTotal(\n\t\tedge, amt, amt+r.reserved[edge.key],\n\t)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 420_000.0\n\t\thopPenalty = 220.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 18_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 230_000\n\n\t\t\tif r.reserved[edge.key] > 0 {\n\t\t\t\tedgeScore += r.planReusePenalty\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmax lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > max {\n\t\tvalue = max\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\ntype candidatePlanEdgeLoad struct {\n\ttotal lnwire.MilliSatoshi\n\tmax lnwire.MilliSatoshi\n\tuses uint32\n}\n\nfunc (r *candidateRouter) scorePlan(plan []*route.Route,\n\tbaseReservations map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tloads := make(map[candidateEdgeKey]candidatePlanEdgeLoad)\n\tvar fees lnwire.MilliSatoshi\n\n\tfor _, rt := range plan {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tfees += rt.TotalAmount - finalAmt\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\n\t\t\tload := loads[key]\n\t\t\tload.total += amt\n\t\t\tif amt > load.max {\n\t\t\t\tload.max = amt\n\t\t\t}\n\t\t\tload.uses++\n\t\t\tloads[key] = load\n\t\t}\n\t}\n\n\tscore := -float64(fees)/4_000_000 - 0.025*float64(len(plan))\n\n\tfor key, load := range loads {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\ttotal := baseReservations[key] + load.total\n\t\tp := r.probabilityWithTotal(edge, load.max, total)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\n\t\tif load.uses > 1 && edge.key.from != r.source {\n\t\t\tscore -= 0.22 * float64(load.uses-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) planOnce(total lnwire.MilliSatoshi,\n\tparts uint32, sizeBias, reusePenalty float64) (\n\t[]*route.Route, bool) {\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tsavedPenalty := r.planReusePenalty\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t\tr.planReusePenalty = savedPenalty\n\t}()\n\n\tr.planReusePenalty = reusePenalty\n\tremaining := total\n\tslots := parts\n\tvar plan []*route.Route\n\n\tfor remaining > 0 && slots > 0 {\n\t\tif slots == 1 {\n\t\t\trt, _, err := r.findRoute(remaining)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tplan = append(plan, rt)\n\t\t\tr.reserveRoute(rt)\n\t\t\tremaining = 0\n\t\t\tbreak\n\t\t}\n\n\t\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tvar sizes []lnwire.MilliSatoshi\n\t\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\t\tcandidateAddAmount(&sizes, seen, base/2, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*3/4, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*7/8, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*5/4, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*3/2, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*2, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*3, remaining)\n\t\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\n\t\tif r.lastFailedShard > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, r.lastFailedShard/2, remaining,\n\t\t\t)\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, r.lastFailedShard*5/8, remaining,\n\t\t\t)\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, r.lastFailedShard*3/4, remaining,\n\t\t\t)\n\t\t}\n\n\t\tvar bestRoute *route.Route\n\t\tbestSize := lnwire.MilliSatoshi(0)\n\t\tbestUtility := math.Inf(-1)\n\n\t\tfor _, size := range sizes {\n\t\t\trt, logProb, err := r.findRoute(size)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfees := rt.TotalAmount - size\n\t\t\tsizeReward := math.Log(\n\t\t\t\tfloat64(size) / float64(base),\n\t\t\t)\n\t\t\tutility := logProb + sizeBias*sizeReward -\n\t\t\t\tfloat64(fees)/4_000_000\n\n\t\t\tif bestRoute == nil || utility > bestUtility {\n\t\t\t\tbestRoute = rt\n\t\t\t\tbestSize = size\n\t\t\t\tbestUtility = utility\n\t\t\t}\n\t\t}\n\n\t\tif bestRoute == nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tplan = append(plan, bestRoute)\n\t\tr.reserveRoute(bestRoute)\n\t\tremaining -= bestSize\n\t\tslots--\n\t}\n\n\tif remaining != 0 || len(plan) == 0 {\n\t\treturn nil, false\n\t}\n\treturn plan, true\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tparts uint32) (*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\treturn rt, err\n\t}\n\n\tbase := (total + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&sizes, seen, total, total)\n\tcandidateAddAmount(&sizes, seen, total*3/4, total)\n\tcandidateAddAmount(&sizes, seen, total/2, total)\n\tcandidateAddAmount(&sizes, seen, base*3/2, total)\n\tcandidateAddAmount(&sizes, seen, base, total)\n\tcandidateAddAmount(&sizes, seen, base*3/4, total)\n\tcandidateAddAmount(&sizes, seen, base/2, total)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*3/4, total,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8, total,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2, total,\n\t\t)\n\t}\n\n\tvar best *route.Route\n\tbestUtility := math.Inf(-1)\n\n\tfor _, size := range sizes {\n\t\trt, logProb, err := r.findRoute(size)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfees := rt.TotalAmount - size\n\t\tprogress := math.Log(float64(size) / float64(base))\n\t\tutility := logProb + 0.62*progress -\n\t\t\tfloat64(fees)/4_000_000\n\n\t\tif best == nil || utility > bestUtility {\n\t\t\tbest = rt\n\t\t\tbestUtility = utility\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32, inFlight uint32) ([]*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tif inFlight == 0 && r.lastFailedShard == 0 {\n\t\tfull, logProb, err := r.findRoute(total)\n\t\tif err == nil && logProb >= math.Log(0.24) {\n\t\t\treturn []*route.Route{full}, nil\n\t\t}\n\t}\n\n\ttype planConfig struct {\n\t\tsizeBias float64\n\t\treusePenalty float64\n\t}\n\n\tconfigs := []planConfig{\n\t\t{sizeBias: 0.18, reusePenalty: 180_000},\n\t\t{sizeBias: 0.40, reusePenalty: 520_000},\n\t\t{sizeBias: 0.68, reusePenalty: 1_050_000},\n\t\t{sizeBias: 0.95, reusePenalty: 1_700_000},\n\t}\n\n\tbaseReservations := candidateCopyReservations(r.reserved)\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor _, config := range configs {\n\t\tplan, ok := r.planOnce(\n\t\t\ttotal, parts, config.sizeBias, config.reusePenalty,\n\t\t)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tscore := r.scorePlan(plan, baseReservations)\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan != nil {\n\t\treturn bestPlan, nil\n\t}\n\n\trt, err := r.fallbackShard(total, parts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{rt}, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft, inFlightHtlcs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 5 / 8\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t}\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 1,
|
|
"parent": 0,
|
|
"score": 0.1704,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(candidateVertexHash(e.key.to) +\n\t\t0x9e3779b97f4a7c15)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(uint64(e.baseFeeMsat) +\n\t\tuint64(e.feeRatePPM)<<17)\n\tx ^= candidateMix64(uint64(e.timeLockDelta) +\n\t\tuint64(e.minHTLC)<<16)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*3 + 8\n\tif r.attemptLimit < 24 {\n\t\tr.attemptLimit = 24\n\t}\n\tif r.attemptLimit > 64 {\n\t\tr.attemptLimit = 64\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treserved := r.reserved[edge.key]\n\ttotal := amt + reserved\n\n\tif !edge.policyAllows(amt) || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok {\n\t\tif failure.count >= 2 {\n\t\t\treturn 0\n\t\t}\n\t\tif failure.upper > 0 {\n\t\t\tif total >= failure.upper {\n\t\t\t\treturn 0\n\t\t\t}\n\n\t\t\tretryCeiling := failure.upper * 2 / 3\n\t\t\tif retryCeiling == 0 {\n\t\t\t\tretryCeiling = 1\n\t\t\t}\n\t\t\tif total > retryCeiling {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tretryScale = 0.35\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retryScale\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995 * retryScale\n\t}\n\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\tif p > 0.012 {\n\t\t\tp = 0.012\n\t\t}\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.985 - 0.97*pos\n\t\tp = 0.2*p + 0.8*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.55 {\n\t\t\tscale := 1 - 0.92*(ratio-0.55)/0.45\n\t\t\tif scale < 0.08 {\n\t\t\t\tscale = 0.08\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.45 * math.Exp(-distance/0.18)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.12 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tp = 0.7*p + 0.3*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.002 {\n\t\treturn 0.002\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 420_000.0\n\t\thopPenalty = 220.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 22_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 260_000\n\n\t\t\tif r.reserved[edge.key] > 0 {\n\t\t\t\tedgeScore += 260_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmax lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > max {\n\t\tvalue = max\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\nfunc (r *candidateRouter) planOnce(total lnwire.MilliSatoshi,\n\tparts uint32, sizeBias float64) ([]*route.Route, float64, bool) {\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tremaining := total\n\tslots := parts\n\tvar plan []*route.Route\n\tjointScore := 0.0\n\n\tfor remaining > 0 && slots > 0 {\n\t\tif slots == 1 {\n\t\t\trt, logProb, err := r.findRoute(remaining)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, false\n\t\t\t}\n\n\t\t\tfees := rt.TotalAmount - remaining\n\t\t\tjointScore += logProb -\n\t\t\t\tfloat64(fees)/4_000_000 - 0.025\n\t\t\tplan = append(plan, rt)\n\t\t\tr.reserveRoute(rt)\n\t\t\tremaining = 0\n\t\t\tbreak\n\t\t}\n\n\t\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tvar sizes []lnwire.MilliSatoshi\n\t\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\t\tcandidateAddAmount(&sizes, seen, base, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*5/4, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*3/2, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*2, remaining)\n\t\tcandidateAddAmount(&sizes, seen, base*3, remaining)\n\t\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\n\t\tif r.lastFailedShard > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, r.lastFailedShard*5/8,\n\t\t\t\tremaining,\n\t\t\t)\n\t\t}\n\n\t\tvar bestRoute *route.Route\n\t\tbestSize := lnwire.MilliSatoshi(0)\n\t\tbestLogProb := 0.0\n\t\tbestUtility := math.Inf(-1)\n\n\t\tfor _, size := range sizes {\n\t\t\trt, logProb, err := r.findRoute(size)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfees := rt.TotalAmount - size\n\t\t\tsizeReward := math.Log(\n\t\t\t\tfloat64(size) / float64(base),\n\t\t\t)\n\t\t\tutility := logProb + sizeBias*sizeReward -\n\t\t\t\tfloat64(fees)/4_000_000\n\n\t\t\tif bestRoute == nil || utility > bestUtility {\n\t\t\t\tbestRoute = rt\n\t\t\t\tbestSize = size\n\t\t\t\tbestLogProb = logProb\n\t\t\t\tbestUtility = utility\n\t\t\t}\n\t\t}\n\n\t\tif bestRoute == nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tfees := bestRoute.TotalAmount - bestSize\n\t\tjointScore += bestLogProb -\n\t\t\tfloat64(fees)/4_000_000 - 0.025\n\n\t\tplan = append(plan, bestRoute)\n\t\tr.reserveRoute(bestRoute)\n\t\tremaining -= bestSize\n\t\tslots--\n\t}\n\n\tif remaining != 0 || len(plan) == 0 {\n\t\treturn nil, 0, false\n\t}\n\treturn plan, jointScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32, inFlight uint32) ([]*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tif inFlight == 0 && r.lastFailedShard == 0 {\n\t\tfull, logProb, err := r.findRoute(total)\n\t\tif err == nil && logProb >= math.Log(0.22) {\n\t\t\treturn []*route.Route{full}, nil\n\t\t}\n\t}\n\n\tbiases := []float64{0.28, 0.48, 0.72}\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor _, bias := range biases {\n\t\tplan, score, ok := r.planOnce(total, parts, bias)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tif candidateFinalAmount(rt) > 0 &&\n\t\t\tcandidateFinalAmount(rt) <= amt {\n\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft, inFlightHtlcs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 5 / 8\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t}\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}\n"
|
|
}
|
|
},
|
|
{
|
|
"id": 2,
|
|
"parent": 0,
|
|
"score": 0.5081,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*3 + 10\n\tif r.attemptLimit < 20 {\n\t\tr.attemptLimit = 20\n\t}\n\tif r.attemptLimit > 64 {\n\t\tr.attemptLimit = 64\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, known := r.beliefs[edge.key]\n\n\tif known {\n\t\tswitch {\n\t\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\t\tp = 0.995\n\n\t\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\t\tif p > 0.015 {\n\t\t\t\tp = 0.015\n\t\t\t}\n\n\t\tcase belief.lowerOK > 0 &&\n\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\twidth := float64(\n\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t)\n\t\t\tpos := float64(total-belief.lowerOK) / width\n\t\t\tif pos < 0 {\n\t\t\t\tpos = 0\n\t\t\t}\n\t\t\tif pos > 1 {\n\t\t\t\tpos = 1\n\t\t\t}\n\t\t\tevidence := 0.99 - 0.975*pos\n\t\t\tp = 0.18*p + 0.82*evidence\n\n\t\tcase belief.upperFail > 0:\n\t\t\tratio := float64(total) /\n\t\t\t\tfloat64(belief.upperFail)\n\t\t\tif ratio > 0.45 {\n\t\t\t\tscale := 1 - 0.9*(ratio-0.45)/0.55\n\t\t\t\tif scale < 0.1 {\n\t\t\t\t\tscale = 0.1\n\t\t\t\t}\n\t\t\t\tp *= scale\n\t\t\t}\n\n\t\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\t\tfloat64(edge.capacity)\n\t\t\tboost := 0.5 * math.Exp(-distance/0.2)\n\t\t\tp += boost * (1 - p)\n\t\t}\n\n\t\tif belief.estimate > 0 &&\n\t\t\t!(belief.lowerOK > 0 && total <= belief.lowerOK) {\n\n\t\t\tscale := 0.1 * float64(edge.capacity)\n\t\t\tif scale < 1 {\n\t\t\t\tscale = 1\n\t\t\t}\n\t\t\tq := 1 / (1 + math.Exp(\n\t\t\t\t(float64(total)-float64(belief.estimate)) /\n\t\t\t\t\tscale,\n\t\t\t))\n\t\t\tp = 0.72*p + 0.28*q\n\t\t}\n\t}\n\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\trefresh := 0.006 /\n\t\t\t\t(1 + 0.5*float64(failure.count))\n\t\t\tif p > refresh {\n\t\t\t\tp = refresh\n\t\t\t}\n\t\t} else {\n\t\t\tratio := float64(total) /\n\t\t\t\tfloat64(failure.upper)\n\t\t\tif ratio > 0.45 {\n\t\t\t\tscale := 1 - 0.82*(ratio-0.45)/0.55\n\t\t\t\tif scale < 0.18 {\n\t\t\t\t\tscale = 0.18\n\t\t\t\t}\n\t\t\t\tp *= scale\n\t\t\t} else {\n\t\t\t\tp *= 0.8\n\t\t\t}\n\t\t}\n\t}\n\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\ttotal := amt + r.reserved[edge.key]\n\tif total < amt {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, total)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 360_000.0\n\t\thopPenalty = 3_500.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\t\t\tif sending < amtOver {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 8_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 150_000\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmax lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > max {\n\t\tvalue = max\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\nfunc (r *candidateRouter) planningSizes(remaining lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&sizes, seen, base/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/4, remaining)\n\tcandidateAddAmount(&sizes, seen, base, remaining)\n\tcandidateAddAmount(&sizes, seen, base*5/4, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*3/4, remaining,\n\t\t)\n\t}\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\tdi := sizes[i] - base\n\t\tif di < 0 {\n\t\t\tdi = -di\n\t\t}\n\t\tdj := sizes[j] - base\n\t\tif dj < 0 {\n\t\t\tdj = -dj\n\t\t}\n\t\treturn di < dj\n\t})\n\n\tif len(sizes) > 10 {\n\t\tsizes = sizes[:10]\n\t}\n\treturn sizes\n}\n\nfunc (r *candidateRouter) jointPlanScore(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotalFees lnwire.MilliSatoshi, parts int) float64 {\n\n\tscore := 0.0\n\tfor key, total := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, total)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tscore -= float64(totalFees) / 3_500_000\n\tscore -= 0.035 * float64(parts)\n\treturn score\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\ttotalFees lnwire.MilliSatoshi\n\tscore float64\n\trank float64\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tif parts == 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tinitial := candidatePlanState{\n\t\tremaining: total,\n\t\treservations: candidateCopyReservations(savedReservations),\n\t}\n\tinitial.score = r.jointPlanScore(\n\t\tinitial.reservations, 0, 0,\n\t)\n\n\tstates := []candidatePlanState{initial}\n\tvar bestComplete *candidatePlanState\n\n\tconst beamWidth = 10\n\n\tfor depth := uint32(0); depth < parts && len(states) > 0; depth++ {\n\t\tslots := parts - depth\n\t\tvar nextStates []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tif state.remaining == 0 {\n\t\t\t\tcopyState := state\n\t\t\t\tif bestComplete == nil ||\n\t\t\t\t\tcopyState.score > bestComplete.score {\n\n\t\t\t\t\tbestComplete = ©State\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsizes := r.planningSizes(state.remaining, slots)\n\t\t\tr.reserved = state.reservations\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tif size <= 0 || size > state.remaining {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\trt, _, err := r.findRoute(size)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\tfees := rt.TotalAmount - size\n\t\t\t\tif fees < 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\tremaining: state.remaining - size,\n\t\t\t\t\troutes: append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\trt,\n\t\t\t\t\t),\n\t\t\t\t\treservations: reservations,\n\t\t\t\t\ttotalFees: state.totalFees + fees,\n\t\t\t\t}\n\t\t\t\tnext.score = r.jointPlanScore(\n\t\t\t\t\tnext.reservations, next.totalFees,\n\t\t\t\t\tlen(next.routes),\n\t\t\t\t)\n\t\t\t\tif math.IsInf(next.score, -1) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tdelivered := float64(total-next.remaining) /\n\t\t\t\t\tfloat64(total)\n\t\t\t\tnext.rank = next.score + 3.0*delivered\n\n\t\t\t\tif next.remaining == 0 {\n\t\t\t\t\tcopyState := next\n\t\t\t\t\tif bestComplete == nil ||\n\t\t\t\t\t\tcopyState.score >\n\t\t\t\t\t\t\tbestComplete.score {\n\n\t\t\t\t\t\tbestComplete = ©State\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif slots > 1 {\n\t\t\t\t\tnextStates = append(nextStates, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(nextStates, func(i, j int) bool {\n\t\t\treturn nextStates[i].rank > nextStates[j].rank\n\t\t})\n\t\tif len(nextStates) > beamWidth {\n\t\t\tnextStates = nextStates[:beamWidth]\n\t\t}\n\t\tstates = nextStates\n\t}\n\n\tif bestComplete != nil && len(bestComplete.routes) > 0 {\n\t\treturn bestComplete.routes, nil\n\t}\n\n\tr.reserved = savedReservations\n\trt, _, err := r.findRoute(total)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{rt}, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\tbelief.successes++\n\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 5 / 8\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t}\n\tbelief.failures++\n\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn errors.New(\"invalid attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t\tif r.suspect[key] > 0 {\n\t\t\t\tr.suspect[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 3,
|
|
"parent": 0,
|
|
"score": 0.4535,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*2 + 8\n\tif r.attemptLimit < 12 {\n\t\tr.attemptLimit = 12\n\t}\n\tif r.attemptLimit > 36 {\n\t\tr.attemptLimit = 36\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := math.Exp(-x / 0.05)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.032))\n\tp := 0.48*lowMode + 0.52*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.25:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.38:\n\t\t\treturn 0\n\t\tcase ratio > 0.68:\n\t\t\treturn 0\n\t\tcase ratio > 0.50:\n\t\t\tretryScale = 0.16\n\t\tcase ratio > 0.34:\n\t\t\tretryScale = 0.34\n\t\tcase ratio > 0.20:\n\t\t\tretryScale = 0.58\n\t\tdefault:\n\t\t\tretryScale = 0.78\n\t\t}\n\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.55\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\tp = 0.996\n\t} else if belief.upperFail > 0 && total >= belief.upperFail {\n\t\tp = 0.006\n\t} else if belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.992 - 0.982*pos\n\t\tp = 0.12*p + 0.88*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.30 {\n\t\t\tscale := 1 - 0.94*(ratio-0.30)/0.70\n\t\t\tif scale < 0.06 {\n\t\t\t\tscale = 0.06\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.65 * math.Exp(-distance/0.22)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.08 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.28\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.40\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.0015 {\n\t\treturn 0.0015\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 330_000.0\n\t\thopPenalty = 900.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 18_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 210_000\n\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tedgeScore += 65_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\n\tfor _, rt := range routes {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmt {\n\t\t\tfees += rt.TotalAmount - finalAmt\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 5_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.105 * float64(len(routes)-1)\n\t}\n\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.025 * float64(count-1)\n\t\t}\n\t}\n\n\tif !complete && total > 0 {\n\t\tsent := total\n\t\tfor _, rt := range routes {\n\t\t\tsent -= candidateFinalAmount(rt)\n\t\t}\n\t\tprogress := 1 - float64(sent)/float64(total)\n\t\tscore += 0.45 * progress\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(\n\t\t&sizes, seen, base/2, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*3/4, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*5/4, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*3/2, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*2, minimum, maximum,\n\t)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/4,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/3,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\treturn sizes\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 7\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tinitialReservations := candidateCopyReservations(r.reserved)\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: initialReservations,\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\trt, _, err := r.findRoute(size)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\troutes := append(\n\t\t\t\t\tappend([]*route.Route(nil), state.routes...),\n\t\t\t\t\trt,\n\t\t\t\t)\n\t\t\t\tremaining := state.remaining - size\n\t\t\t\tslots := state.slots - 1\n\t\t\t\tcomplete := remaining == 0 && slots == 0\n\n\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnextState := candidatePlanState{\n\t\t\t\t\tremaining: remaining,\n\t\t\t\t\tslots: slots,\n\t\t\t\t\troutes: routes,\n\t\t\t\t\treservations: reservations,\n\t\t\t\t}\n\t\t\t\tnextState.score = r.planScore(\n\t\t\t\t\troutes, reservations, total, complete,\n\t\t\t\t)\n\t\t\t\tif math.IsInf(nextState.score, -1) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\texpanded = append(expanded, nextState)\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32, inFlight uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.72) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\tif maxPlanParts > 12 {\n\t\tmaxPlanParts = 12\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(\n\t\t\tbestPlan, reservations, total, true,\n\t\t)\n\t}\n\n\tfor count := 2; count <= maxPlanParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\n\t\tif count >= 4 && bestPlan != nil &&\n\t\t\tlen(bestPlan) < count-1 {\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\t_ = inFlight\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft, inFlightHtlcs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 7 / 8\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 18\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 4,
|
|
"parent": 0,
|
|
"score": 0.5278,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\tavoid map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(candidateVertexHash(e.key.to) +\n\t\t0x9e3779b97f4a7c15)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(uint64(e.baseFeeMsat) +\n\t\tuint64(e.feeRatePPM)<<17)\n\tx ^= candidateMix64(uint64(e.timeLockDelta) +\n\t\tuint64(e.minHTLC)<<16)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\tavoid: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*2 + 8\n\tif r.attemptLimit < 16 {\n\t\tr.attemptLimit = 16\n\t}\n\tif r.attemptLimit > 40 {\n\t\tr.attemptLimit = 40\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif !edge.policyAllows(amt) || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase ratio > 0.80:\n\t\t\tretryScale = 0.08\n\t\tcase ratio > 0.66:\n\t\t\tretryScale = 0.22\n\t\tcase ratio > 0.50:\n\t\t\tretryScale = 0.55\n\t\tdefault:\n\t\t\tretryScale = 0.88\n\t\t}\n\n\t\tif failure.count > 1 {\n\t\t\tretryScale *= math.Pow(\n\t\t\t\t0.82, float64(failure.count-1),\n\t\t\t)\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retryScale\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995 * retryScale\n\t}\n\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\tif p > 0.010 {\n\t\t\tp = 0.010\n\t\t}\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.99 - 0.98*pos\n\t\tp = 0.15*p + 0.85*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.45 {\n\t\t\tscale := 1 - 0.94*(ratio-0.45)/0.55\n\t\t\tif scale < 0.06 {\n\t\t\t\tscale = 0.06\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.52 * math.Exp(-distance/0.16)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.10 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tp = 0.72*p + 0.28*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\ntype candidateLabel struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\n\tnextEdge *candidateEdge\n\tnextLabel *candidateLabel\n\tactive bool\n}\n\ntype candidateLabelQueue []*candidateLabel\n\nfunc (q candidateLabelQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateLabelQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateLabelQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateLabelQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateLabel))\n}\n\nfunc (q *candidateLabelQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidateAcceptLabel(labels map[route.Vertex][]*candidateLabel,\n\tlabel *candidateLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif old.score <= label.score &&\n\t\t\told.required <= label.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor _, old := range current {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif label.score <= old.score &&\n\t\t\tlabel.required <= old.required {\n\n\t\t\told.active = false\n\t\t}\n\t}\n\n\tactive := 0\n\tfor _, old := range current {\n\t\tif old.active {\n\t\t\tactive++\n\t\t}\n\t}\n\n\tconst maxLabels = 8\n\tif active >= maxLabels {\n\t\tvar worst *candidateLabel\n\t\tworstRank := math.Inf(-1)\n\n\t\tfor _, old := range current {\n\t\t\tif !old.active {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trank := old.score + float64(old.required)/1000\n\t\t\tif rank > worstRank {\n\t\t\t\tworst = old\n\t\t\t\tworstRank = rank\n\t\t\t}\n\t\t}\n\n\t\tnewRank := label.score + float64(label.required)/1000\n\t\tif newRank >= worstRank {\n\t\t\treturn false\n\t\t}\n\t\tworst.active = false\n\t}\n\n\tlabel.active = true\n\tlabels[label.node] = append(current, label)\n\treturn true\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 440_000.0\n\t\thopPenalty = 180.0\n\t)\n\n\tstart := &candidateLabel{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidateLabel{\n\t\tr.spec.Target: {start},\n\t}\n\tpq := &candidateLabelQueue{}\n\theap.Push(pq, start)\n\n\tvar sourceLabel *candidateLabel\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateLabel)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLabel = item\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 32_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 300_000\n\t\t\tedgeScore += float64(r.avoid[edge.key]) * 180_000\n\n\t\t\tif r.reserved[edge.key] > 0 {\n\t\t\t\tedgeScore += 360_000\n\t\t\t}\n\n\t\t\tlabel := &candidateLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tnextEdge: edge,\n\t\t\t\tnextLabel: item,\n\t\t\t}\n\t\t\tif candidateAcceptLabel(labels, label) {\n\t\t\t\theap.Push(pq, label)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor label := sourceLabel; label.node != r.spec.Target; {\n\t\tif label.nextEdge == nil || label.nextLabel == nil {\n\t\t\treturn nil, 0, errors.New(\"broken route label\")\n\t\t}\n\t\tpath = append(path, label.nextEdge)\n\t\tlabel = label.nextLabel\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLabel.logProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tnode := r.source\n\tvisited := make(map[route.Vertex]bool)\n\tfor _, edge := range path {\n\t\tif visited[node] || edge.key.from != node {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, fmt.Errorf(\"route does not reach target\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateCopyCounts(\n\tsrc map[candidateEdgeKey]uint32,\n) map[candidateEdgeKey]uint32 {\n\n\tdst := make(map[candidateEdgeKey]uint32, len(src))\n\tfor key, count := range src {\n\t\tdst[key] = count\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\ntype candidateRouteChoice struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeChoices(\n\tamt lnwire.MilliSatoshi) []candidateRouteChoice {\n\n\tfirst, firstProb, err := r.findRoute(amt)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tchoices := []candidateRouteChoice{{\n\t\troute: first,\n\t\tlogProb: firstProb,\n\t}}\n\n\tsaved := candidateCopyCounts(r.avoid)\n\tfor i := range first.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(first, i)\n\t\tif ok {\n\t\t\tr.avoid[key] += 2\n\t\t}\n\t}\n\n\tsecond, secondProb, err := r.findRoute(amt)\n\tr.avoid = saved\n\tif err == nil {\n\t\tchoices = append(choices, candidateRouteChoice{\n\t\t\troute: second,\n\t\t\tlogProb: secondProb,\n\t\t})\n\t}\n\n\treturn choices\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmax lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > max {\n\t\tvalue = max\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\tcandidateAddAmount(&sizes, seen, base, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*2, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining/2, remaining)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*2/3, remaining,\n\t\t)\n\t}\n\n\treturn sizes\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutes []*route.Route\n\tscore float64\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tvar best *candidatePlanState\n\tconst beamWidth = 5\n\n\tfor depth := uint32(0); depth < parts && len(states) > 0; depth++ {\n\t\tvar next []candidatePlanState\n\t\tslots := parts - depth\n\n\t\tfor _, state := range states {\n\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\tstate.reservations,\n\t\t\t)\n\n\t\t\tfor _, size := range r.shardSizes(\n\t\t\t\tstate.remaining, slots,\n\t\t\t) {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\tfor _, choice := range r.routeChoices(size) {\n\t\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tr.reserveRoute(choice.route)\n\n\t\t\t\t\tfees := choice.route.TotalAmount - size\n\t\t\t\t\tscore := state.score + choice.logProb -\n\t\t\t\t\t\tfloat64(fees)/4_000_000 -\n\t\t\t\t\t\t0.012*float64(len(choice.route.Hops))\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\t[]*route.Route(nil), state.routes...,\n\t\t\t\t\t)\n\t\t\t\t\troutes = append(routes, choice.route)\n\n\t\t\t\t\tchild := candidatePlanState{\n\t\t\t\t\t\tremaining: state.remaining - size,\n\t\t\t\t\t\treservations: candidateCopyReservations(\n\t\t\t\t\t\t\tr.reserved,\n\t\t\t\t\t\t),\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\tscore: score,\n\t\t\t\t\t}\n\n\t\t\t\t\tif child.remaining == 0 {\n\t\t\t\t\t\tif best == nil ||\n\t\t\t\t\t\t\tchild.score > best.score {\n\n\t\t\t\t\t\t\tcopy := child\n\t\t\t\t\t\t\tbest = ©\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tnext = append(next, child)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(next, func(i, j int) bool {\n\t\t\treturn next[i].score > next[j].score\n\t\t})\n\t\tif len(next) > beamWidth {\n\t\t\tnext = next[:beamWidth]\n\t\t}\n\t\tstates = next\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best.routes, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\tif belief.estimate == 0 {\n\t\tbelief.estimate = amt\n\t} else if amt > belief.estimate {\n\t\tbelief.estimate = (belief.estimate + 2*amt) / 3\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 9 / 16\n\tif belief.lowerOK > 0 && estimate < belief.lowerOK {\n\t\testimate = belief.lowerOK\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t} else {\n\t\tbelief.estimate = (2*belief.estimate + estimate) / 3\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 5,
|
|
"parent": 0,
|
|
"score": 0.1788,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*2 + 6\n\tif r.attemptLimit < 10 {\n\t\tr.attemptLimit = 10\n\t}\n\tif r.attemptLimit > 48 {\n\t\tr.attemptLimit = 48\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.052)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.034))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.32:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.48:\n\t\t\treturn 0\n\t\tcase ratio > 0.72:\n\t\t\treturn 0\n\t\tcase ratio > 0.58:\n\t\t\tretryScale = 0.20\n\t\tcase ratio > 0.44:\n\t\t\tretryScale = 0.42\n\t\tcase ratio > 0.28:\n\t\t\tretryScale = 0.66\n\t\tdefault:\n\t\t\tretryScale = 0.84\n\t\t}\n\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.72\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.004\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence := 0.994 - 0.988*pos\n\t\tp = 0.15*p + 0.85*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.25 {\n\t\t\tscale := 1 - 0.90*(ratio-0.25)/0.75\n\t\t\tif scale < 0.10 {\n\t\t\t\tscale = 0.10\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.58 * math.Exp(-distance/0.20)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.075 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.16\n\t\tevidenceCount := belief.successes + belief.failures\n\t\tif evidenceCount >= 2 {\n\t\t\tweight = 0.24\n\t\t}\n\t\tif evidenceCount >= 4 {\n\t\t\tweight = 0.32\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 540_000.0\n\t\thopPenalty = 13_000.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 270_000\n\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif reserved > 0 && edge.key.from != r.source {\n\t\t\t\tload := float64(reserved) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t\tif load > 0.95 {\n\t\t\t\t\tload = 0.95\n\t\t\t\t}\n\t\t\t\tedgeScore += 90_000 +\n\t\t\t\t\t260_000*load/(1.05-load)\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\n\tfor _, rt := range routes {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmt {\n\t\t\tfees += rt.TotalAmount - finalAmt\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 30_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.035 * float64(len(routes)-1)\n\t}\n\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.012 * float64(count-1)\n\t\t}\n\t}\n\n\tif !complete && total > 0 {\n\t\tunsent := total\n\t\tfor _, rt := range routes {\n\t\t\tunsent -= candidateFinalAmount(rt)\n\t\t}\n\t\tprogress := 1 - float64(unsent)/float64(total)\n\t\tscore += 0.30 * progress\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*2/3, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*4/5, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*6/5, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*3/2, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*2, minimum, maximum,\n\t)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/4,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/3,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*2/5,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\treturn sizes\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 6\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\trt, _, err := r.findRoute(size)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\troutes := append(\n\t\t\t\t\tappend([]*route.Route(nil), state.routes...),\n\t\t\t\t\trt,\n\t\t\t\t)\n\t\t\t\tremaining := state.remaining - size\n\t\t\t\tslots := state.slots - 1\n\t\t\t\tcomplete := remaining == 0 && slots == 0\n\n\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnextState := candidatePlanState{\n\t\t\t\t\tremaining: remaining,\n\t\t\t\t\tslots: slots,\n\t\t\t\t\troutes: routes,\n\t\t\t\t\treservations: reservations,\n\t\t\t\t}\n\t\t\t\tnextState.score = r.planScore(\n\t\t\t\t\troutes, reservations, total, complete,\n\t\t\t\t)\n\t\t\t\tif math.IsInf(nextState.score, -1) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\texpanded = append(expanded, nextState)\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\tif expanded[i].score == expanded[j].score {\n\t\t\t\treturn len(expanded[i].routes) <\n\t\t\t\t\tlen(expanded[j].routes)\n\t\t\t}\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.94) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(\n\t\t\tbestPlan, reservations, total, true,\n\t\t)\n\t}\n\n\tfor count := 2; count <= maxPlanParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil && edge.capacity > 0 {\n\t\tratio := float64(amt) / float64(edge.capacity)\n\t\tswitch {\n\t\tcase ratio >= 0.08:\n\t\t\testimate = edge.capacity * 7 / 8\n\t\tcase ratio >= 0.035:\n\t\t\testimate = edge.capacity * 3 / 4\n\t\tdefault:\n\t\t\tprobeEstimate := amt * 2\n\t\t\tif probeEstimate > estimate {\n\t\t\t\testimate = probeEstimate\n\t\t\t}\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 18\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t\tif r.suspect[key] > 0 {\n\t\t\t\tr.suspect[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 4\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 6,
|
|
"parent": 0,
|
|
"score": 0.6318,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*4 + 12\n\tif r.attemptLimit < 24 {\n\t\tr.attemptLimit = 24\n\t}\n\tif r.attemptLimit > 80 {\n\t\tr.attemptLimit = 80\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\ttotal := amt + reserved\n\tif total < amt || total > edge.capacity {\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase ratio > 0.80:\n\t\t\tretryScale = 0.10\n\t\tcase ratio > 0.62:\n\t\t\tretryScale = 0.32\n\t\tcase ratio > 0.45:\n\t\t\tretryScale = 0.58\n\t\tdefault:\n\t\t\tretryScale = 0.78\n\t\t}\n\t\tif failure.count > 2 {\n\t\t\tretryScale *= 0.8\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retryScale\n\t}\n\n\tprior := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn prior * retryScale\n\t}\n\n\tp := prior\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\tp = 0.995\n\t} else if belief.upperFail > 0 && total >= belief.upperFail {\n\t\tp = 0.006\n\t} else if belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence := 0.992 - 0.984*pos\n\t\tp = 0.15*prior + 0.85*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.40 {\n\t\t\tscale := 1 - 0.94*(ratio-0.40)/0.60\n\t\t\tif scale < 0.06 {\n\t\t\t\tscale = 0.06\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.52 * math.Exp(-distance/0.16)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.10 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tweight := 0.22\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.32\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.002 {\n\t\treturn 0.002\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\n\tvia *candidateEdge\n\tdown *candidateDijkstraItem\n\tactive bool\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\tif q[i].score == q[j].score {\n\t\treturn q[i].required < q[j].required\n\t}\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\titem.idx = -1\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidateAddLabel(\n\tlabels map[route.Vertex][]*candidateDijkstraItem,\n\titem *candidateDijkstraItem) bool {\n\n\tconst maxLabels = 8\n\n\tcurrent := labels[item.node]\n\tfor _, old := range current {\n\t\tif old.active && old.score <= item.score &&\n\t\t\told.required <= item.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := current[:0]\n\tfor _, old := range current {\n\t\tif old.active && item.score <= old.score &&\n\t\t\titem.required <= old.required {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tif old.active {\n\t\t\tkept = append(kept, old)\n\t\t}\n\t}\n\n\titem.active = true\n\tkept = append(kept, item)\n\n\tif len(kept) > maxLabels {\n\t\tminRequired := kept[0]\n\t\tfor _, candidate := range kept[1:] {\n\t\t\tif candidate.required < minRequired.required {\n\t\t\t\tminRequired = candidate\n\t\t\t}\n\t\t}\n\n\t\tvar worst *candidateDijkstraItem\n\t\tfor _, candidate := range kept {\n\t\t\tif candidate == minRequired && len(kept) > 1 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst == nil || candidate.score > worst.score {\n\t\t\t\tworst = candidate\n\t\t\t}\n\t\t}\n\t\tif worst == nil {\n\t\t\tworst = kept[len(kept)-1]\n\t\t}\n\t\tworst.active = false\n\n\t\tfiltered := kept[:0]\n\t\tfor _, candidate := range kept {\n\t\t\tif candidate.active {\n\t\t\t\tfiltered = append(filtered, candidate)\n\t\t\t}\n\t\t}\n\t\tkept = filtered\n\t}\n\n\tlabels[item.node] = kept\n\treturn item.active\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 420_000.0\n\t\thopPenalty = 220.0\n\t)\n\n\ttarget := &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t\tactive: true,\n\t}\n\n\tlabels := make(map[route.Vertex][]*candidateDijkstraItem)\n\tlabels[r.spec.Target] = []*candidateDijkstraItem{target}\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, target)\n\n\tvar sourceItem *candidateDijkstraItem\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceItem = item\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\t\t\tif sending < amtOver {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 20_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 220_000\n\n\t\t\tif reserved := r.reserved[edge.key]; reserved > 0 {\n\t\t\t\treusePenalty := 280_000.0\n\t\t\t\ttotal := reserved + amtOver\n\t\t\t\tif edge.key.from == r.source {\n\t\t\t\t\treusePenalty = 25_000\n\t\t\t\t} else if belief, ok := r.beliefs[edge.key]; ok &&\n\t\t\t\t\tbelief.lowerOK >= total {\n\n\t\t\t\t\treusePenalty = 45_000\n\t\t\t\t}\n\t\t\t\tedgeScore += reusePenalty\n\t\t\t}\n\n\t\t\tnext := &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tvia: edge,\n\t\t\t\tdown: item,\n\t\t\t}\n\t\t\tif candidateAddLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceItem == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\tfor item := sourceItem; item.node != r.spec.Target; {\n\t\tif item.via == nil || item.down == nil {\n\t\t\treturn nil, 0, errors.New(\"broken route label\")\n\t\t}\n\t\tif visited[item.node] {\n\t\t\treturn nil, 0, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[item.node] = true\n\t\tpath = append(path, item.via)\n\t\titem = item.down\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceItem.logProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tnode := r.source\n\tfor _, edge := range path {\n\t\tif edge.key.from != node {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, errors.New(\"route does not reach target\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmax lnwire.MilliSatoshi) {\n\n\tif value <= 0 || max <= 0 {\n\t\treturn\n\t}\n\tif value > max {\n\t\tvalue = max\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\nfunc (r *candidateRouter) planningSizes(\n\tremaining lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&sizes, seen, base/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/4, remaining)\n\tcandidateAddAmount(&sizes, seen, base, remaining)\n\tcandidateAddAmount(&sizes, seen, base*5/4, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*2, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/3, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*3/4, remaining,\n\t\t)\n\t}\n\n\tvar evidence []lnwire.MilliSatoshi\n\tevidenceSeen := make(map[lnwire.MilliSatoshi]bool)\n\taddEvidence := func(value lnwire.MilliSatoshi) {\n\t\tif value < base/3 || value > remaining {\n\t\t\treturn\n\t\t}\n\t\tif !evidenceSeen[value] {\n\t\t\tevidenceSeen[value] = true\n\t\t\tevidence = append(evidence, value)\n\t\t}\n\t}\n\n\tfor key, belief := range r.beliefs {\n\t\treserved := r.reserved[key]\n\t\tif belief.lowerOK > reserved {\n\t\t\tavailable := belief.lowerOK - reserved\n\t\t\taddEvidence(available * 9 / 10)\n\t\t\taddEvidence(available)\n\t\t}\n\t\tif belief.upperFail > reserved {\n\t\t\tavailable := belief.upperFail - reserved\n\t\t\taddEvidence(available / 2)\n\t\t\taddEvidence(available * 5 / 8)\n\t\t}\n\t\tif belief.estimate > reserved {\n\t\t\tavailable := belief.estimate - reserved\n\t\t\taddEvidence(available * 4 / 5)\n\t\t}\n\t}\n\n\tfor key, failure := range r.currentFails {\n\t\treserved := r.reserved[key]\n\t\tif failure.upper > reserved {\n\t\t\tavailable := failure.upper - reserved\n\t\t\taddEvidence(available / 2)\n\t\t\taddEvidence(available * 5 / 8)\n\t\t}\n\t}\n\n\tfor _, edges := range r.incomingEdges {\n\t\tfor _, edge := range edges {\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\t\t\tif ok && balance > reserved {\n\t\t\t\t\taddEvidence((balance - reserved) * 19 / 20)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlimit := edge.capacity\n\t\t\tif edge.maxHTLC > 0 && edge.maxHTLC < limit {\n\t\t\t\tlimit = edge.maxHTLC\n\t\t\t}\n\t\t\tif limit > reserved {\n\t\t\t\taddEvidence((limit - reserved) * 17 / 20)\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Slice(evidence, func(i, j int) bool {\n\t\tdi := math.Abs(math.Log(\n\t\t\tfloat64(evidence[i]) / float64(base),\n\t\t))\n\t\tdj := math.Abs(math.Log(\n\t\t\tfloat64(evidence[j]) / float64(base),\n\t\t))\n\t\tif di == dj {\n\t\t\treturn evidence[i] > evidence[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\n\tif len(evidence) > 6 {\n\t\tevidence = evidence[:6]\n\t}\n\tfor _, value := range evidence {\n\t\tcandidateAddAmount(&sizes, seen, value, remaining)\n\t}\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\treturn sizes[i] > sizes[j]\n\t})\n\treturn sizes\n}\n\nfunc (r *candidateRouter) planOnce(total lnwire.MilliSatoshi,\n\tparts uint32, sizeBias float64) ([]*route.Route, float64, bool) {\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tremaining := total\n\tslots := parts\n\tvar plan []*route.Route\n\tjointScore := 0.0\n\n\tfor remaining > 0 && slots > 0 {\n\t\tsizes := r.planningSizes(remaining, slots)\n\t\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tvar bestRoute *route.Route\n\t\tbestSize := lnwire.MilliSatoshi(0)\n\t\tbestLogProb := 0.0\n\t\tbestUtility := math.Inf(-1)\n\n\t\tfor _, size := range sizes {\n\t\t\trt, logProb, err := r.findRoute(size)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfees := rt.TotalAmount - size\n\t\t\tsizeReward := math.Log(\n\t\t\t\tfloat64(size) / float64(base),\n\t\t\t)\n\t\t\tutility := logProb + sizeBias*sizeReward -\n\t\t\t\tfloat64(fees)/4_000_000\n\n\t\t\tif bestRoute == nil || utility > bestUtility {\n\t\t\t\tbestRoute = rt\n\t\t\t\tbestSize = size\n\t\t\t\tbestLogProb = logProb\n\t\t\t\tbestUtility = utility\n\t\t\t}\n\t\t}\n\n\t\tif bestRoute == nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tfees := bestRoute.TotalAmount - bestSize\n\t\tjointScore += bestLogProb -\n\t\t\tfloat64(fees)/4_000_000 - 0.04\n\n\t\tplan = append(plan, bestRoute)\n\t\tr.reserveRoute(bestRoute)\n\t\tremaining -= bestSize\n\t\tslots--\n\t}\n\n\tif remaining != 0 || len(plan) == 0 {\n\t\treturn nil, 0, false\n\t}\n\treturn plan, jointScore, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif rt, logProb, err := r.findRoute(total); err == nil {\n\t\tfees := rt.TotalAmount - total\n\t\tbestPlan = []*route.Route{rt}\n\t\tbestScore = logProb - float64(fees)/4_000_000 - 0.04\n\t}\n\n\tif parts > 1 {\n\t\tfor _, bias := range []float64{0.8, 1.8, 3.5} {\n\t\t\tplan, score, ok := r.planOnce(total, parts, bias)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif bestPlan == nil || score > bestScore {\n\t\t\t\tbestPlan = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif bestPlan == nil && parts > 1 {\n\t\tbase := (total + lnwire.MilliSatoshi(parts) - 1) /\n\t\t\tlnwire.MilliSatoshi(parts)\n\t\tbestUtility := math.Inf(-1)\n\n\t\tfor _, size := range r.planningSizes(total, parts) {\n\t\t\trt, logProb, err := r.findRoute(size)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfees := rt.TotalAmount - size\n\t\t\tutility := logProb +\n\t\t\t\t1.4*math.Log(float64(size)/float64(base)) -\n\t\t\t\tfloat64(fees)/4_000_000\n\t\t\tif bestPlan == nil || utility > bestUtility {\n\t\t\t\tbestPlan = []*route.Route{rt}\n\t\t\t\tbestUtility = utility\n\t\t\t}\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tsort.SliceStable(bestPlan, func(i, j int) bool {\n\t\treturn candidateFinalAmount(bestPlan[i]) >\n\t\t\tcandidateFinalAmount(bestPlan[j])\n\t})\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 5 / 8\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t}\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 7,
|
|
"parent": 0,
|
|
"score": 0.4576,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = 18 + int(maxParts)*4\n\tif r.attemptLimit < 24 {\n\t\tr.attemptLimit = 24\n\t}\n\tif r.attemptLimit > 60 {\n\t\tr.attemptLimit = 60\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.045)\n\thighMode := 1 / (1 + math.Exp((x-0.92)/0.035))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase ratio > 0.78:\n\t\t\tretryScale = 0.025\n\t\tcase ratio > 0.62:\n\t\t\tretryScale = 0.10\n\t\tcase ratio > 0.48:\n\t\t\tretryScale = 0.24\n\t\tcase ratio > 0.34:\n\t\t\tretryScale = 0.43\n\t\tcase ratio > 0.22:\n\t\t\tretryScale = 0.67\n\t\tdefault:\n\t\t\tretryScale = 0.84\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.68\n\t\t}\n\t\tif failure.count >= 4 {\n\t\t\tretryScale *= 0.55\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.005\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(total-belief.lowerOK) / width\n\t\tif position < 0 {\n\t\t\tposition = 0\n\t\t}\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\t\tevidence := 0.994 - 0.988*position\n\t\tp = 0.10*p + 0.90*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.22 {\n\t\t\tscale := 1 - 0.94*(ratio-0.22)/0.78\n\t\t\tif scale < 0.05 {\n\t\t\t\tscale = 0.05\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.68 * math.Exp(-distance/0.20)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.075 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\testimateProbability := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.24\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.36\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateProbability\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(value any) {\n\titem := value.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 260_000.0\n\t\thopPenalty = 700.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tbestScore[r.spec.Target] = 0\n\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProbability float64\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProbability = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOverEdge := item.required\n\t\t\tprobability := r.probability(edge, amountOverEdge)\n\t\t\tif probability <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\tsending := amountOverEdge + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(probability)) +\n\t\t\t\thopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 11_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 42_000\n\t\t\tedgeScore += extraPenalty[edge.key]\n\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tedgeScore += 22_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\toldScore, found := bestScore[edge.key.from]\n\t\t\tif found && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(probability),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLogProbability, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] + outgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tchannelIndex int) (lnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\n\thop := rt.Hops[channelIndex]\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\thash := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\thash ^= candidateMix64(\n\t\t\tkey.chanID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\thash ^= candidateMix64(candidateVertexHash(key.to))\n\t}\n\treturn candidateMix64(hash)\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tcopy := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tcopy[key] = amount\n\t}\n\treturn copy\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&values, seen, base, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base*3/4, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base*5/4, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base/2, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base*3/2, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base*2, minimum, maximum)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/4, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/3, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/2, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard*5/8,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tfor _, edge := range r.edges {\n\t\tif edge.key.to != r.spec.Target &&\n\t\t\tedge.key.from != r.source {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, edge.capacity*9/20, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, edge.capacity*4/5, minimum, maximum,\n\t\t)\n\n\t\tif belief, ok := r.beliefs[edge.key]; ok {\n\t\t\tif belief.lowerOK > 0 {\n\t\t\t\tcandidateAddAmount(\n\t\t\t\t\t&values, seen, belief.lowerOK,\n\t\t\t\t\tminimum, maximum,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif belief.upperFail > 0 {\n\t\t\t\tcandidateAddAmount(\n\t\t\t\t\t&values, seen, belief.upperFail*3/5,\n\t\t\t\t\tminimum, maximum,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\tdi := math.Abs(float64(values[i] - base))\n\t\tdj := math.Abs(float64(values[j] - base))\n\t\treturn di < dj\n\t})\n\n\tconst maximumCandidates = 14\n\tif len(values) > maximumCandidates {\n\t\tvalues = values[:maximumCandidates]\n\t}\n\n\treturn values\n}\n\ntype candidateRouteOption struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeOptions(\n\tamt lnwire.MilliSatoshi) []candidateRouteOption {\n\n\tconst optionCount = 3\n\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\tvar options []candidateRouteOption\n\n\tfor option := 0; option < optionCount; option++ {\n\t\trt, logProbability, err := r.findRoute(amt, penalties)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\toptions = append(options, candidateRouteOption{\n\t\t\t\troute: rt,\n\t\t\t\tlogProb: logProbability,\n\t\t\t})\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpenalty := 420_000.0\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalty = 150_000\n\t\t\t}\n\t\t\tpenalties[key] += penalty\n\t\t}\n\t}\n\n\treturn options\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tprobability := r.liquidityProbability(edge, amount)\n\t\tif probability <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(probability)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 6_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.045 * float64(len(routes)-1)\n\t}\n\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.045 * float64(count-1)\n\t\t}\n\t}\n\n\tif !complete && total > 0 {\n\t\tsent := total\n\t\tfor _, rt := range routes {\n\t\t\tsent -= candidateFinalAmount(rt)\n\t\t}\n\t\tprogress := 1 - float64(sent)/float64(total)\n\t\tscore += 0.35 * progress\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 10\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\toptions := r.routeOptions(size)\n\t\t\t\tfor _, option := range options {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(\n\t\t\t\t\t\treservations, option.route,\n\t\t\t\t\t)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\toption.route,\n\t\t\t\t\t)\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\n\t\t\t\t\tif remaining < 0 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnextState := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnextState.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, total,\n\t\t\t\t\t\tremaining == 0,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(nextState.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\texpanded = append(expanded, nextState)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.SliceStable(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProbability, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil &&\n\t\tfullLogProbability >= math.Log(0.82) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\tif maxPlanParts > 16 {\n\t\tmaxPlanParts = 16\n\t}\n\tif lnwire.MilliSatoshi(maxPlanParts) > total {\n\t\tmaxPlanParts = int(total)\n\t}\n\tif maxPlanParts < 1 {\n\t\treturn nil, errors.New(\"payment amount too small\")\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(\n\t\t\tbestPlan, reservations, total, true,\n\t\t)\n\t}\n\n\tfor count := 2; count <= maxPlanParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.policyBlocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || !edge.policyAllows(amount) {\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > edge.capacity {\n\t\t\treturn false\n\t\t}\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalances[key.chanID]\n\t\t\tif !ok || total > balance {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\tif failure, ok := r.currentFails[key]; ok &&\n\t\t\tfailure.upper > 0 && total >= failure.upper {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt && r.routeFits(rt) {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 17 / 20\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 4\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 20\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tpassedEdges := failureIndex\n\tif passedEdges > len(rt.Hops) {\n\t\tpassedEdges = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < passedEdges; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamountOverEdge, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amountOverEdge + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 3\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 8,
|
|
"parent": 1,
|
|
"score": 0.3677,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\tbaseFee lnwire.MilliSatoshi\n\tfeeRate lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFee + amt*e.feeRate/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFee) + uint64(e.feeRate)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = 16 + int(maxParts)*3\n\tif r.attemptLimit < 20 {\n\t\tr.attemptLimit = 20\n\t}\n\tif r.attemptLimit > 48 {\n\t\tr.attemptLimit = 48\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFee: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRate: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tif _, ok := r.edges[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.042)\n\thighMode := 1 / (1 + math.Exp((x-0.91)/0.034))\n\tp := 0.45*lowMode + 0.55*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, known := r.beliefs[edge.key]\n\n\tif known {\n\t\tswitch {\n\t\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\t\tp = 0.996\n\n\t\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\t\tp = 0.0045\n\n\t\tcase belief.lowerOK > 0 &&\n\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\t\tposition := float64(total-belief.lowerOK) / width\n\t\t\tif position < 0 {\n\t\t\t\tposition = 0\n\t\t\t}\n\t\t\tif position > 1 {\n\t\t\t\tposition = 1\n\t\t\t}\n\t\t\tevidence := 0.995 - 0.990*position\n\t\t\tp = 0.08*p + 0.92*evidence\n\n\t\tcase belief.upperFail > 0:\n\t\t\tratio := float64(total) / float64(belief.upperFail)\n\t\t\tif ratio > 0.18 {\n\t\t\t\tscale := 1 - 0.95*(ratio-0.18)/0.82\n\t\t\t\tif scale < 0.045 {\n\t\t\t\t\tscale = 0.045\n\t\t\t\t}\n\t\t\t\tp *= scale\n\t\t\t}\n\n\t\tcase belief.lowerOK > 0:\n\t\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\t\tfloat64(edge.capacity)\n\t\t\tif distance > 0 {\n\t\t\t\tboost := 0.72 * math.Exp(-distance/0.18)\n\t\t\t\tp += boost * (1 - p)\n\t\t\t}\n\t\t}\n\n\t\tif belief.estimate > 0 {\n\t\t\tscale := 0.065 * float64(edge.capacity)\n\t\t\tif scale < 1 {\n\t\t\t\tscale = 1\n\t\t\t}\n\t\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t\t))\n\n\t\t\tweight := 0.22\n\t\t\tif belief.successes+belief.failures >= 3 {\n\t\t\t\tweight = 0.34\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*estimateP\n\t\t}\n\t}\n\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\tif failure.count >= 4 {\n\t\t\t\treturn 0\n\t\t\t}\n\n\t\t\tretry := 0.020\n\t\t\tif failure.count == 2 {\n\t\t\t\tretry = 0.008\n\t\t\t} else if failure.count == 3 {\n\t\t\t\tretry = 0.003\n\t\t\t}\n\n\t\t\tovershoot := float64(total) / float64(failure.upper)\n\t\t\tif overshoot > 1.15 {\n\t\t\t\tretry *= math.Exp(-(overshoot - 1.15) * 4)\n\t\t\t}\n\t\t\tp *= retry\n\t\t} else {\n\t\t\tratio := float64(total) / float64(failure.upper)\n\t\t\tscale := 0.90\n\t\t\tswitch {\n\t\t\tcase ratio > 0.80:\n\t\t\t\tscale = 0.035\n\t\t\tcase ratio > 0.65:\n\t\t\t\tscale = 0.12\n\t\t\tcase ratio > 0.50:\n\t\t\t\tscale = 0.28\n\t\t\tcase ratio > 0.35:\n\t\t\t\tscale = 0.52\n\t\t\tcase ratio > 0.20:\n\t\t\t\tscale = 0.74\n\t\t\t}\n\t\t\tif failure.count >= 2 {\n\t\t\t\tscale *= 0.72\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t}\n\n\tif p < 0.00001 {\n\t\treturn 0.00001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(value any) {\n\titem := value.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 245_000.0\n\t\thopPenalty = 650.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tbestScore[r.spec.Target] = 0\n\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProbability float64\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProbability = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOverEdge := item.required\n\t\t\tprobability := r.probability(edge, amountOverEdge)\n\t\t\tif probability <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\tsending := amountOverEdge + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(probability)) +\n\t\t\t\thopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 8_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 48_000\n\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tedgeScore += 35_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\toldScore, found := bestScore[edge.key.from]\n\t\t\tif found && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(probability),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLogProbability, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] + outgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tchannelIndex int) (lnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\n\thop := rt.Hops[channelIndex]\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\th := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\th ^= candidateMix64(\n\t\t\tkey.chanID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th ^= candidateMix64(candidateVertexHash(key.to))\n\t}\n\treturn candidateMix64(h)\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) edgeOffer(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tavailable := edge.capacity - r.reserved[edge.key]\n\tif available <= 0 {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok {\n\t\t\treturn 0\n\t\t}\n\t\tbalance -= r.reserved[edge.key]\n\t\tif balance < available {\n\t\t\tavailable = balance\n\t\t}\n\t}\n\n\tbelief := r.beliefs[edge.key]\n\tswitch {\n\tcase belief.lowerOK > r.reserved[edge.key]:\n\t\tproven := belief.lowerOK - r.reserved[edge.key]\n\t\tif proven < available {\n\t\t\tavailable = proven\n\t\t}\n\n\tcase belief.upperFail > r.reserved[edge.key]:\n\t\tlikely := (belief.upperFail-r.reserved[edge.key])*3/5\n\t\tif likely < available {\n\t\t\tavailable = likely\n\t\t}\n\n\tdefault:\n\t\tavailable = available * 4 / 5\n\t}\n\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > r.reserved[edge.key] {\n\n\t\tretry := (failure.upper - r.reserved[edge.key]) * 3 / 5\n\t\tif retry < available {\n\t\t\tavailable = retry\n\t\t}\n\t}\n\n\tif edge.maxHTLC > 0 && available > edge.maxHTLC {\n\t\tavailable = edge.maxHTLC\n\t}\n\treturn available\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, numerator := range []int64{100, 65, 80, 120, 145, 50, 175} {\n\t\tvalue := base * lnwire.MilliSatoshi(numerator) / 100\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tfor _, denominator := range []int64{2, 3, 4} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\tr.lastFailedShard/\n\t\t\t\t\tlnwire.MilliSatoshi(denominator),\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor _, edge := range r.edges {\n\t\toffer := r.edgeOffer(edge)\n\t\tif offer <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\timportant := edge.key.from == r.source ||\n\t\t\tedge.key.to == r.spec.Target\n\n\t\tbelief := r.beliefs[edge.key]\n\t\tif !important && belief.lowerOK == 0 &&\n\t\t\tbelief.upperFail == 0 {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, offer, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, offer*4/5, minimum, maximum,\n\t\t)\n\t}\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\tdi := math.Abs(float64(values[i] - base))\n\t\tdj := math.Abs(float64(values[j] - base))\n\t\treturn di < dj\n\t})\n\n\tconst maximumCandidates = 16\n\tif len(values) > maximumCandidates {\n\t\tvalues = values[:maximumCandidates]\n\t}\n\n\treturn values\n}\n\ntype candidateRouteOption struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeOptions(\n\tamt lnwire.MilliSatoshi) []candidateRouteOption {\n\n\tconst optionCount = 4\n\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\tvar options []candidateRouteOption\n\n\tfor option := 0; option < optionCount; option++ {\n\t\trt, logProbability, err := r.findRoute(amt, penalties)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\toptions = append(options, candidateRouteOption{\n\t\t\t\troute: rt,\n\t\t\t\tlogProb: logProbability,\n\t\t\t})\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpenalty := 480_000.0\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalty = 180_000\n\t\t\t}\n\t\t\tif key.to == r.spec.Target {\n\t\t\t\tpenalty = 560_000\n\t\t\t}\n\t\t\tpenalties[key] += penalty\n\t\t}\n\t}\n\n\treturn options\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tprobability := r.liquidityProbability(edge, amount)\n\t\tif probability <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(probability)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 5_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.035 * float64(len(routes)-1)\n\t}\n\n\tfor key, count := range uses {\n\t\tif count <= 1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif key.from == r.source {\n\t\t\tscore -= 0.025 * float64(count-1)\n\t\t} else {\n\t\t\tscore -= 0.16 * float64(count-1)\n\t\t}\n\t}\n\n\tif !complete && total > 0 {\n\t\tsent := total\n\t\tfor _, rt := range routes {\n\t\t\tsent -= candidateFinalAmount(rt)\n\t\t}\n\t\tprogress := 1 - float64(sent)/float64(total)\n\t\tscore += 0.40 * progress\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 12\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\tstate.reservations,\n\t\t\t)\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\toptions := r.routeOptions(size)\n\t\t\t\tfor _, option := range options {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(\n\t\t\t\t\t\treservations, option.route,\n\t\t\t\t\t)\n\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif remaining < 0 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\toption.route,\n\t\t\t\t\t)\n\n\t\t\t\t\tnextState := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnextState.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, total,\n\t\t\t\t\t\tremaining == 0,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(nextState.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\texpanded = append(expanded, nextState)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.SliceStable(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProbability, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil &&\n\t\tfullLogProbability >= math.Log(0.78) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\tif maxPlanParts > 10 {\n\t\tmaxPlanParts = 10\n\t}\n\tif total < lnwire.MilliSatoshi(maxPlanParts) {\n\t\tmaxPlanParts = int(total)\n\t}\n\tif maxPlanParts < 1 {\n\t\treturn nil, errors.New(\"payment amount too small\")\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(\n\t\t\tbestPlan, reservations, total, true,\n\t\t)\n\t}\n\n\tfor count := 2; count <= maxPlanParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.policyBlocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || !edge.policyAllows(amount) {\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > edge.capacity {\n\t\t\treturn false\n\t\t}\n\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalances[key.chanID]\n\t\t\tif !ok || total > balance {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\tif failure, ok := r.currentFails[key]; ok &&\n\t\t\tfailure.upper > 0 &&\n\t\t\tfailure.count >= 4 &&\n\t\t\ttotal >= failure.upper {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 &&\n\t\t\tfinalAmount <= amt &&\n\t\t\tr.routeFits(rt) {\n\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 17 / 20\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 4\n\tif edge := r.edges[key]; edge != nil {\n\t\tfloor := edge.capacity / 25\n\t\tif estimate > floor {\n\t\t\testimate = floor\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 ||\n\t\tbelief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tpassedEdges := failureIndex\n\tif passedEdges > len(rt.Hops) {\n\t\tpassedEdges = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < passedEdges; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tamountOverEdge, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\ttotalRequired := amountOverEdge + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 4\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 9,
|
|
"parent": 0,
|
|
"score": 0.3505,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateFinalCltvDelta = 40\n\tcandidateMaxRouteHops = 20\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateAttemptKey struct {\n\trouteHash uint64\n\tamount lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\ttried map[candidateAttemptKey]bool\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tif spec == nil {\n\t\treturn nil, errors.New(\"nil payment specification\")\n\t}\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\ttried: make(map[candidateAttemptKey]bool),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tr.attemptLimit = 16 + int(maxParts)*2\n\tif r.attemptLimit < 18 {\n\t\tr.attemptLimit = 18\n\t}\n\tif r.attemptLimit > 40 {\n\t\tr.attemptLimit = 40\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tif _, ok := r.edges[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.045)\n\thighMode := 1 / (1 + math.Exp((x-0.92)/0.035))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tswitch {\n\tcase p < 0.004:\n\t\treturn 0.004\n\tcase p > 0.988:\n\t\treturn 0.988\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity ||\n\t\tr.policyBlocked[edge.key] {\n\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase ratio > 0.80:\n\t\t\tretryScale = 0.02\n\t\tcase ratio > 0.65:\n\t\t\tretryScale = 0.09\n\t\tcase ratio > 0.50:\n\t\t\tretryScale = 0.23\n\t\tcase ratio > 0.35:\n\t\t\tretryScale = 0.46\n\t\tcase ratio > 0.22:\n\t\t\tretryScale = 0.70\n\t\tdefault:\n\t\t\tretryScale = 0.88\n\t\t}\n\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.72\n\t\t}\n\t\tif failure.count >= 4 {\n\t\t\tretryScale *= 0.60\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif ok {\n\t\tswitch {\n\t\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\t\tp = 0.996\n\n\t\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\t\tp = 0.005\n\n\t\tcase belief.lowerOK > 0 &&\n\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\twidth := float64(\n\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t)\n\t\t\tposition := float64(total-belief.lowerOK) / width\n\t\t\tif position < 0 {\n\t\t\t\tposition = 0\n\t\t\t}\n\t\t\tif position > 1 {\n\t\t\t\tposition = 1\n\t\t\t}\n\n\t\t\tevidence := 0.995 - 0.990*position\n\t\t\tp = 0.08*p + 0.92*evidence\n\n\t\tcase belief.upperFail > 0:\n\t\t\tratio := float64(total) /\n\t\t\t\tfloat64(belief.upperFail)\n\t\t\tif ratio > 0.20 {\n\t\t\t\tscale := 1 - 0.94*(ratio-0.20)/0.80\n\t\t\t\tif scale < 0.05 {\n\t\t\t\t\tscale = 0.05\n\t\t\t\t}\n\t\t\t\tp *= scale\n\t\t\t}\n\n\t\tcase belief.lowerOK > 0:\n\t\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\t\tfloat64(edge.capacity)\n\t\t\tif distance > 0 {\n\t\t\t\tboost := 0.70 * math.Exp(-distance/0.18)\n\t\t\t\tp += boost * (1 - p)\n\t\t\t}\n\t\t}\n\n\t\tif belief.estimate > 0 {\n\t\t\tscale := 0.07 * float64(edge.capacity)\n\t\t\tif scale < 1 {\n\t\t\t\tscale = 1\n\t\t\t}\n\t\t\testimateProbability := 1 / (1 + math.Exp(\n\t\t\t\t(float64(total)-float64(belief.estimate)) /\n\t\t\t\t\tscale,\n\t\t\t))\n\n\t\t\tweight := 0.22\n\t\t\tif belief.successes+belief.failures >= 3 {\n\t\t\t\tweight = 0.35\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*estimateProbability\n\t\t}\n\t}\n\n\tp *= retryScale\n\tswitch {\n\tcase p < 0.001:\n\t\treturn 0.001\n\tcase p > 0.997:\n\t\treturn 0.997\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(value any) {\n\titem := value.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc candidatePathContains(path []*candidateEdge,\n\tvertex route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == vertex || edge.key.to == vertex {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64,\n\terror) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 1_100_000.0\n\t\thopPenalty = 120_000.0\n\t)\n\n\tbestScore := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(item.path, amt)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\t\t\treturn rt, item.logProb, nil\n\t\t}\n\n\t\tif len(item.path) >= candidateMaxRouteHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item.path, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOverEdge := item.required\n\t\t\tprobability := r.probability(edge, amountOverEdge)\n\t\t\tif probability <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\tsending := amountOverEdge + fee\n\t\t\tif sending < amountOverEdge {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(probability)) +\n\t\t\t\thopPenalty\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 20_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 170_000\n\t\t\tedgeScore += extraPenalty[edge.key]\n\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tedgeScore += 80_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\toldScore, found := bestScore[edge.key.from]\n\t\t\tif found && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = edge\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb +\n\t\t\t\t\tmath.Log(probability),\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\tif len(path) > candidateMaxRouteHops {\n\t\treturn nil, errors.New(\"route too long\")\n\t}\n\n\tnode := r.source\n\tseen := make(map[route.Vertex]bool)\n\tfor _, edge := range path {\n\t\tif edge.key.from != node || seen[node] {\n\t\t\treturn nil, errors.New(\"invalid route path\")\n\t\t}\n\t\tseen[node] = true\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, errors.New(\"route does not reach target\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = candidateFinalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] + outgoing.fee(amounts[i+1])\n\t\tif amounts[i] < amounts[i+1] {\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountOverEdge := amounts[i]\n\t\tif r.probability(edge, amountOverEdge) <= 0 {\n\t\t\treturn nil, errors.New(\"route violates edge constraints\")\n\t\t}\n\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(candidateFinalCltvDelta)\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tchannelIndex int) (lnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\n\thop := rt.Hops[channelIndex]\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\th := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\th ^= candidateMix64(\n\t\t\tkey.chanID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th ^= candidateMix64(candidateVertexHash(key.to))\n\t}\n\n\treturn candidateMix64(h)\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\ntype candidateRouteOption struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeOptions(amt lnwire.MilliSatoshi,\n\tcount int) []candidateRouteOption {\n\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\toptions := make([]candidateRouteOption, 0, count)\n\n\tfor option := 0; option < count; option++ {\n\t\trt, logProbability, err := r.findRoute(amt, penalties)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\toptions = append(options, candidateRouteOption{\n\t\t\t\troute: rt,\n\t\t\t\tlogProb: logProbability,\n\t\t\t})\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpenalty := 900_000.0\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalty = 300_000\n\t\t\t}\n\t\t\tpenalties[key] += penalty\n\t\t}\n\t}\n\n\treturn options\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc candidatePlanSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 0 || remaining < lnwire.MilliSatoshi(slots) {\n\t\treturn nil\n\t}\n\tif slots == 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&values, seen, base, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base*2/3, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base*4/3, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base/2, minimum, maximum)\n\tcandidateAddAmount(&values, seen, base*3/2, minimum, maximum)\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\tdi := math.Abs(float64(values[i] - base))\n\t\tdj := math.Abs(float64(values[j] - base))\n\t\treturn di < dj\n\t})\n\n\treturn values\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tprobability := r.liquidityProbability(edge, amount)\n\t\tif probability <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(probability)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 10_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.025 * float64(len(routes)-1)\n\t}\n\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.035 * float64(count-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 4\n\n\tif parts <= 0 || total < lnwire.MilliSatoshi(parts) {\n\t\treturn nil, 0, false\n\t}\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tsizes := candidatePlanSizes(\n\t\t\t\tstate.remaining, state.slots,\n\t\t\t)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\tfor _, option := range r.routeOptions(size, 2) {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(\n\t\t\t\t\t\treservations, option.route,\n\t\t\t\t\t)\n\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif remaining < 0 ||\n\t\t\t\t\t\t(remaining == 0) != (slots == 0) {\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\toption.route,\n\t\t\t\t\t)\n\n\t\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\t\troutes, reservations,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(next.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.SliceStable(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) makeInitialPlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProbability, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProbability >= math.Log(0.78) {\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\tif maxPlanParts > 16 {\n\t\tmaxPlanParts = 16\n\t}\n\tif total < lnwire.MilliSatoshi(maxPlanParts) {\n\t\tmaxPlanParts = int(total)\n\t}\n\tif maxPlanParts < 1 {\n\t\treturn nil, errors.New(\"payment amount too small\")\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(bestPlan, reservations)\n\t}\n\n\tcountSet := make(map[int]bool)\n\tcounts := []int{2, 3, 4, maxPlanParts}\n\tfor _, count := range counts {\n\t\tif count < 2 || count > maxPlanParts || countSet[count] {\n\t\t\tcontinue\n\t\t}\n\t\tcountSet[count] = true\n\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif ok && (bestPlan == nil || score > bestScore) {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan != nil {\n\t\treturn bestPlan, nil\n\t}\n\tif fullErr != nil {\n\t\treturn nil, fullErr\n\t}\n\n\treturn []*route.Route{full}, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.policyBlocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || !edge.policyAllows(amount) {\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > edge.capacity {\n\t\t\treturn false\n\t\t}\n\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalances[key.chanID]\n\t\t\tif !ok || total > balance {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\tif failure, ok := r.currentFails[key]; ok &&\n\t\t\tfailure.upper > 0 && total >= failure.upper {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) attempted(rt *route.Route) bool {\n\tkey := candidateAttemptKey{\n\t\trouteHash: candidateRouteHash(rt),\n\t\tamount: candidateFinalAmount(rt),\n\t}\n\treturn r.tried[key]\n}\n\nfunc (r *candidateRouter) markAttempted(rt *route.Route) {\n\tkey := candidateAttemptKey{\n\t\trouteHash: candidateRouteHash(rt),\n\t\tamount: candidateFinalAmount(rt),\n\t}\n\tr.tried[key] = true\n}\n\nfunc (r *candidateRouter) reactiveSizes(remaining lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tcandidateAddAmount(\n\t\t&values, seen, base, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&values, seen, remaining, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&values, seen, remaining*3/4, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&values, seen, remaining/2, minimum, maximum,\n\t)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard*2/3,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/2,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/3,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/4,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\treturn values\n}\n\nfunc (r *candidateRouter) chooseReactiveRoute(\n\tremaining lnwire.MilliSatoshi,\n\tslots uint32) (*route.Route, error) {\n\n\tvar best *route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor _, size := range r.reactiveSizes(remaining, slots) {\n\t\tfor _, option := range r.routeOptions(size, 4) {\n\t\t\tif r.attempted(option.route) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfinalAmount := candidateFinalAmount(option.route)\n\t\t\tif finalAmount <= 0 || finalAmount > remaining {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfees := option.route.TotalAmount - finalAmount\n\t\t\tprogress := float64(finalAmount) /\n\t\t\t\tfloat64(remaining)\n\t\t\tscore := option.logProb +\n\t\t\t\t0.65*math.Log(progress) -\n\t\t\t\tfloat64(fees)/10_000_000\n\n\t\t\tif finalAmount == remaining {\n\t\t\t\tscore += 0.12\n\t\t\t}\n\n\t\t\tif best == nil || score > bestScore {\n\t\t\t\tbest = option.route\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no untried route found\")\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) returnRoute(rt *route.Route) (*route.Route,\n\terror) {\n\n\tif rt == nil || candidateFinalAmount(rt) <= 0 {\n\t\treturn nil, errors.New(\"invalid selected route\")\n\t}\n\tif r.attempted(rt) {\n\t\treturn nil, errors.New(\"selected route already attempted\")\n\t}\n\n\tr.markAttempted(rt)\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tfor len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tr.planned = r.planned[1:]\n\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount <= 0 || finalAmount > amt ||\n\t\t\t!r.routeFits(rt) || r.attempted(rt) {\n\n\t\t\tr.planned = nil\n\t\t\tbreak\n\t\t}\n\n\t\treturn r.returnRoute(rt)\n\t}\n\n\tif r.attempts == 0 {\n\t\tplan, err := r.makeInitialPlan(amt, partsLeft)\n\t\tif err == nil && len(plan) > 0 {\n\t\t\trt := plan[0]\n\t\t\tif len(plan) > 1 {\n\t\t\t\tr.planned = append(\n\t\t\t\t\t[]*route.Route(nil), plan[1:]...,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn r.returnRoute(rt)\n\t\t}\n\t}\n\n\trt, err := r.chooseReactiveRoute(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r.returnRoute(rt)\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 17 / 20\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 4\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 20\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tpassedEdges := failureIndex\n\tif passedEdges > len(rt.Hops) {\n\t\tpassedEdges = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < passedEdges; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tamountOverEdge, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amountOverEdge + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 4\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 10,
|
|
"parent": 1,
|
|
"score": 0.403,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(candidateVertexHash(e.key.to) +\n\t\t0x9e3779b97f4a7c15)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(uint64(e.baseFeeMsat) +\n\t\tuint64(e.feeRatePPM)<<17)\n\tx ^= candidateMix64(uint64(e.timeLockDelta) +\n\t\tuint64(e.minHTLC)<<16)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*4 + 10\n\tif r.attemptLimit < 24 {\n\t\tr.attemptLimit = 24\n\t}\n\tif r.attemptLimit > 72 {\n\t\tr.attemptLimit = 72\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treserved := r.reserved[edge.key]\n\ttotal := amt + reserved\n\n\tif !edge.policyAllows(amt) || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok {\n\t\tif failure.count >= 3 {\n\t\t\treturn 0\n\t\t}\n\t\tif failure.upper > 0 {\n\t\t\tif total >= failure.upper {\n\t\t\t\treturn 0\n\t\t\t}\n\n\t\t\tretryCeiling := failure.upper * 2 / 3\n\t\t\tif retryCeiling < 1 {\n\t\t\t\tretryCeiling = 1\n\t\t\t}\n\t\t\tif total > retryCeiling {\n\t\t\t\treturn 0\n\t\t\t}\n\n\t\t\tretryScale = 0.42\n\t\t\tif failure.count == 2 {\n\t\t\t\tretryScale = 0.22\n\t\t\t}\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retryScale\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995 * retryScale\n\t}\n\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\tif p > 0.012 {\n\t\t\tp = 0.012\n\t\t}\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.985 - 0.97*pos\n\t\tp = 0.18*p + 0.82*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.5 {\n\t\t\tscale := 1 - 0.94*(ratio-0.5)/0.5\n\t\t\tif scale < 0.06 {\n\t\t\t\tscale = 0.06\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.48 * math.Exp(-distance/0.18)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.11 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tp = 0.68*p + 0.32*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.002 {\n\t\treturn 0.002\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 420_000.0\n\t\thopPenalty = 220.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 18_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 230_000\n\n\t\t\tif r.reserved[edge.key] > 0 {\n\t\t\t\tedgeScore += 210_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmax lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > max {\n\t\tvalue = max\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\nfunc (r *candidateRouter) planSizes(remaining lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&sizes, seen, base/3, remaining)\n\tcandidateAddAmount(&sizes, seen, base/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*2/3, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/4, remaining)\n\tcandidateAddAmount(&sizes, seen, base, remaining)\n\tcandidateAddAmount(&sizes, seen, base*5/4, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining/2, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining*2/3, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*3/8, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8, remaining,\n\t\t)\n\t}\n\n\tfor _, failure := range r.currentFails {\n\t\tif failure.upper == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, failure.upper*3/8, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, failure.upper/2, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, failure.upper*5/8, remaining,\n\t\t)\n\t}\n\n\treturn sizes\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots uint32\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n\trank float64\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tbeam := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(savedReservations),\n\t}}\n\n\tvar complete []candidatePlanState\n\tconst beamWidth = 24\n\n\tfor depth := uint32(0); depth < parts && len(beam) > 0; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range beam {\n\t\t\tsizes := r.planSizes(state.remaining, state.slots)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\trt, logProb, err := r.findRoute(size)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfees := rt.TotalAmount - size\n\t\t\t\tnextReservations := candidateCopyReservations(\n\t\t\t\t\tr.reserved,\n\t\t\t\t)\n\t\t\t\tr.reserved = nextReservations\n\t\t\t\tr.reserveRoute(rt)\n\n\t\t\t\tnextRemaining := state.remaining - size\n\t\t\t\tnextScore := state.score + logProb -\n\t\t\t\t\tfloat64(fees)/4_000_000 - 0.035\n\n\t\t\t\tnextRoutes := make(\n\t\t\t\t\t[]*route.Route, len(state.routes), len(state.routes)+1,\n\t\t\t\t)\n\t\t\t\tcopy(nextRoutes, state.routes)\n\t\t\t\tnextRoutes = append(nextRoutes, rt)\n\n\t\t\t\tprogress := float64(total-nextRemaining) /\n\t\t\t\t\tfloat64(total)\n\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\tremaining: nextRemaining,\n\t\t\t\t\tslots: state.slots - 1,\n\t\t\t\t\troutes: nextRoutes,\n\t\t\t\t\treservations: candidateCopyReservations(r.reserved),\n\t\t\t\t\tscore: nextScore,\n\t\t\t\t\trank: nextScore + 0.20*progress,\n\t\t\t\t}\n\n\t\t\t\tif nextRemaining == 0 {\n\t\t\t\t\tcomplete = append(complete, next)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif next.slots > 0 {\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\tif expanded[i].rank == expanded[j].rank {\n\t\t\t\treturn expanded[i].remaining <\n\t\t\t\t\texpanded[j].remaining\n\t\t\t}\n\t\t\treturn expanded[i].rank > expanded[j].rank\n\t\t})\n\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tbeam = expanded\n\t}\n\n\tif len(complete) == 0 {\n\t\tr.reserved = savedReservations\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tsort.Slice(complete, func(i, j int) bool {\n\t\tif complete[i].score == complete[j].score {\n\t\t\treturn len(complete[i].routes) < len(complete[j].routes)\n\t\t}\n\t\treturn complete[i].score > complete[j].score\n\t})\n\n\treturn complete[0].routes, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tpartAmt := candidateFinalAmount(rt)\n\t\tif partAmt > 0 && partAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 5 / 8\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t}\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.suspect[key]++\n\t\tr.markRouteSuspect(rt, 1)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 11,
|
|
"parent": 3,
|
|
"score": 0.6306,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = 16 + int(maxParts)*2\n\tif r.attemptLimit < 20 {\n\t\tr.attemptLimit = 20\n\t}\n\tif r.attemptLimit > 42 {\n\t\tr.attemptLimit = 42\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.045)\n\thighMode := 1 / (1 + math.Exp((x-0.92)/0.035))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase ratio > 0.80:\n\t\t\tretryScale = 0.018\n\t\tcase ratio > 0.65:\n\t\t\tretryScale = 0.075\n\t\tcase ratio > 0.50:\n\t\t\tretryScale = 0.21\n\t\tcase ratio > 0.35:\n\t\t\tretryScale = 0.44\n\t\tcase ratio > 0.22:\n\t\t\tretryScale = 0.68\n\t\tdefault:\n\t\t\tretryScale = 0.86\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.65\n\t\t}\n\t\tif failure.count >= 4 {\n\t\t\tretryScale *= 0.50\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.005\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(total-belief.lowerOK) / width\n\t\tif position < 0 {\n\t\t\tposition = 0\n\t\t}\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\t\tevidence := 0.994 - 0.989*position\n\t\tp = 0.08*p + 0.92*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.20 {\n\t\t\tscale := 1 - 0.95*(ratio-0.20)/0.80\n\t\t\tif scale < 0.04 {\n\t\t\t\tscale = 0.04\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tif distance > 0 {\n\t\t\tboost := 0.70 * math.Exp(-distance/0.18)\n\t\t\tp += boost * (1 - p)\n\t\t}\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.065 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\testimateProbability := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.25\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.38\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateProbability\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(value any) {\n\titem := value.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 390_000.0\n\t\thopPenalty = 650.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tbestScore[r.spec.Target] = 0\n\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProbability float64\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProbability = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOverEdge := item.required\n\t\t\tprobability := r.probability(edge, amountOverEdge)\n\t\t\tif probability <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\tsending := amountOverEdge + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(probability)) +\n\t\t\t\thopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 8_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 58_000\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tedgeScore += 35_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\toldScore, found := bestScore[edge.key.from]\n\t\t\tif found && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(probability),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLogProbability, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\treturn r.buildPath(path, amt)\n}\n\nfunc (r *candidateRouter) buildPath(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 || amt <= 0 {\n\t\treturn nil, errors.New(\"empty path\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] + outgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tif !edge.policyAllows(amounts[i]) {\n\t\t\treturn nil, errors.New(\"path amount violates policy\")\n\t\t}\n\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tchannelIndex int) (lnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\n\thop := rt.Hops[channelIndex]\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\thash := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\thash ^= candidateMix64(\n\t\t\tkey.chanID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\thash ^= candidateMix64(candidateVertexHash(key.to))\n\t}\n\treturn candidateMix64(hash)\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc (r *candidateRouter) routePath(\n\trt *route.Route) ([]*candidateEdge, bool) {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil, false\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn nil, false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn nil, false\n\t\t}\n\t\tpath = append(path, edge)\n\t}\n\treturn path, true\n}\n\nfunc (r *candidateRouter) planningCeiling(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tceiling := edge.capacity\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok {\n\t\t\treturn 0\n\t\t}\n\t\tif balance < ceiling {\n\t\t\tceiling = balance\n\t\t}\n\t\treturn ceiling\n\t}\n\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tlimit := failure.upper - 1\n\t\tif limit < ceiling {\n\t\t\tceiling = limit\n\t\t}\n\t}\n\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\tlimit := edge.capacity * 17 / 20\n\t\tif limit < ceiling {\n\t\t\tceiling = limit\n\t\t}\n\t\treturn ceiling\n\t}\n\n\tvar limit lnwire.MilliSatoshi\n\tswitch {\n\tcase belief.lowerOK > 0:\n\t\tlimit = belief.lowerOK\n\t\tif belief.estimate > limit {\n\t\t\tlimit = belief.estimate\n\t\t}\n\n\tcase belief.upperFail > 0:\n\t\tlimit = belief.upperFail * 3 / 5\n\n\tcase belief.estimate > 0:\n\t\tlimit = belief.estimate\n\n\tdefault:\n\t\tlimit = edge.capacity * 17 / 20\n\t}\n\n\tif limit > 0 && limit < ceiling {\n\t\tceiling = limit\n\t}\n\treturn ceiling\n}\n\nfunc (r *candidateRouter) pathFitsPlanning(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) bool {\n\n\trt, err := r.buildPath(path, amt)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.policyBlocked[key] {\n\t\t\treturn false\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || !edge.policyAllows(amount) {\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > r.planningCeiling(edge) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) pathLimit(path []*candidateEdge,\n\tmaximum lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif maximum <= 0 || len(path) == 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := maximum\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathFitsPlanning(path, mid) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\treturn low\n}\n\ntype candidatePathOption struct {\n\tpath []*candidateEdge\n\tlimit lnwire.MilliSatoshi\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) discoverPaths(total lnwire.MilliSatoshi,\n\tparts uint32) []candidatePathOption {\n\n\tmaxPaths := int(parts)\n\tif maxPaths > 12 {\n\t\tmaxPaths = 12\n\t}\n\tif maxPaths < 2 {\n\t\treturn nil\n\t}\n\n\tprobe := total / lnwire.MilliSatoshi(maxPaths*2)\n\tif probe < 1 {\n\t\tprobe = 1\n\t}\n\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\toptions := make([]candidatePathOption, 0, maxPaths)\n\tvar available lnwire.MilliSatoshi\n\n\tmaxSearches := maxPaths * 2\n\tfor search := 0; search < maxSearches &&\n\t\tlen(options) < maxPaths; search++ {\n\n\t\trt, logProbability, err := r.findRoute(probe, penalties)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpenalty := 1_150_000.0\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalty = 310_000\n\t\t\t}\n\t\t\tpenalties[key] += penalty\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif seen[hash] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[hash] = true\n\n\t\tpath, ok := r.routePath(rt)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlimit := r.pathLimit(path, total)\n\t\tif limit < probe {\n\t\t\tcontinue\n\t\t}\n\n\t\toptions = append(options, candidatePathOption{\n\t\t\tpath: path,\n\t\t\tlimit: limit,\n\t\t\tlogProb: logProbability,\n\t\t})\n\t\tavailable += limit\n\n\t\tif len(options) >= 2 &&\n\t\t\tavailable >= total+total/3 {\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn options\n}\n\nfunc (r *candidateRouter) validatePlan(routes []*route.Route,\n\ttotal lnwire.MilliSatoshi) (\n\tmap[candidateEdgeKey]lnwire.MilliSatoshi, bool) {\n\n\tif len(routes) == 0 {\n\t\treturn nil, false\n\t}\n\n\treservations := candidateCopyReservations(r.reserved)\n\tvar delivered lnwire.MilliSatoshi\n\n\tfor _, rt := range routes {\n\t\tif rt == nil || len(rt.Hops) == 0 {\n\t\t\treturn nil, false\n\t\t}\n\t\tdelivered += candidateFinalAmount(rt)\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tedge := r.edges[key]\n\t\t\tif edge == nil || r.policyBlocked[key] {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok || !edge.policyAllows(amount) {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n\n\tif delivered != total {\n\t\treturn nil, false\n\t}\n\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil || amount > edge.capacity {\n\t\t\treturn nil, false\n\t\t}\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalances[key.chanID]\n\t\t\tif !ok || amount > balance {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t}\n\t\tif failure, ok := r.currentFails[key]; ok &&\n\t\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\t\treturn nil, false\n\t\t}\n\t}\n\n\treturn reservations, true\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tprobability := r.liquidityProbability(edge, amount)\n\t\tif probability <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(probability)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 7_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.028 * float64(len(routes)-1)\n\t}\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.085 * float64(count-1)\n\t\t}\n\t}\n\treturn score\n}\n\nfunc candidateAllocate(total lnwire.MilliSatoshi,\n\toptions []candidatePathOption, equal bool) []lnwire.MilliSatoshi {\n\n\tif total <= 0 || len(options) == 0 {\n\t\treturn nil\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(options))\n\tremaining := total\n\n\tvar totalWeight float64\n\tfor _, option := range options {\n\t\tif equal {\n\t\t\ttotalWeight += 1\n\t\t} else {\n\t\t\ttotalWeight += float64(option.limit)\n\t\t}\n\t}\n\tif totalWeight <= 0 {\n\t\treturn nil\n\t}\n\n\tweightLeft := totalWeight\n\tfor i, option := range options {\n\t\tslotsLeft := len(options) - i\n\t\tif slotsLeft == 1 {\n\t\t\tamounts[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tweight := 1.0\n\t\tif !equal {\n\t\t\tweight = float64(option.limit)\n\t\t}\n\n\t\tvalue := lnwire.MilliSatoshi(\n\t\t\tmath.Round(float64(remaining) * weight / weightLeft),\n\t\t)\n\t\tmaximum := remaining - lnwire.MilliSatoshi(slotsLeft-1)\n\t\tif value < 1 {\n\t\t\tvalue = 1\n\t\t}\n\t\tif value > maximum {\n\t\t\tvalue = maximum\n\t\t}\n\n\t\tamounts[i] = value\n\t\tremaining -= value\n\t\tweightLeft -= weight\n\t}\n\n\tfor i, amount := range amounts {\n\t\tif amount <= 0 || amount > options[i].limit {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn amounts\n}\n\nfunc (r *candidateRouter) buildOptionPlan(total lnwire.MilliSatoshi,\n\toptions []candidatePathOption, equal bool) (\n\t[]*route.Route, float64, bool) {\n\n\tamounts := candidateAllocate(total, options, equal)\n\tif amounts == nil {\n\t\treturn nil, 0, false\n\t}\n\n\troutes := make([]*route.Route, 0, len(options))\n\tfor i, option := range options {\n\t\trt, err := r.buildPath(option.path, amounts[i])\n\t\tif err != nil {\n\t\t\treturn nil, 0, false\n\t\t}\n\t\troutes = append(routes, rt)\n\t}\n\n\treservations, ok := r.validatePlan(routes, total)\n\tif !ok {\n\t\treturn nil, 0, false\n\t}\n\treturn routes, r.planScore(routes, reservations), true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProbability, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil &&\n\t\tfullLogProbability >= math.Log(0.80) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations, ok := r.validatePlan(\n\t\t\t[]*route.Route{full}, total,\n\t\t)\n\t\tif ok {\n\t\t\tbestPlan = []*route.Route{full}\n\t\t\tbestScore = r.planScore(bestPlan, reservations)\n\t\t}\n\t}\n\n\toptions := r.discoverPaths(total, parts)\n\tvar capacity lnwire.MilliSatoshi\n\n\tfor count := 1; count <= len(options); count++ {\n\t\tcapacity += options[count-1].limit\n\t\tif count < 2 || capacity < total {\n\t\t\tcontinue\n\t\t}\n\n\t\tsubset := options[:count]\n\t\tfor _, equal := range []bool{false, true} {\n\t\t\tplan, score, ok := r.buildOptionPlan(\n\t\t\t\ttotal, subset, equal,\n\t\t\t)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif bestPlan == nil || score > bestScore {\n\t\t\t\tbestPlan = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif bestPlan != nil {\n\t\treturn bestPlan, nil\n\t}\n\tif fullErr != nil {\n\t\treturn nil, fullErr\n\t}\n\treturn []*route.Route{full}, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.policyBlocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || !edge.policyAllows(amount) {\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > edge.capacity {\n\t\t\treturn false\n\t\t}\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalances[key.chanID]\n\t\t\tif !ok || total > balance {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\tif failure, ok := r.currentFails[key]; ok &&\n\t\t\tfailure.upper > 0 && total >= failure.upper {\n\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt &&\n\t\t\tr.routeFits(rt) {\n\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(plan) == 0 {\n\t\treturn nil, errors.New(\"empty route plan\")\n\t}\n\n\trt := plan[0]\n\tif candidateFinalAmount(rt) <= 0 ||\n\t\tcandidateFinalAmount(rt) > amt {\n\n\t\treturn nil, errors.New(\"invalid planned shard\")\n\t}\n\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 17 / 20\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 4\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 20\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tpassedEdges := failureIndex\n\tif passedEdges > len(rt.Hops) {\n\t\tpassedEdges = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < passedEdges; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamountOverEdge, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amountOverEdge + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 3\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t\tr.markRouteSuspect(rt, 1)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 12,
|
|
"parent": 3,
|
|
"score": 0.0,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\tedgeUses map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = 12 + int(maxParts)*4\n\tif r.attemptLimit < 20 {\n\t\tr.attemptLimit = 20\n\t}\n\tif r.attemptLimit > 48 {\n\t\tr.attemptLimit = 48\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.048)\n\thighMode := 1 / (1 + math.Exp((x-0.92)/0.03))\n\tp := 0.46*lowMode + 0.54*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity ||\n\t\tr.policyBlocked[edge.key] {\n\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 4 && ratio > 0.18:\n\t\t\treturn 0\n\t\tcase failure.count >= 3 && ratio > 0.28:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.48:\n\t\t\treturn 0\n\t\tcase ratio > 0.72:\n\t\t\treturn 0\n\t\tcase ratio > 0.55:\n\t\t\tretryScale = 0.16\n\t\tcase ratio > 0.38:\n\t\t\tretryScale = 0.34\n\t\tcase ratio > 0.24:\n\t\t\tretryScale = 0.58\n\t\tdefault:\n\t\t\tretryScale = 0.79\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.005\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(total-belief.lowerOK) / width\n\t\tif position < 0 {\n\t\t\tposition = 0\n\t\t}\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\t\tevidence := 0.994 - 0.989*position\n\t\tp = 0.1*p + 0.9*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.25 {\n\t\t\tscale := 1 - 0.95*(ratio-0.25)/0.75\n\t\t\tif scale < 0.05 {\n\t\t\t\tscale = 0.05\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.68 * math.Exp(-distance/0.2)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.065 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\testimateProbability := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.25\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.38\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateProbability\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateLabel struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\thops int\n\tvia *candidateEdge\n\tnext *candidateLabel\n\tindex int\n}\n\ntype candidateQueue []*candidateLabel\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateQueue) Push(x any) {\n\titem := x.(*candidateLabel)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc candidateAcceptLabel(labels map[route.Vertex][]*candidateLabel,\n\tlabel *candidateLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif old.score <= label.score &&\n\t\t\told.required <= label.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfiltered := current[:0]\n\tfor _, old := range current {\n\t\tif label.score <= old.score &&\n\t\t\tlabel.required <= old.required {\n\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, old)\n\t}\n\tfiltered = append(filtered, label)\n\n\tsort.Slice(filtered, func(i, j int) bool {\n\t\tif filtered[i].score == filtered[j].score {\n\t\t\treturn filtered[i].required < filtered[j].required\n\t\t}\n\t\treturn filtered[i].score < filtered[j].score\n\t})\n\tif len(filtered) > 4 {\n\t\tfiltered = filtered[:4]\n\t}\n\n\tkept := false\n\tfor _, item := range filtered {\n\t\tif item == label {\n\t\t\tkept = true\n\t\t\tbreak\n\t\t}\n\t}\n\tlabels[label.node] = filtered\n\treturn kept\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 305_000.0\n\t\thopPenalty = 850.0\n\t\tmaxHops = 24\n\t)\n\n\tinitial := &candidateLabel{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t}\n\tlabels := map[route.Vertex][]*candidateLabel{\n\t\tr.spec.Target: {initial},\n\t}\n\tqueue := &candidateQueue{}\n\theap.Push(queue, initial)\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateLabel)\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(item)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\t\t\treturn rt, item.logProb, nil\n\t\t}\n\t\tif item.hops >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 175_000\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 7_500\n\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tedgeScore += 115_000\n\t\t\t}\n\n\t\t\tlabel := &candidateLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: amtOver + fee,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tvia: edge,\n\t\t\t\tnext: item,\n\t\t\t}\n\t\t\tif candidateAcceptLabel(labels, label) {\n\t\t\t\theap.Push(queue, label)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(sourceLabel *candidateLabel) (\n\t*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor label := sourceLabel; label.node != r.spec.Target; {\n\t\tif seen[label.node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tseen[label.node] = true\n\n\t\tif label.via == nil || label.next == nil {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", label.node)\n\t\t}\n\n\t\tpath = append(path, label.via)\n\t\tlabel = label.next\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tlast := len(path) - 1\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tfinalAmount := sourceLabel\n\tfor finalAmount.next != nil {\n\t\tfinalAmount = finalAmount.next\n\t}\n\tamtOver[last] = finalAmount.required\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amtOver[last]\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, index int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif index == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[index-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route, index int) (\n\tcandidateEdgeKey, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\thop := rt.Hops[index]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amount := range src {\n\t\tdst[key] = amount\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tnumerators := []int64{1, 2, 3, 4, 5, 6, 8, 10, 12, 16}\n\tfor _, numerator := range numerators {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen,\n\t\t\tbase*lnwire.MilliSatoshi(numerator)/8,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tcandidateAddAmount(\n\t\t&values, seen, remaining/10, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&values, seen, remaining/5, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&values, seen, remaining/3, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&values, seen, remaining/2, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&values, seen, remaining*2/3, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&values, seen, remaining*4/5, minimum, maximum,\n\t)\n\n\tif r.lastFailedShard > 0 {\n\t\tfor _, denominator := range []int64{2, 3, 4, 6, 8} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\tr.lastFailedShard/\n\t\t\t\t\tlnwire.MilliSatoshi(denominator),\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\tdi := values[i] - base\n\t\tif di < 0 {\n\t\t\tdi = -di\n\t\t}\n\t\tdj := values[j] - base\n\t\tif dj < 0 {\n\t\t\tdj = -dj\n\t\t}\n\t\tif di == dj {\n\t\t\treturn values[i] > values[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\n\tif len(values) > 14 {\n\t\tvalues = values[:14]\n\t}\n\treturn values\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 3_500_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.075 * float64(len(routes)-1)\n\t}\n\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.11 * float64(count-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 10\n\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(saved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tfor _, size := range r.shardSizes(\n\t\t\t\tstate.remaining, state.slots,\n\t\t\t) {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\trt, _, err := r.findRoute(size)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\troutes := append(\n\t\t\t\t\tappend([]*route.Route(nil), state.routes...),\n\t\t\t\t\trt,\n\t\t\t\t)\n\t\t\t\tremaining := state.remaining - size\n\t\t\t\tslots := state.slots - 1\n\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\tremaining: remaining,\n\t\t\t\t\tslots: slots,\n\t\t\t\t\troutes: routes,\n\t\t\t\t\treservations: reservations,\n\t\t\t\t}\n\t\t\t\tnext.score = r.planScore(routes, reservations)\n\t\t\t\tif !math.IsInf(next.score, -1) {\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) fallbackShard(\n\ttotal lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvar sizes []lnwire.MilliSatoshi\n\n\tadd := func(value lnwire.MilliSatoshi) {\n\t\tif value > 0 && value <= total && !seen[value] {\n\t\t\tseen[value] = true\n\t\t\tsizes = append(sizes, value)\n\t\t}\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tadd(r.lastFailedShard * 5 / 8)\n\t\tadd(r.lastFailedShard / 2)\n\t\tadd(r.lastFailedShard * 3 / 8)\n\t\tadd(r.lastFailedShard / 3)\n\t\tadd(r.lastFailedShard / 4)\n\t\tadd(r.lastFailedShard / 6)\n\t\tadd(r.lastFailedShard / 8)\n\t}\n\n\tadd(total * 3 / 4)\n\tadd(total * 5 / 8)\n\tadd(total / 2)\n\tadd(total * 3 / 8)\n\tadd(total / 3)\n\tadd(total / 4)\n\tadd(total / 6)\n\tadd(total / 8)\n\tadd(total / 12)\n\tadd(total / 16)\n\tadd(1)\n\n\tvar best *route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor _, size := range sizes {\n\t\trt, logProbability, err := r.findRoute(size)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tprogress := float64(size) / float64(total)\n\t\tscore := logProbability + 0.9*math.Log(progress)\n\t\tfee := rt.TotalAmount - candidateFinalAmount(rt)\n\t\tscore -= float64(fee) / 3_500_000\n\n\t\tif best == nil || score > bestScore {\n\t\t\tbest = rt\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProbability, fullErr := r.findRoute(total)\n\n\tif parts > 1 && fullErr == nil &&\n\t\tfullLogProbability >= math.Log(0.86) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\tif maxPlanParts > 10 {\n\t\tmaxPlanParts = 10\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(bestPlan, reservations)\n\t}\n\n\tfor count := 2; count <= maxPlanParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan != nil {\n\t\treturn bestPlan, nil\n\t}\n\n\tshard, err := r.fallbackShard(total)\n\tif err != nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{shard}, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\thigh := edge.capacity * 7 / 8\n\t\tif high > estimate {\n\t\t\testimate = high\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlow := edge.capacity / 20\n\t\tif estimate > low {\n\t\t\testimate = low\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failureIndex\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamount, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amount + r.reserved[key]\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 13,
|
|
"parent": 0,
|
|
"score": 0.7194,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts) + 6\n\tif r.attemptLimit < 10 {\n\t\tr.attemptLimit = 10\n\t}\n\tif r.attemptLimit > 28 {\n\t\tr.attemptLimit = 28\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.052)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.034))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.30:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.48:\n\t\t\treturn 0\n\t\tcase ratio > 0.74:\n\t\t\treturn 0\n\t\tcase ratio > 0.60:\n\t\t\tretryScale = 0.18\n\t\tcase ratio > 0.45:\n\t\t\tretryScale = 0.40\n\t\tcase ratio > 0.30:\n\t\t\tretryScale = 0.65\n\t\tdefault:\n\t\t\tretryScale = 0.84\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.70\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.004\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence := 0.994 - 0.988*pos\n\t\tp = 0.13*p + 0.87*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.22 {\n\t\t\tscale := 1 - 0.91*(ratio-0.22)/0.78\n\t\t\tif scale < 0.09 {\n\t\t\t\tscale = 0.09\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.60 * math.Exp(-distance/0.19)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.075 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.16\n\t\tcount := belief.successes + belief.failures\n\t\tif count >= 2 {\n\t\t\tweight = 0.24\n\t\t}\n\t\tif count >= 4 {\n\t\t\tweight = 0.32\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\ttotalP := r.liquidityProbability(edge, reserved+amt)\n\tif totalP <= 0 {\n\t\treturn 0\n\t}\n\tif reserved == 0 {\n\t\treturn totalP\n\t}\n\n\tbaseP := r.liquidityProbability(edge, reserved)\n\tif baseP <= 0 {\n\t\treturn 0\n\t}\n\n\tp := totalP / baseP\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\treturn p\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tedge *candidateEdge\n\tnext *candidatePathLabel\n\thops int\n\tactive bool\n\tidx int\n}\n\ntype candidatePathQueue []*candidatePathLabel\n\nfunc (q candidatePathQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidatePathQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidatePathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidatePathQueue) Push(x any) {\n\titem := x.(*candidatePathLabel)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidatePathQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidatePathContains(label *candidatePathLabel,\n\tnode route.Vertex) bool {\n\n\tfor cur := label; cur != nil; cur = cur.next {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(labels map[route.Vertex][]*candidatePathLabel,\n\tlabel *candidatePathLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif old.active && old.score <= label.score &&\n\t\t\told.required <= label.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := current[:0]\n\tfor _, old := range current {\n\t\tif old.active && label.score <= old.score &&\n\t\t\tlabel.required <= old.required {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tif old.active {\n\t\t\tkept = append(kept, old)\n\t\t}\n\t}\n\n\tkept = append(kept, label)\n\tif len(kept) > 5 {\n\t\tsort.Slice(kept, func(i, j int) bool {\n\t\t\tli := kept[i].score +\n\t\t\t\tfloat64(kept[i].required)/10_000_000\n\t\t\tlj := kept[j].score +\n\t\t\t\tfloat64(kept[j].required)/10_000_000\n\t\t\treturn li < lj\n\t\t})\n\t\tfor _, old := range kept[5:] {\n\t\t\told.active = false\n\t\t}\n\t\tkept = kept[:5]\n\t}\n\n\tlabels[label.node] = kept\n\treturn label.active\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64,\n\terror) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 500_000.0\n\t\thopPenalty = 25_000.0\n\t\tmaxHops = 30\n\t\tmaxExpand = 30000\n\t)\n\n\ttarget := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidatePathLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tpq := &candidatePathQueue{}\n\theap.Push(pq, target)\n\n\texpanded := 0\n\tvar sourceLabel *candidatePathLabel\n\n\tfor pq.Len() > 0 && expanded < maxExpand {\n\t\titem := heap.Pop(pq).(*candidatePathLabel)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLabel = item\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\texpanded++\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 220_000\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\n\t\t\tlabel := &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tedge: edge,\n\t\t\t\tnext: item,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tactive: true,\n\t\t\t}\n\t\t\tif candidateInsertLabel(labels, label) {\n\t\t\t\theap.Push(pq, label)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor cur := sourceLabel; cur != nil && cur.edge != nil; cur = cur.next {\n\t\tpath = append(path, cur.edge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLabel.logProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tnode := r.source\n\tfor _, edge := range path {\n\t\tif edge.key.from != node {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, errors.New(\"path does not reach target\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations, base map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\toldAmount := base[key]\n\t\tif amount <= oldAmount {\n\t\t\tcontinue\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tif oldAmount > 0 {\n\t\t\toldP := r.liquidityProbability(edge, oldAmount)\n\t\t\tif oldP <= 0 {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\t\t\tp /= oldP\n\t\t}\n\t\tif p > 0.999 {\n\t\t\tp = 0.999\n\t\t}\n\t\tif p < 0.001 {\n\t\t\tp = 0.001\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmt {\n\t\t\tfees += rt.TotalAmount - finalAmt\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 28_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.025 * float64(len(routes)-1)\n\t}\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.018 * float64(count-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tavg := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tavg / 2,\n\t\tavg * 2 / 3,\n\t\tavg * 4 / 5,\n\t\tavg,\n\t\tavg * 6 / 5,\n\t\tavg * 3 / 2,\n\t\tavg * 2,\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailedShard * 3 / 10,\n\t\t\tr.lastFailedShard * 2 / 5,\n\t\t\tr.lastFailedShard / 2,\n\t\t\tr.lastFailedShard * 3 / 5,\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor key, edge := range r.edges {\n\t\treserved := r.reserved[key]\n\t\tbelief := r.beliefs[key]\n\n\t\tif belief.lowerOK > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen,\n\t\t\t\t(belief.lowerOK-reserved)*19/20,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > reserved {\n\t\t\tavailable := belief.upperFail - reserved\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*2/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*11/20,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen,\n\t\t\t\t(belief.estimate-reserved)*4/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\n\t\tavailable := edge.capacity - reserved\n\t\tif edge.key.from == r.source {\n\t\t\tif balance, ok := r.localBalances[edge.key.chanID]; ok {\n\t\t\t\tavailable = balance - reserved\n\t\t\t}\n\t\t}\n\t\tif available > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*4/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\tdi := math.Abs(math.Log(\n\t\t\tfloat64(sizes[i]) / float64(avg),\n\t\t))\n\t\tdj := math.Abs(math.Log(\n\t\t\tfloat64(sizes[j]) / float64(avg),\n\t\t))\n\t\tif di == dj {\n\t\t\treturn sizes[i] > sizes[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\tif len(sizes) > 12 {\n\t\tsizes = sizes[:12]\n\t}\n\n\treturn sizes\n}\n\nfunc candidateRouteSignature(rt *route.Route) uint64 {\n\tvar h uint64 = 1469598103934665603\n\tfor i, hop := range rt.Hops {\n\t\th ^= candidateMix64(\n\t\t\thop.ChannelID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateDiversityPenalty(rt *route.Route) map[candidateEdgeKey]float64 {\n\tpenalty := make(map[candidateEdgeKey]float64)\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == rt.SourcePubKey {\n\t\t\tpenalty[key] += 80_000\n\t\t} else {\n\t\t\tpenalty[key] += 450_000\n\t\t}\n\t}\n\treturn penalty\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 8\n\n\tbase := candidateCopyReservations(r.reserved)\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(base),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\tstate.reservations,\n\t\t\t)\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\tfirst, _, err := r.findRoute(size, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\toptions := []*route.Route{first}\n\t\t\t\tif depth < 3 || len(states) <= 3 {\n\t\t\t\t\talternate, _, altErr := r.findRoute(\n\t\t\t\t\t\tsize, candidateDiversityPenalty(first),\n\t\t\t\t\t)\n\t\t\t\t\tif altErr == nil &&\n\t\t\t\t\t\tcandidateRouteSignature(alternate) !=\n\t\t\t\t\t\t\tcandidateRouteSignature(first) {\n\n\t\t\t\t\t\toptions = append(options, alternate)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, rt := range options {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\trt,\n\t\t\t\t\t)\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, base,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(next.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\tif expanded[i].score == expanded[j].score {\n\t\t\t\treturn len(expanded[i].routes) <\n\t\t\t\t\tlen(expanded[j].routes)\n\t\t\t}\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\treturn nil, 0, false\n}\n\nfunc candidatePartCounts(maxParts int) []int {\n\tseen := make(map[int]bool)\n\tvar counts []int\n\tadd := func(v int) {\n\t\tif v >= 2 && v <= maxParts && !seen[v] {\n\t\t\tseen[v] = true\n\t\t\tcounts = append(counts, v)\n\t\t}\n\t}\n\n\tif maxParts <= 8 {\n\t\tfor i := 2; i <= maxParts; i++ {\n\t\t\tadd(i)\n\t\t}\n\t} else {\n\t\tfor _, value := range []int{2, 3, 4, 6, 8, 12, maxParts} {\n\t\t\tadd(value)\n\t\t}\n\t}\n\tsort.Ints(counts)\n\treturn counts\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.965) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tbase := candidateCopyReservations(r.reserved)\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(base)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(bestPlan, reservations, base)\n\t}\n\n\tfor _, count := range candidatePartCounts(int(parts)) {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil && edge.capacity > 0 {\n\t\tratio := float64(amt) / float64(edge.capacity)\n\t\tswitch {\n\t\tcase ratio >= 0.08:\n\t\t\testimate = edge.capacity * 7 / 8\n\t\tcase ratio >= 0.035:\n\t\t\testimate = edge.capacity * 3 / 4\n\t\tdefault:\n\t\t\testimate = amt * 2\n\t\t}\n\t\tif estimate > edge.capacity {\n\t\t\testimate = edge.capacity\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 18\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t\tif r.suspect[key] > 0 {\n\t\t\t\tr.suspect[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 4\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
},
|
|
"role": "best"
|
|
},
|
|
{
|
|
"id": 14,
|
|
"parent": 3,
|
|
"score": 0.5112,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\tbaseFee, feeRate lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC, maxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFee + amt*e.feeRate/1_000_000\n}\n\nfunc (e *candidateEdge) allows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK, upperFail, estimate lnwire.MilliSatoshi\n\tsuccesses, failures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]map[candidateEdgeKey]candidateBelief\n}{\n\tnetworks: make(\n\t\tmap[candidateNetworkKey]map[candidateEdgeKey]candidateBelief,\n\t),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalance map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateFailure\n\tblocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\tuses map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailure lnwire.MilliSatoshi\n\tattempts int\n\tattemptCap int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\th := candidateMix64(e.key.chanID)\n\th ^= candidateMix64(candidateVertexHash(e.key.from))\n\th ^= candidateMix64(candidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15)\n\th ^= candidateMix64(uint64(e.capacity))\n\th ^= candidateMix64(uint64(e.baseFee) + uint64(e.feeRate)<<17)\n\th ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\th ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(h)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincoming: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalance: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateFailure),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\tuses: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptCap = 12 + 4*int(maxParts)\n\tif r.attemptCap < 20 {\n\t\tr.attemptCap = 20\n\t}\n\tif r.attemptCap > 48 {\n\t\tr.attemptCap = 48\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\te := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFee: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRate: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\te.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(r.incoming[node], e)\n\t\t\t\tr.edges[e.key] = e\n\t\t\t\tfingerprint ^= candidateEdgeHash(e)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = make(map[candidateEdgeKey]candidateBelief)\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tdepletedSide := math.Exp(-x / 0.040)\n\tfundedSide := 1 / (1 + math.Exp((x-0.925)/0.032))\n\tp := 0.48*depletedSide + 0.52*fundedSide\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.989 {\n\t\treturn 0.989\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(e *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > e.capacity || r.blocked[e.key] {\n\t\treturn 0\n\t}\n\n\tretry := 1.0\n\tif f := r.currentFails[e.key]; f.upper > 0 {\n\t\tif total >= f.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(f.upper)\n\t\tswitch {\n\t\tcase ratio > 0.78:\n\t\t\tretry = 0.015\n\t\tcase ratio > 0.62:\n\t\t\tretry = 0.07\n\t\tcase ratio > 0.48:\n\t\t\tretry = 0.22\n\t\tcase ratio > 0.34:\n\t\t\tretry = 0.51\n\t\tcase ratio > 0.22:\n\t\t\tretry = 0.76\n\t\tdefault:\n\t\t\tretry = 0.91\n\t\t}\n\t\tif f.count >= 3 {\n\t\t\tretry *= 0.72\n\t\t}\n\t}\n\n\tif e.key.from == r.source {\n\t\tbalance, ok := r.localBalance[e.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retry\n\t}\n\n\tp := candidatePrior(total, e.capacity)\n\tbelief, ok := r.beliefs[e.key]\n\tif !ok {\n\t\treturn p * retry\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.004\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\tposition := float64(total-belief.lowerOK) /\n\t\t\tfloat64(belief.upperFail-belief.lowerOK)\n\t\tposition = math.Max(0, math.Min(1, position))\n\t\tevidence := 0.996 - 0.992*position\n\t\tp = 0.08*p + 0.92*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.18 {\n\t\t\tscale := 1 - 0.95*(ratio-0.18)/0.82\n\t\t\tp *= math.Max(0.04, scale)\n\t\t}\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(e.capacity)\n\t\tp += 0.72 * math.Exp(-distance/0.18) * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\twidth := math.Max(1, 0.065*float64(e.capacity))\n\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/width,\n\t\t))\n\t\tweight := 0.24\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.38\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateP\n\t}\n\n\tp *= retry\n\treturn math.Max(0.001, math.Min(0.997, p))\n}\n\nfunc (r *candidateRouter) probability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !e.allows(amt) {\n\t\treturn 0\n\t}\n\ttotal := r.reserved[e.key] + amt\n\tif total < amt {\n\t\treturn 0\n\t}\n\treturn r.liquidityProbability(e, total)\n}\n\ntype candidateLabel struct {\n\tscore, logProbability float64\n\trequired lnwire.MilliSatoshi\n}\n\ntype candidatePathItem struct {\n\tnode route.Vertex\n\tscore, logProbability float64\n\trequired lnwire.MilliSatoshi\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype candidatePathQueue []*candidatePathItem\n\nfunc (q candidatePathQueue) Len() int { return len(q) }\nfunc (q candidatePathQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\nfunc (q candidatePathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index, q[j].index = i, j\n}\nfunc (q *candidatePathQueue) Push(x any) {\n\titem := x.(*candidatePathItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *candidatePathQueue) Pop() any {\n\told := *q\n\titem := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn item\n}\n\nfunc candidatePathContains(path []*candidateEdge, node route.Vertex) bool {\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAcceptLabel(labels []candidateLabel,\n\tnext candidateLabel) ([]candidateLabel, bool) {\n\n\tfor _, label := range labels {\n\t\tif label.score <= next.score &&\n\t\t\tlabel.required <= next.required {\n\n\t\t\treturn labels, false\n\t\t}\n\t}\n\n\tfiltered := labels[:0]\n\tfor _, label := range labels {\n\t\tif next.score <= label.score &&\n\t\t\tnext.required <= label.required {\n\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, label)\n\t}\n\tfiltered = append(filtered, next)\n\n\tsort.Slice(filtered, func(i, j int) bool {\n\t\treturn filtered[i].score < filtered[j].score\n\t})\n\tif len(filtered) > 4 {\n\t\tfiltered = filtered[:4]\n\t}\n\n\taccepted := false\n\tfor _, label := range filtered {\n\t\tif label.score == next.score &&\n\t\t\tlabel.required == next.required {\n\n\t\t\taccepted = true\n\t\t\tbreak\n\t\t}\n\t}\n\treturn filtered, accepted\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\tpenalty map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 245_000.0\n\t\thopPenalty = 1_500.0\n\t)\n\n\tlabels := make(map[route.Vertex][]candidateLabel)\n\tlabels[r.spec.Target] = []candidateLabel{{\n\t\trequired: amt,\n\t}}\n\n\tq := &candidatePathQueue{}\n\theap.Push(q, &candidatePathItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tfor q.Len() != 0 {\n\t\titem := heap.Pop(q).(*candidatePathItem)\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(amt, item.path)\n\t\t\treturn rt, item.logProbability, err\n\t\t}\n\n\t\tfor _, edge := range r.incoming[item.node] {\n\t\t\tif candidatePathContains(item.path, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\toverEdge := item.required\n\t\t\tp := r.probability(edge, overEdge)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(overEdge)\n\t\t\t}\n\t\t\trequired := overEdge + fee\n\t\t\tif required < overEdge {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := item.score + float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tuseCount := r.uses[edge.key]\n\t\t\tif useCount > 5 {\n\t\t\t\tuseCount = 5\n\t\t\t}\n\t\t\tsuspectCount := r.suspect[edge.key]\n\t\t\tif suspectCount > 5 {\n\t\t\t\tsuspectCount = 5\n\t\t\t}\n\t\t\tscore += 3_500 * float64(useCount)\n\t\t\tscore += 22_000 * float64(suspectCount)\n\t\t\tscore += penalty[edge.key]\n\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tscore += 35_000\n\t\t\t}\n\n\t\t\tnextLabel := candidateLabel{\n\t\t\t\tscore: score,\n\t\t\t\trequired: required,\n\t\t\t\tlogProbability: item.logProbability + math.Log(p),\n\t\t\t}\n\t\t\tvar accepted bool\n\t\t\tlabels[edge.key.from], accepted = candidateAcceptLabel(\n\t\t\t\tlabels[edge.key.from], nextLabel,\n\t\t\t)\n\t\t\tif !accepted {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = edge\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\theap.Push(q, &candidatePathItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: required,\n\t\t\t\tlogProbability: nextLabel.logProbability,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 || path[0].key.from != r.source ||\n\t\tpath[len(path)-1].key.to != r.spec.Target {\n\n\t\treturn nil, errors.New(\"invalid path\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] + outgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tif !edge.allows(amounts[i]) {\n\t\t\treturn nil, errors.New(\"path violates channel policy\")\n\t\t}\n\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tindex int) (lnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif index == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[index-1].AmtToForward, true\n}\n\nfunc candidateRouteKey(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\th := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\th ^= candidateMix64(\n\t\t\tkey.chanID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th ^= candidateMix64(candidateVertexHash(key.to))\n\t}\n\treturn candidateMix64(h)\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amount := range src {\n\t\tdst[key] = amount\n\t}\n\treturn dst\n}\n\nfunc candidateReserve(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, keyOK := candidateRouteKey(rt, i)\n\t\tamount, amountOK := candidateRouteAmount(rt, i)\n\t\tif keyOK && amountOK {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tcandidateReserve(r.reserved, rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tbase, base * 2 / 3, base * 4 / 3,\n\t\tbase / 2, base * 3 / 2, base / 3, base * 2,\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailure > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailure / 5,\n\t\t\tr.lastFailure / 3,\n\t\t\tr.lastFailure * 2 / 5,\n\t\t\tr.lastFailure / 2,\n\t\t\tr.lastFailure * 3 / 5,\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tvar evidence []lnwire.MilliSatoshi\n\tfor key, belief := range r.beliefs {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK > 0 {\n\t\t\tevidence = append(evidence,\n\t\t\t\tbelief.lowerOK,\n\t\t\t\tbelief.lowerOK*4/5,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > 0 {\n\t\t\tevidence = append(evidence,\n\t\t\t\tbelief.upperFail/3,\n\t\t\t\tbelief.upperFail*2/5,\n\t\t\t\tbelief.upperFail/2,\n\t\t\t)\n\t\t}\n\n\t\tif key.from == r.source || key.to == r.spec.Target {\n\t\t\tevidence = append(evidence,\n\t\t\t\tedge.capacity/5,\n\t\t\t\tedge.capacity*2/5,\n\t\t\t\tedge.capacity*3/5,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(evidence, func(i, j int) bool {\n\t\tdi := math.Abs(float64(evidence[i] - base))\n\t\tdj := math.Abs(float64(evidence[j] - base))\n\t\tif di == dj {\n\t\t\treturn evidence[i] < evidence[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\tif len(evidence) > 8 {\n\t\tevidence = evidence[:8]\n\t}\n\tfor _, value := range evidence {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\tdi := math.Abs(float64(values[i] - base))\n\t\tdj := math.Abs(float64(values[j] - base))\n\t\tif di == dj {\n\t\t\treturn values[i] < values[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\tif len(values) > 16 {\n\t\tvalues = values[:16]\n\t}\n\treturn values\n}\n\ntype candidateRouteOption struct {\n\trt *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeOptions(\n\tamt lnwire.MilliSatoshi) []candidateRouteOption {\n\n\tconst optionCount = 5\n\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\toptions := make([]candidateRouteOption, 0, optionCount)\n\n\tfor n := 0; n < optionCount; n++ {\n\t\trt, logProb, err := r.findRoute(amt, penalties)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\toptions = append(options, candidateRouteOption{\n\t\t\t\trt: rt,\n\t\t\t\tlogProb: logProb,\n\t\t\t})\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalties[key] += 220_000\n\t\t\t} else {\n\t\t\t\tpenalties[key] += 900_000\n\t\t\t}\n\t\t}\n\t}\n\n\treturn options\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\toriginal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\trouteUses := make(map[candidateEdgeKey]uint32)\n\tsent := lnwire.MilliSatoshi(0)\n\n\tfor _, rt := range routes {\n\t\tfinal := candidateFinalAmount(rt)\n\t\tsent += final\n\t\tif rt.TotalAmount > final {\n\t\t\tfees += rt.TotalAmount - final\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteKey(rt, i)\n\t\t\tif ok {\n\t\t\t\trouteUses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 5_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.035 * float64(len(routes)-1)\n\t}\n\tfor key, count := range routeUses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.14 * float64(count-1)\n\t\t}\n\t}\n\n\tif !complete && original > 0 {\n\t\tscore += 0.24 * float64(sent) / float64(original)\n\t}\n\treturn score\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 18\n\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tfor _, size := range r.shardSizes(\n\t\t\t\tstate.remaining, state.slots,\n\t\t\t) {\n\t\t\t\tif size <= 0 || size > state.remaining {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tfor _, option := range r.routeOptions(size) {\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserve(reservations, option.rt)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\toption.rt,\n\t\t\t\t\t)\n\n\t\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, total,\n\t\t\t\t\t\tremaining == 0,\n\t\t\t\t\t)\n\t\t\t\t\tif !math.IsInf(next.score, -1) {\n\t\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.SliceStable(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.90) &&\n\t\tr.lastFailure == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\tif maxPlanParts > 10 {\n\t\tmaxPlanParts = 10\n\t}\n\tif lnwire.MilliSatoshi(maxPlanParts) > total {\n\t\tmaxPlanParts = int(total)\n\t}\n\n\tvar best []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserve(reservations, full)\n\t\tbest = []*route.Route{full}\n\t\tbestScore = r.planScore(best, reservations, total, true)\n\t}\n\n\tfor count := 2; count <= maxPlanParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif ok && (best == nil || score > bestScore) {\n\t\t\tbest = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif best == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn nil, errors.New(\"no route plan found\")\n\t}\n\n\tsort.SliceStable(best, func(i, j int) bool {\n\t\treturn candidateFinalAmount(best[i]) >\n\t\t\tcandidateFinalAmount(best[j])\n\t})\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tfor i := range rt.Hops {\n\t\tkey, keyOK := candidateRouteKey(rt, i)\n\t\tamount, amountOK := candidateRouteAmount(rt, i)\n\t\tedge := r.edges[key]\n\n\t\tif !keyOK || !amountOK || edge == nil ||\n\t\t\tr.blocked[key] || !edge.allows(amount) {\n\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > edge.capacity {\n\t\t\treturn false\n\t\t}\n\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalance[key.chanID]\n\t\t\tif !ok || total > balance {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\tif failure := r.currentFails[key]; failure.upper > 0 &&\n\t\t\ttotal >= failure.upper {\n\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) recordUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif ok {\n\t\t\tr.uses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptCap {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) != 0 {\n\t\trt := r.planned[0]\n\t\tfinal := candidateFinalAmount(rt)\n\t\tif final > 0 && final <= amt && r.routeFits(rt) {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\tr.recordUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = make(map[candidateEdgeKey]candidateBelief)\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure := r.currentFails[key]; failure.upper > 0 &&\n\t\tamount >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\tfundedEstimate := edge.capacity * 7 / 8\n\t\tif fundedEstimate > estimate {\n\t\t\testimate = fundedEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 5\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowMode := edge.capacity / 25\n\t\tif estimate > lowMode {\n\t\t\testimate = lowMode\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markSuspect(rt *route.Route, weight uint32) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, keyOK := candidateRouteKey(rt, i)\n\t\t\tamount, amountOK := candidateRouteAmount(rt, i)\n\t\t\tif keyOK && amountOK {\n\t\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t\t}\n\t\t}\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailure = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailure = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tpassed := failureIndex\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, keyOK := candidateRouteKey(rt, i)\n\t\tamount, amountOK := candidateRouteAmount(rt, i)\n\t\tif keyOK && amountOK {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, keyOK := candidateRouteKey(rt, failureIndex)\n\tamount, amountOK := candidateRouteAmount(rt, failureIndex)\n\tif !keyOK || !amountOK {\n\t\tr.markSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\ttotal := amount + r.reserved[key]\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, total)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.blocked[key] = true\n\t\tr.suspect[key] += 3\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}\n\nvar _ routing.SimRouter = (*candidateRouter)(nil)\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\n\t\t\"candidateRouter(source=%v,target=%v,attempts=%d)\",\n\t\tr.source, r.spec.Target, r.attempts,\n\t)\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 15,
|
|
"parent": 1,
|
|
"score": 0.4349,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*2 + 8\n\tif r.attemptLimit < 12 {\n\t\tr.attemptLimit = 12\n\t}\n\tif r.attemptLimit > 36 {\n\t\tr.attemptLimit = 36\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tif _, ok := r.edges[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.052)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.031))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.28:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.43:\n\t\t\treturn 0\n\t\tcase ratio > 0.72:\n\t\t\treturn 0\n\t\tcase ratio > 0.55:\n\t\t\tretryScale = 0.12\n\t\tcase ratio > 0.38:\n\t\t\tretryScale = 0.30\n\t\tcase ratio > 0.23:\n\t\t\tretryScale = 0.55\n\t\tdefault:\n\t\t\tretryScale = 0.78\n\t\t}\n\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.52\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.004\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence := 0.994 - 0.988*pos\n\t\tp = 0.10*p + 0.90*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.25 {\n\t\t\tscale := 1 - 0.95*(ratio-0.25)/0.75\n\t\t\tif scale < 0.045 {\n\t\t\t\tscale = 0.045\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.68 * math.Exp(-distance/0.20)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.075 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.26\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.38\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tpath []*candidateEdge\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidatePathContains(path []*candidateEdge,\n\tnode route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *candidateRouter) findRouteWithPenalty(\n\tamt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 390_000.0\n\t\thopPenalty = 1_100.0\n\t\tlabelLimit = 3\n\t)\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tpopped := make(map[route.Vertex]int)\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\n\t\tif popped[item.node] >= labelLimit {\n\t\t\tcontinue\n\t\t}\n\t\tpopped[item.node]++\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(amt, item.path)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\t\t\treturn rt, item.logProb, nil\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item.path, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tif fee < 0 || amtOver > math.MaxInt64-fee {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 35_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 760_000\n\n\t\t\tif r.reserved[edge.key] > 0 {\n\t\t\t\tif edge.key.from == r.source {\n\t\t\t\t\tedgeScore += 70_000\n\t\t\t\t} else {\n\t\t\t\t\tedgeScore += 185_000\n\t\t\t\t}\n\t\t\t}\n\t\t\tedgeScore += extraPenalty[edge.key]\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = edge\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\treturn r.findRouteWithPenalty(\n\t\tamt, make(map[candidateEdgeKey]float64),\n\t)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\tif path[0].key.from != r.source {\n\t\treturn nil, errors.New(\"route does not start at source\")\n\t}\n\tif path[len(path)-1].key.to != r.spec.Target {\n\t\treturn nil, errors.New(\"route does not end at target\")\n\t}\n\n\tfor i := 1; i < len(path); i++ {\n\t\tif path[i-1].key.to != path[i].key.from {\n\t\t\treturn nil, errors.New(\"disconnected route\")\n\t\t}\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tfee := forwardingEdge.fee(amtOver[i+1])\n\t\tif fee < 0 || amtOver[i+1] > math.MaxInt64-fee {\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateSameRoute(a, b *route.Route) bool {\n\tif a == nil || b == nil || len(a.Hops) != len(b.Hops) {\n\t\treturn false\n\t}\n\tfor i := range a.Hops {\n\t\tak, aok := candidateRouteEdgeKey(a, i)\n\t\tbk, bok := candidateRouteEdgeKey(b, i)\n\t\tif !aok || !bok || ak != bk {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc candidateRouteHasEdge(rt *route.Route,\n\twanted candidateEdgeKey) bool {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok && key == wanted {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\n\tfor _, rt := range routes {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmt {\n\t\t\tfees += rt.TotalAmount - finalAmt\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 5_500_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.075 * float64(len(routes)-1)\n\t}\n\n\tfor key, count := range uses {\n\t\tif count <= 1 {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source {\n\t\t\tscore -= 0.055 * float64(count-1)\n\t\t} else {\n\t\t\tscore -= 0.16 * float64(count-1)\n\t\t}\n\t}\n\n\tif !complete && total > 0 {\n\t\tunsent := total\n\t\tfor _, rt := range routes {\n\t\t\tunsent -= candidateFinalAmount(rt)\n\t\t}\n\t\tprogress := 1 - float64(unsent)/float64(total)\n\t\tscore += 0.42 * progress\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&sizes, seen, base/2, minimum, maximum)\n\tcandidateAddAmount(&sizes, seen, base*3/4, minimum, maximum)\n\tcandidateAddAmount(&sizes, seen, base, minimum, maximum)\n\tcandidateAddAmount(&sizes, seen, base*5/4, minimum, maximum)\n\tcandidateAddAmount(&sizes, seen, base*3/2, minimum, maximum)\n\tcandidateAddAmount(&sizes, seen, base*2, minimum, maximum)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/4,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*3/8,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\tdi := sizes[i] - base\n\t\tif di < 0 {\n\t\t\tdi = -di\n\t\t}\n\t\tdj := sizes[j] - base\n\t\tif dj < 0 {\n\t\t\tdj = -dj\n\t\t}\n\t\treturn di < dj\n\t})\n\tif len(sizes) > 8 {\n\t\tsizes = sizes[:8]\n\t}\n\n\treturn sizes\n}\n\nfunc (r *candidateRouter) routeAlternatives(\n\tamt lnwire.MilliSatoshi, count int) []*route.Route {\n\n\tpenalty := make(map[candidateEdgeKey]float64)\n\tvar routes []*route.Route\n\n\tfor len(routes) < count {\n\t\trt, _, err := r.findRouteWithPenalty(amt, penalty)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tduplicate := false\n\t\tfor _, existing := range routes {\n\t\t\tif candidateSameRoute(existing, rt) {\n\t\t\t\tduplicate = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif duplicate {\n\t\t\tbreak\n\t\t}\n\n\t\troutes = append(routes, rt)\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalty[key] += 420_000\n\t\t\t} else {\n\t\t\t\tpenalty[key] += 1_350_000\n\t\t\t}\n\t\t}\n\t}\n\n\treturn routes\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 8\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\talternatives := r.routeAlternatives(size, 2)\n\t\t\t\tfor _, rt := range alternatives {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\trt,\n\t\t\t\t\t)\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tcomplete := remaining == 0 && slots == 0\n\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnextState := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnextState.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, total,\n\t\t\t\t\t\tcomplete,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(nextState.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\texpanded = append(expanded, nextState)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc candidatePartCounts(maxParts int) []int {\n\tif maxParts < 2 {\n\t\treturn nil\n\t}\n\n\tvar counts []int\n\tfor count := 2; count <= maxParts && count <= 8; count++ {\n\t\tcounts = append(counts, count)\n\t}\n\n\tif maxParts > 8 {\n\t\tcounts = append(counts, maxParts)\n\t}\n\n\treturn counts\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.78) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(\n\t\t\tbestPlan, reservations, total, true,\n\t\t)\n\t}\n\n\tfor _, count := range candidatePartCounts(int(parts)) {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 7 / 8\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 18\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) discardPlannedThrough(\n\tfailed candidateEdgeKey) {\n\n\tif len(r.planned) == 0 {\n\t\treturn\n\t}\n\n\tfiltered := r.planned[:0]\n\tfor _, planned := range r.planned {\n\t\tif !candidateRouteHasEdge(planned, failed) {\n\t\t\tfiltered = append(filtered, planned)\n\t\t}\n\t}\n\tr.planned = filtered\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.planned = nil\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.planned = nil\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.planned = nil\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.planned = nil\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tr.discardPlannedThrough(key)\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 16,
|
|
"parent": 2,
|
|
"score": 0.5821,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst candidateFinalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts) + 6\n\tif r.attemptLimit < 10 {\n\t\tr.attemptLimit = 10\n\t}\n\tif r.attemptLimit > 32 {\n\t\tr.attemptLimit = 32\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.050)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.033))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity ||\n\t\tr.policyBlocked[edge.key] {\n\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.38:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.55:\n\t\t\treturn 0\n\t\tcase ratio > 0.78:\n\t\t\treturn 0\n\t\tcase ratio > 0.64:\n\t\t\tretryScale = 0.16\n\t\tcase ratio > 0.50:\n\t\t\tretryScale = 0.34\n\t\tcase ratio > 0.36:\n\t\t\tretryScale = 0.58\n\t\tdefault:\n\t\t\tretryScale = 0.82\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.72\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.003\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(total-belief.lowerOK) / width\n\t\tif position < 0 {\n\t\t\tposition = 0\n\t\t}\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\n\t\tevidence := 0.995 - 0.991*position\n\t\tp = 0.10*p + 0.90*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.20 {\n\t\t\tscale := 1 - 0.90*(ratio-0.20)/0.80\n\t\t\tif scale < 0.08 {\n\t\t\t\tscale = 0.08\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tif distance > 0 {\n\t\t\tboost := 0.62 * math.Exp(-distance/0.18)\n\t\t\tp += boost * (1 - p)\n\t\t}\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.07 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.15\n\t\tevidenceCount := belief.successes + belief.failures\n\t\tif evidenceCount >= 2 {\n\t\t\tweight = 0.23\n\t\t}\n\t\tif evidenceCount >= 4 {\n\t\t\tweight = 0.31\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateP\n\t}\n\n\tp *= retryScale\n\tswitch {\n\tcase p < 0.001:\n\t\treturn 0.001\n\tcase p > 0.997:\n\t\treturn 0.997\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\ttotalP := r.liquidityProbability(edge, reserved+amt)\n\tif totalP <= 0 {\n\t\treturn 0\n\t}\n\tif reserved == 0 {\n\t\treturn totalP\n\t}\n\n\tbaseP := r.liquidityProbability(edge, reserved)\n\tif baseP <= 0 {\n\t\treturn 0\n\t}\n\n\tp := totalP / baseP\n\tswitch {\n\tcase p < 0.001:\n\t\treturn 0.001\n\tcase p > 0.997:\n\t\treturn 0.997\n\tdefault:\n\t\treturn p\n\t}\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tedge *candidateEdge\n\tnext *candidatePathLabel\n\thops int\n\tactive bool\n\tindex int\n}\n\ntype candidatePathQueue []*candidatePathLabel\n\nfunc (q candidatePathQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidatePathQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidatePathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidatePathQueue) Push(value any) {\n\tlabel := value.(*candidatePathLabel)\n\tlabel.index = len(*q)\n\t*q = append(*q, label)\n}\n\nfunc (q *candidatePathQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\tlabel := old[last]\n\t*q = old[:last]\n\treturn label\n}\n\nfunc candidatePathContains(label *candidatePathLabel,\n\tnode route.Vertex) bool {\n\n\tfor current := label; current != nil; current = current.next {\n\t\tif current.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(labels map[route.Vertex][]*candidatePathLabel,\n\tlabel *candidatePathLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif old.active && old.score <= label.score &&\n\t\t\told.required <= label.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := current[:0]\n\tfor _, old := range current {\n\t\tif old.active && label.score <= old.score &&\n\t\t\tlabel.required <= old.required {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tif old.active {\n\t\t\tkept = append(kept, old)\n\t\t}\n\t}\n\tkept = append(kept, label)\n\n\tif len(kept) > 3 {\n\t\tsort.Slice(kept, func(i, j int) bool {\n\t\t\tleft := kept[i].score +\n\t\t\t\tfloat64(kept[i].required)/16_000_000\n\t\t\tright := kept[j].score +\n\t\t\t\tfloat64(kept[j].required)/16_000_000\n\t\t\treturn left < right\n\t\t})\n\t\tfor _, old := range kept[3:] {\n\t\t\told.active = false\n\t\t}\n\t\tkept = kept[:3]\n\t}\n\n\tlabels[label.node] = kept\n\treturn label.active\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64,\n\terror) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 780_000.0\n\t\thopPenalty = 22_000.0\n\t\tmaxHops = 30\n\t\tmaxExpand = 14000\n\t)\n\n\ttarget := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidatePathLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tqueue := &candidatePathQueue{}\n\theap.Push(queue, target)\n\n\tvar sourceLabel *candidatePathLabel\n\texpanded := 0\n\n\tfor queue.Len() != 0 && expanded < maxExpand {\n\t\titem := heap.Pop(queue).(*candidatePathLabel)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLabel = item\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\texpanded++\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOverEdge := item.required\n\t\t\tp := r.probability(edge, amountOverEdge)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\trequired := amountOverEdge + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 310_000\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\n\t\t\tlabel := &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: required,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tedge: edge,\n\t\t\t\tnext: item,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tactive: true,\n\t\t\t}\n\t\t\tif candidateInsertLabel(labels, label) {\n\t\t\t\theap.Push(queue, label)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, sourceLabel.hops)\n\tfor current := sourceLabel; current != nil && current.edge != nil;\n\t\tcurrent = current.next {\n\n\t\tpath = append(path, current.edge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLabel.logProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tnode := r.source\n\tfor _, edge := range path {\n\t\tif edge.key.from != node {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, errors.New(\"path does not reach target\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = candidateFinalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tforwardingEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(candidateFinalCltvDelta)\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, index int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif index == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[index-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tcopy := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tcopy[key] = amount\n\t}\n\treturn copy\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tavg := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvalues := make([]lnwire.MilliSatoshi, 0, 12)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tavg * 2 / 3,\n\t\tavg * 5 / 6,\n\t\tavg,\n\t\tavg * 6 / 5,\n\t\tavg * 3 / 2,\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailedShard * 2 / 5,\n\t\t\tr.lastFailedShard / 2,\n\t\t\tr.lastFailedShard * 3 / 5,\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor key, belief := range r.beliefs {\n\t\treserved := r.reserved[key]\n\t\tif belief.lowerOK > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\t(belief.lowerOK-reserved)*19/20,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\t(belief.upperFail-reserved)*9/20,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\t(belief.estimate-reserved)*4/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(values, func(i, j int) bool {\n\t\tleft := math.Abs(math.Log(\n\t\t\tfloat64(values[i]) / float64(avg),\n\t\t))\n\t\tright := math.Abs(math.Log(\n\t\t\tfloat64(values[j]) / float64(avg),\n\t\t))\n\t\tif left == right {\n\t\t\treturn values[i] > values[j]\n\t\t}\n\t\treturn left < right\n\t})\n\tif len(values) > 7 {\n\t\tvalues = values[:7]\n\t}\n\n\treturn values\n}\n\nfunc candidateSharedPenalty(routes []*route.Route) map[candidateEdgeKey]float64 {\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tfor _, rt := range routes {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif key.from == rt.SourcePubKey {\n\t\t\t\tpenalties[key] += 100_000\n\t\t\t} else {\n\t\t\t\tpenalties[key] += 520_000\n\t\t\t}\n\t\t}\n\t}\n\treturn penalties\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations, base map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, total := range reservations {\n\t\told := base[key]\n\t\tif total <= old {\n\t\t\tcontinue\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, total)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tif old > 0 {\n\t\t\toldP := r.liquidityProbability(edge, old)\n\t\t\tif oldP <= 0 {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\t\t\tp /= oldP\n\t\t}\n\n\t\tif p < 0.001 {\n\t\t\tp = 0.001\n\t\t}\n\t\tif p > 0.999 {\n\t\t\tp = 0.999\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 30_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.018 * float64(len(routes)-1)\n\t}\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.025 * float64(count-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tshapePenalty float64\n\tscore float64\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 2\n\n\tbase := candidateCopyReservations(r.reserved)\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\ttargetAverage := float64(total) / float64(parts)\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\treservations: candidateCopyReservations(base),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tslots := parts - depth\n\t\texpanded := make([]candidatePlanState, 0, beamWidth*7)\n\n\t\tfor _, state := range states {\n\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\tstate.reservations,\n\t\t\t)\n\t\t\tsizes := r.shardSizes(state.remaining, slots)\n\t\t\tpenalty := candidateSharedPenalty(state.routes)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tif size <= 0 || size > state.remaining {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tremaining := state.remaining - size\n\t\t\t\tif slots == 1 && remaining != 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif slots > 1 &&\n\t\t\t\t\tremaining < lnwire.MilliSatoshi(slots-1) {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\trt, _, err := r.findRoute(size, penalty)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\troutes := append(\n\t\t\t\t\tappend([]*route.Route(nil), state.routes...),\n\t\t\t\t\trt,\n\t\t\t\t)\n\t\t\t\tshape := state.shapePenalty\n\t\t\t\tratio := float64(size) / targetAverage\n\t\t\t\tif ratio > 0 {\n\t\t\t\t\tshape += 0.10 * math.Abs(math.Log(ratio))\n\t\t\t\t}\n\n\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\tremaining: remaining,\n\t\t\t\t\troutes: routes,\n\t\t\t\t\treservations: reservations,\n\t\t\t\t\tshapePenalty: shape,\n\t\t\t\t}\n\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\troutes, reservations, base,\n\t\t\t\t) - shape\n\t\t\t\tif math.IsInf(next.score, -1) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\texpanded = append(expanded, next)\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 {\n\t\t\tscore := r.planScore(\n\t\t\t\tstate.routes, state.reservations, base,\n\t\t\t)\n\t\t\treturn state.routes, score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc candidatePartCounts(maxParts int) []int {\n\tif maxParts < 2 {\n\t\treturn nil\n\t}\n\n\tseen := make(map[int]bool)\n\tcounts := make([]int, 0, 4)\n\tadd := func(count int) {\n\t\tif count >= 2 && count <= maxParts && !seen[count] {\n\t\t\tseen[count] = true\n\t\t\tcounts = append(counts, count)\n\t\t}\n\t}\n\n\tadd(2)\n\tadd(3)\n\tadd(4)\n\n\tlargest := maxParts\n\tif largest > 8 {\n\t\tlargest = 8\n\t}\n\tadd(largest)\n\n\tsort.Ints(counts)\n\treturn counts\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfullRoute, fullLogProb, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{fullRoute}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.982) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{fullRoute}, nil\n\t}\n\n\tbase := candidateCopyReservations(r.reserved)\n\tvar best []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(base)\n\t\tcandidateReserveInto(reservations, fullRoute)\n\t\tbest = []*route.Route{fullRoute}\n\t\tbestScore = r.planScore(best, reservations, base)\n\t}\n\n\tfor _, count := range candidatePartCounts(int(parts)) {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif best == nil || score > bestScore {\n\t\t\tbest = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif best == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{fullRoute}, nil\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) != 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(plan) == 0 {\n\t\treturn nil, errors.New(\"empty route plan\")\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil && edge.capacity > 0 {\n\t\tratio := float64(amt) / float64(edge.capacity)\n\t\tswitch {\n\t\tcase ratio >= 0.08:\n\t\t\testimate = edge.capacity * 7 / 8\n\t\tcase ratio >= 0.035:\n\t\t\testimate = edge.capacity * 3 / 4\n\t\tdefault:\n\t\t\testimate = amt * 2\n\t\t}\n\t\tif estimate > edge.capacity {\n\t\t\testimate = edge.capacity\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 18\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t\tif r.suspect[key] > 0 {\n\t\t\t\tr.suspect[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failureIndex\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamount, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amount + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 4\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 17,
|
|
"parent": 3,
|
|
"score": 0.1215,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*4 + 10\n\tif r.attemptLimit < 20 {\n\t\tr.attemptLimit = 20\n\t}\n\tif r.attemptLimit > 48 {\n\t\tr.attemptLimit = 48\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := math.Exp(-x / 0.052)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.034))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif amt <= 0 {\n\t\treturn 0.995\n\t}\n\tif amt > edge.capacity {\n\t\treturn 0\n\t}\n\n\tp := candidatePrior(amt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p\n\t}\n\n\tif belief.lowerOK > 0 && amt <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\treturn 0.008\n\t}\n\n\tif belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK &&\n\t\tamt > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(amt-belief.lowerOK) / width\n\t\tevidence := 0.99 - 0.98*position\n\t\tp = 0.15*p + 0.85*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(amt) / float64(belief.upperFail)\n\t\tif ratio > 0.45 {\n\t\t\tscale := 1 - 0.96*(ratio-0.45)/0.55\n\t\t\tif scale < 0.04 {\n\t\t\t\tscale = 0.04\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && amt > belief.lowerOK {\n\t\tdistance := float64(amt-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.5 * math.Exp(-distance/0.16)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.1 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\n\t\tpoint := 1 / (1 + math.Exp(\n\t\t\t(float64(amt)-float64(belief.estimate))/scale,\n\t\t))\n\t\tp = 0.72*p + 0.28*point\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) observedSurvival(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.liquidityProbability(edge, amt)\n\tfailure, failed := r.currentFails[edge.key]\n\tif !failed || failure.upper == 0 {\n\t\treturn p\n\t}\n\tif amt >= failure.upper {\n\t\treturn 0\n\t}\n\n\tfailSurvival := r.liquidityProbability(edge, failure.upper)\n\tdenominator := 1 - failSurvival\n\tif denominator < 0.01 {\n\t\tdenominator = 0.01\n\t}\n\n\tconditional := (p - failSurvival) / denominator\n\tif conditional < 0.002 {\n\t\tconditional = 0.002\n\t}\n\tif conditional > 0.995 {\n\t\tconditional = 0.995\n\t}\n\n\treturn conditional\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\ttotal := amt + reserved\n\tif total > edge.capacity {\n\t\treturn 0\n\t}\n\n\tif failure, ok := r.currentFails[edge.key]; ok {\n\t\tif failure.upper > 0 && total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999\n\t}\n\n\ttotalProbability := r.observedSurvival(edge, total)\n\tif totalProbability <= 0 {\n\t\treturn 0\n\t}\n\n\tif reserved == 0 {\n\t\treturn totalProbability\n\t}\n\n\treservedProbability := r.observedSurvival(edge, reserved)\n\tif reservedProbability <= 0 {\n\t\treturn 0\n\t}\n\n\tconditional := totalProbability / reservedProbability\n\tif conditional < 0.002 {\n\t\treturn 0.002\n\t}\n\tif conditional > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn conditional\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidateCappedCount(value, cap uint32) uint32 {\n\tif value > cap {\n\t\treturn cap\n\t}\n\treturn value\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 500_000.0\n\t\thopPenalty = 3_500.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProbability float64\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProbability = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOverEdge := item.required\n\t\t\tp := r.probability(edge, amountOverEdge)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\tsendingAmount := amountOverEdge + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tuses := candidateCappedCount(r.edgeUses[edge.key], 5)\n\t\t\tsuspect := candidateCappedCount(r.suspect[edge.key], 4)\n\n\t\t\tedgeScore += float64(uses) * 12_000\n\t\t\tedgeScore += float64(suspect) * 115_000\n\n\t\t\tif r.reserved[edge.key] > 0 {\n\t\t\t\tedgeScore += 85_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\toldScore, found := bestScore[edge.key.from]\n\t\t\tif found && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sendingAmount,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLogProbability, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamountOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamountOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamountOver[i] = amountOver[i+1] +\n\t\t\tforwardingEdge.fee(amountOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamountToForward = amountOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amountOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amount := range src {\n\t\tdst[key] = amount\n\t}\n\treturn dst\n}\n\nfunc candidateReserveRoute(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveRoute(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool,\n\tvalue, maximum lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\nfunc candidateScaledAmount(value lnwire.MilliSatoshi,\n\tnumerator, denominator int64) lnwire.MilliSatoshi {\n\n\tif denominator <= 0 {\n\t\treturn value\n\t}\n\treturn value * lnwire.MilliSatoshi(numerator) /\n\t\tlnwire.MilliSatoshi(denominator)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tratios := [][2]int64{\n\t\t{1, 4},\n\t\t{2, 5},\n\t\t{1, 2},\n\t\t{3, 5},\n\t\t{3, 4},\n\t\t{1, 1},\n\t\t{5, 4},\n\t\t{3, 2},\n\t\t{7, 4},\n\t\t{2, 1},\n\t\t{5, 2},\n\t\t{3, 1},\n\t}\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, ratio := range ratios {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen,\n\t\t\tcandidateScaledAmount(base, ratio[0], ratio[1]),\n\t\t\tremaining,\n\t\t)\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/3, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/12, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8, remaining,\n\t\t)\n\t}\n\n\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\treturn sizes[i] > sizes[j]\n\t})\n\n\treturn sizes\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots uint32\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tsearchScore float64\n\tjointScore float64\n}\n\nfunc candidatePlanBetter(a, b *candidatePlanState) bool {\n\tif a.remaining == 0 && b.remaining != 0 {\n\t\treturn true\n\t}\n\tif a.remaining != 0 && b.remaining == 0 {\n\t\treturn false\n\t}\n\tif a.searchScore != b.searchScore {\n\t\treturn a.searchScore > b.searchScore\n\t}\n\treturn a.remaining < b.remaining\n}\n\nfunc (r *candidateRouter) planBeam(total lnwire.MilliSatoshi,\n\tparts uint32, sizeBias float64) ([]*route.Route, float64, bool) {\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tstates := []*candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(savedReservations),\n\t}}\n\n\tvar completed []*candidatePlanState\n\n\tfor depth := uint32(0); depth < parts && len(states) > 0; depth++ {\n\t\tvar nextStates []*candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tif state.remaining == 0 {\n\t\t\t\tcompleted = append(completed, state)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif state.slots == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbase := (state.remaining +\n\t\t\t\tlnwire.MilliSatoshi(state.slots) - 1) /\n\t\t\t\tlnwire.MilliSatoshi(state.slots)\n\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\trt, logProbability, err := r.findRoute(size)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfees := rt.TotalAmount - size\n\t\t\t\tsizeReward := math.Log(\n\t\t\t\t\tfloat64(size) / float64(base),\n\t\t\t\t)\n\n\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tcandidateReserveRoute(reservations, rt)\n\n\t\t\t\troutes := make(\n\t\t\t\t\t[]*route.Route, len(state.routes), len(state.routes)+1,\n\t\t\t\t)\n\t\t\t\tcopy(routes, state.routes)\n\t\t\t\troutes = append(routes, rt)\n\n\t\t\t\tjointIncrement := logProbability -\n\t\t\t\t\tfloat64(fees)/4_000_000 - 0.02\n\n\t\t\t\tnext := &candidatePlanState{\n\t\t\t\t\tremaining: state.remaining - size,\n\t\t\t\t\tslots: state.slots - 1,\n\t\t\t\t\troutes: routes,\n\t\t\t\t\treservations: reservations,\n\t\t\t\t\tjointScore: state.jointScore +\n\t\t\t\t\t\tjointIncrement,\n\t\t\t\t\tsearchScore: state.searchScore +\n\t\t\t\t\t\tjointIncrement + sizeBias*sizeReward,\n\t\t\t\t}\n\n\t\t\t\tif next.remaining == 0 {\n\t\t\t\t\tcompleted = append(completed, next)\n\t\t\t\t} else {\n\t\t\t\t\tnextStates = append(nextStates, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(nextStates, func(i, j int) bool {\n\t\t\treturn candidatePlanBetter(nextStates[i], nextStates[j])\n\t\t})\n\n\t\tconst beamWidth = 10\n\t\tif len(nextStates) > beamWidth {\n\t\t\tnextStates = nextStates[:beamWidth]\n\t\t}\n\t\tstates = nextStates\n\t}\n\n\tif len(completed) == 0 {\n\t\treturn nil, 0, false\n\t}\n\n\tbest := completed[0]\n\tfor _, state := range completed[1:] {\n\t\tif state.jointScore > best.jointScore {\n\t\t\tbest = state\n\t\t}\n\t}\n\n\treturn best.routes, best.jointScore, true\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tparts uint32) (*route.Route, error) {\n\n\tsizes := r.shardSizes(total, parts)\n\tvar bestRoute *route.Route\n\tbestUtility := math.Inf(-1)\n\n\tfor _, size := range sizes {\n\t\trt, logProbability, err := r.findRoute(size)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfees := rt.TotalAmount - size\n\t\tprogress := math.Log1p(float64(size)) -\n\t\t\tmath.Log1p(float64(total))\n\n\t\tutility := logProbability + 0.75*progress -\n\t\t\tfloat64(fees)/4_000_000\n\n\t\tif bestRoute == nil || utility > bestUtility {\n\t\t\tbestRoute = rt\n\t\t\tbestUtility = utility\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn bestRoute, nil\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32, inFlight uint32) ([]*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tif inFlight == 0 && r.lastFailedShard == 0 {\n\t\tfull, logProbability, err := r.findRoute(total)\n\t\tif err == nil && logProbability >= math.Log(0.3) {\n\t\t\treturn []*route.Route{full}, nil\n\t\t}\n\t}\n\n\tbiases := []float64{0.18, 0.38, 0.62, 0.9}\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor _, bias := range biases {\n\t\tplan, score, ok := r.planBeam(total, parts, bias)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan != nil {\n\t\treturn bestPlan, nil\n\t}\n\n\trt, err := r.fallbackShard(total, parts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{rt}, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\n\t\tif finalAmount > 0 && finalAmount <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft, inFlightHtlcs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 2 / 5\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\treturn nil\n\t}\n\n\tpassedEdges := failureIndex\n\tif passedEdges > len(rt.Hops) {\n\t\tpassedEdges = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < passedEdges; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tamountOver, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amountOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 18,
|
|
"parent": 1,
|
|
"score": 0.0,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*10 + 24\n\tif r.attemptLimit < 48 {\n\t\tr.attemptLimit = 48\n\t}\n\tif r.attemptLimit > 96 {\n\t\tr.attemptLimit = 96\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) retryCeiling(\n\tfailure candidateCurrentFailure) lnwire.MilliSatoshi {\n\n\tif failure.upper <= 1 {\n\t\treturn 0\n\t}\n\n\tnumerator := lnwire.MilliSatoshi(11)\n\tdenominator := lnwire.MilliSatoshi(16)\n\tswitch {\n\tcase failure.count >= 5:\n\t\tnumerator = 1\n\t\tdenominator = 2\n\tcase failure.count >= 3:\n\t\tnumerator = 9\n\t\tdenominator = 16\n\tcase failure.count >= 2:\n\t\tnumerator = 5\n\t\tdenominator = 8\n\t}\n\n\tceiling := failure.upper * numerator / denominator\n\tif ceiling >= failure.upper {\n\t\tceiling = failure.upper - 1\n\t}\n\treturn ceiling\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treserved := r.reserved[edge.key]\n\tif amt <= 0 || reserved > edge.capacity ||\n\t\tamt > edge.capacity-reserved {\n\n\t\treturn 0\n\t}\n\ttotal := amt + reserved\n\n\tif !edge.policyAllows(amt) || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok {\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\t\tceiling := r.retryCeiling(failure)\n\t\tif ceiling == 0 || total > ceiling {\n\t\t\treturn 0\n\t\t}\n\n\t\tretryScale = 0.48 /\n\t\t\t(1 + 0.16*float64(failure.count-1))\n\t\tif retryScale < 0.16 {\n\t\t\tretryScale = 0.16\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || reserved > balance || amt > balance-reserved {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retryScale\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995 * retryScale\n\t}\n\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\tif p > 0.012 {\n\t\t\tp = 0.012\n\t\t}\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK {\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.985 - 0.97*pos\n\t\tp = 0.18*p + 0.82*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.45 {\n\t\t\tscale := 1 - 0.94*(ratio-0.45)/0.55\n\t\t\tif scale < 0.06 {\n\t\t\t\tscale = 0.06\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.50 * math.Exp(-distance/0.18)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.11 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tp = 0.68*p + 0.32*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.002 {\n\t\treturn 0.002\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tidx int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\titem := x.(*candidateDijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 420_000.0\n\t\thopPenalty = 900.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 18_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 230_000\n\n\t\t\tif reserved := r.reserved[edge.key]; reserved > 0 {\n\t\t\t\tif edge.key.from == r.source {\n\t\t\t\t\tedgeScore += 110_000\n\t\t\t\t} else {\n\t\t\t\t\tedgeScore += 470_000\n\t\t\t\t}\n\t\t\t\tutil := float64(reserved) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t\tedgeScore += 180_000 * util\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.reserved[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmax lnwire.MilliSatoshi) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > max {\n\t\tvalue = max\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\nfunc (r *candidateRouter) planSizes(remaining lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&sizes, seen, base/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*2/3, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/4, remaining)\n\tcandidateAddAmount(&sizes, seen, base, remaining)\n\tcandidateAddAmount(&sizes, seen, base*5/4, remaining)\n\tcandidateAddAmount(&sizes, seen, base*3/2, remaining)\n\tcandidateAddAmount(&sizes, seen, base*2, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining/3, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining/2, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining*2/3, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining*3/4, remaining)\n\tcandidateAddAmount(&sizes, seen, remaining, remaining)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*9/16, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8, remaining,\n\t\t)\n\t}\n\n\tfor _, failure := range r.currentFails {\n\t\tif failure.upper == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tceiling := r.retryCeiling(failure)\n\t\tcandidateAddAmount(&sizes, seen, ceiling/2, remaining)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, ceiling*3/4, remaining,\n\t\t)\n\t\tcandidateAddAmount(&sizes, seen, ceiling, remaining)\n\t}\n\n\treturn sizes\n}\n\nfunc (r *candidateRouter) routeShardHint(rt *route.Route,\n\tmax lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tfinalAmt := candidateFinalAmount(rt)\n\tif finalAmt <= 0 {\n\t\treturn 0\n\t}\n\n\thint := max\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn 0\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn 0\n\t\t}\n\t\tover, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || over <= 0 {\n\t\t\treturn 0\n\t\t}\n\n\t\treserved := r.reserved[key]\n\t\ttotalTarget := edge.capacity * 13 / 16\n\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalances[key.chanID]\n\t\t\tif !ok {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\ttotalTarget = balance\n\t\t} else if belief, ok := r.beliefs[key]; ok {\n\t\t\tswitch {\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\t\ttotalTarget = belief.lowerOK +\n\t\t\t\t\t(belief.upperFail-belief.lowerOK)*7/16\n\n\t\t\tcase belief.upperFail > 0:\n\t\t\t\ttotalTarget = belief.upperFail * 9 / 16\n\n\t\t\tcase belief.lowerOK > 0:\n\t\t\t\ttarget := edge.capacity * 13 / 16\n\t\t\t\tif belief.lowerOK > target {\n\t\t\t\t\ttarget = belief.lowerOK\n\t\t\t\t}\n\t\t\t\ttotalTarget = target\n\t\t\t}\n\n\t\t\tif belief.estimate > 0 &&\n\t\t\t\tbelief.estimate < totalTarget {\n\n\t\t\t\ttotalTarget = (totalTarget + belief.estimate) / 2\n\t\t\t}\n\t\t}\n\n\t\tif failure, ok := r.currentFails[key]; ok {\n\t\t\tceiling := r.retryCeiling(failure)\n\t\t\tif ceiling < totalTarget {\n\t\t\t\ttotalTarget = ceiling\n\t\t\t}\n\t\t}\n\n\t\tif totalTarget > edge.capacity {\n\t\t\ttotalTarget = edge.capacity\n\t\t}\n\t\tif reserved >= totalTarget {\n\t\t\treturn 0\n\t\t}\n\n\t\tallowed := totalTarget - reserved\n\t\tif edge.maxHTLC > 0 && edge.maxHTLC < allowed {\n\t\t\tallowed = edge.maxHTLC\n\t\t}\n\t\tif allowed < edge.minHTLC {\n\t\t\treturn 0\n\t\t}\n\n\t\tscaled := lnwire.MilliSatoshi(\n\t\t\tfloat64(finalAmt) * float64(allowed) /\n\t\t\t\tfloat64(over),\n\t\t)\n\t\tif scaled < hint {\n\t\t\thint = scaled\n\t\t}\n\t}\n\n\tif hint < 1 {\n\t\treturn 0\n\t}\n\tif hint > max {\n\t\treturn max\n\t}\n\treturn hint\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots uint32\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n\trank float64\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tbeam := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(savedReservations),\n\t}}\n\n\tvar complete []candidatePlanState\n\tconst (\n\t\tbeamWidth = 36\n\t\tmaxSizeTests = 40\n\t)\n\n\tfor depth := uint32(0); depth < parts && len(beam) > 0; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range beam {\n\t\t\tsizes := r.planSizes(state.remaining, state.slots)\n\t\t\tsizeSeen := make(map[lnwire.MilliSatoshi]bool)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tsizeSeen[size] = true\n\t\t\t}\n\n\t\t\tfor sizeIndex := 0; sizeIndex < len(sizes) &&\n\t\t\t\tsizeIndex < maxSizeTests; sizeIndex++ {\n\n\t\t\t\tsize := sizes[sizeIndex]\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\trt, logProb, err := r.findRoute(size)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\thint := r.routeShardHint(rt, state.remaining)\n\t\t\t\tif hint > 0 {\n\t\t\t\t\tcandidateAddAmount(\n\t\t\t\t\t\t&sizes, sizeSeen, hint*3/4,\n\t\t\t\t\t\tstate.remaining,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateAddAmount(\n\t\t\t\t\t\t&sizes, sizeSeen, hint,\n\t\t\t\t\t\tstate.remaining,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateAddAmount(\n\t\t\t\t\t\t&sizes, sizeSeen, hint*9/8,\n\t\t\t\t\t\tstate.remaining,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tfees := rt.TotalAmount - size\n\t\t\t\tr.reserveRoute(rt)\n\n\t\t\t\tnextRemaining := state.remaining - size\n\t\t\t\tnextScore := state.score + logProb -\n\t\t\t\t\tfloat64(fees)/4_000_000 - 0.045\n\n\t\t\t\tnextRoutes := make(\n\t\t\t\t\t[]*route.Route, len(state.routes),\n\t\t\t\t\tlen(state.routes)+1,\n\t\t\t\t)\n\t\t\t\tcopy(nextRoutes, state.routes)\n\t\t\t\tnextRoutes = append(nextRoutes, rt)\n\n\t\t\t\tprogress := float64(total-nextRemaining) /\n\t\t\t\t\tfloat64(total)\n\t\t\t\texpectedProgress := float64(len(nextRoutes)) /\n\t\t\t\t\tfloat64(parts)\n\t\t\t\tbalancePenalty := 7.0 *\n\t\t\t\t\tmath.Abs(progress-expectedProgress)\n\n\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\tremaining: nextRemaining,\n\t\t\t\t\tslots: state.slots - 1,\n\t\t\t\t\troutes: nextRoutes,\n\t\t\t\t\treservations: candidateCopyReservations(\n\t\t\t\t\t\tr.reserved,\n\t\t\t\t\t),\n\t\t\t\t\tscore: nextScore,\n\t\t\t\t\trank: nextScore - balancePenalty,\n\t\t\t\t}\n\n\t\t\t\tif nextRemaining == 0 {\n\t\t\t\t\tcomplete = append(complete, next)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif next.slots > 0 {\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\tif expanded[i].rank == expanded[j].rank {\n\t\t\t\treturn expanded[i].remaining <\n\t\t\t\t\texpanded[j].remaining\n\t\t\t}\n\t\t\treturn expanded[i].rank > expanded[j].rank\n\t\t})\n\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tbeam = expanded\n\t}\n\n\tif len(complete) == 0 {\n\t\tr.reserved = savedReservations\n\t\trt, _, err := r.findRoute(total)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\tsort.Slice(complete, func(i, j int) bool {\n\t\tif complete[i].score == complete[j].score {\n\t\t\treturn len(complete[i].routes) <\n\t\t\t\tlen(complete[j].routes)\n\t\t}\n\t\treturn complete[i].score > complete[j].score\n\t})\n\n\treturn complete[0].routes, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tpartAmt := candidateFinalAmount(rt)\n\t\tif partAmt > 0 && partAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif r.suspect[key] > 0 {\n\t\tr.suspect[key]--\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 5 / 8\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t} else {\n\t\tbelief.estimate =\n\t\t\t(belief.estimate*3 + estimate) / 4\n\t}\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.suspect[key]++\n\t\tr.markRouteSuspect(rt, 1)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 19,
|
|
"parent": 6,
|
|
"score": 0.408,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = 12 + int(maxParts)*2\n\tif r.attemptLimit < 14 {\n\t\tr.attemptLimit = 14\n\t}\n\tif r.attemptLimit > 48 {\n\t\tr.attemptLimit = 48\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.055)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.035))\n\tp := 0.5*lowMode + 0.5*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\ttotal := amt + r.reserved[edge.key]\n\tif !edge.policyAllows(amt) || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tretryScale = 0.55 * math.Pow(1-ratio, 0.45)\n\t\tif retryScale < 0.10 {\n\t\t\tretryScale = 0.10\n\t\t}\n\t\tif failure.count > 1 {\n\t\t\tretryScale *= math.Pow(\n\t\t\t\t0.78, float64(failure.count-1),\n\t\t\t)\n\t\t\tif retryScale < 0.06 {\n\t\t\t\tretryScale = 0.06\n\t\t\t}\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999 * retryScale\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && total <= belief.lowerOK {\n\t\treturn 0.995 * retryScale\n\t}\n\n\tif belief.upperFail > 0 && total >= belief.upperFail {\n\t\tif p > 0.008 {\n\t\t\tp = 0.008\n\t\t}\n\t\treturn p * retryScale\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK {\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence := 0.99 - 0.975*pos\n\t\tp = 0.18*p + 0.82*evidence\n\t} else if belief.upperFail > 0 {\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.35 {\n\t\t\tscale := 1 - 0.94*(ratio-0.35)/0.65\n\t\t\tif scale < 0.06 {\n\t\t\t\tscale = 0.06\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\t} else if belief.lowerOK > 0 && total > belief.lowerOK {\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.50 * math.Exp(-distance/0.16)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.10 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tp = 0.72*p + 0.28*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tpath []*candidateEdge\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype candidateLabel struct {\n\tscore float64\n\trequired lnwire.MilliSatoshi\n}\n\nfunc candidateAcceptLabel(labels map[route.Vertex][]candidateLabel,\n\tnode route.Vertex, score float64,\n\trequired lnwire.MilliSatoshi) bool {\n\n\told := labels[node]\n\tfor _, label := range old {\n\t\tif label.score <= score && label.required <= required {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfiltered := old[:0]\n\tfor _, label := range old {\n\t\tif score <= label.score && required <= label.required {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, label)\n\t}\n\tfiltered = append(filtered, candidateLabel{\n\t\tscore: score,\n\t\trequired: required,\n\t})\n\n\tif len(filtered) > 8 {\n\t\tsort.Slice(filtered, func(i, j int) bool {\n\t\t\treturn filtered[i].score < filtered[j].score\n\t\t})\n\t\tfiltered = filtered[:8]\n\n\t\tfound := false\n\t\tfor _, label := range filtered {\n\t\t\tif label.score == score &&\n\t\t\t\tlabel.required == required {\n\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tlabels[node] = filtered\n\t\t\treturn false\n\t\t}\n\t}\n\n\tlabels[node] = filtered\n\treturn true\n}\n\nfunc candidateLabelLive(labels map[route.Vertex][]candidateLabel,\n\titem *candidateDijkstraItem) bool {\n\n\tfor _, label := range labels[item.node] {\n\t\tif label.score == item.score &&\n\t\t\tlabel.required == item.required {\n\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidatePathContains(path []*candidateEdge,\n\tnode route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (\n\t*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 420_000.0\n\t\thopPenalty = 1_200.0\n\t\tmaxHops = 20\n\t)\n\n\tlabels := make(map[route.Vertex][]candidateLabel)\n\tlabels[r.spec.Target] = []candidateLabel{{\n\t\tscore: 0,\n\t\trequired: amt,\n\t}}\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tif !candidateLabelLive(labels, item) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(item.path, amt)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\t\t\treturn rt, item.logProb, nil\n\t\t}\n\n\t\tif len(item.path) >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item.path, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\t\t\tif sending < amtOver {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tutilization := float64(\n\t\t\t\tamtOver+r.reserved[edge.key],\n\t\t\t) / float64(edge.capacity)\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty +\n\t\t\t\t45_000*utilization*utilization\n\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 18_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 210_000\n\n\t\t\tif r.reserved[edge.key] > 0 {\n\t\t\t\tedgeScore += 190_000\n\t\t\t}\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\tif !candidateAcceptLabel(\n\t\t\t\tlabels, edge.key.from, score, sending,\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = edge\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\tif path[0].key.from != r.source ||\n\t\tpath[len(path)-1].key.to != r.spec.Target {\n\n\t\treturn nil, errors.New(\"route endpoints do not match\")\n\t}\n\tfor i := 1; i < len(path); i++ {\n\t\tif path[i-1].key.to != path[i].key.from {\n\t\t\treturn nil, fmt.Errorf(\"broken path at hop %d\", i)\n\t\t}\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif channelIndex < 0 || channelIndex >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateCopyLogs(src map[candidateEdgeKey]float64) map[\n\tcandidateEdgeKey]float64 {\n\n\tdst := make(map[candidateEdgeKey]float64, len(src))\n\tfor key, value := range src {\n\t\tdst[key] = value\n\t}\n\treturn dst\n}\n\nfunc candidateReserve(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserve(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateRouteID(rt *route.Route) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, hop := range rt.Hops {\n\t\th ^= candidateMix64(hop.ChannelID)\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\ntype candidateRouteChoice struct {\n\trt *route.Route\n}\n\nfunc (r *candidateRouter) routeChoices(\n\tamt lnwire.MilliSatoshi) []candidateRouteChoice {\n\n\tfirst, _, err := r.findRoute(amt, nil)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tchoices := []candidateRouteChoice{{rt: first}}\n\tseen := map[uint64]bool{candidateRouteID(first): true}\n\n\tpenalty := make(map[candidateEdgeKey]float64)\n\tfor i := range first.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(first, i)\n\t\tif ok {\n\t\t\tpenalty[key] = 720_000\n\t\t}\n\t}\n\n\talternate, _, err := r.findRoute(amt, penalty)\n\tif err == nil {\n\t\tid := candidateRouteID(alternate)\n\t\tif !seen[id] {\n\t\t\tchoices = append(choices, candidateRouteChoice{\n\t\t\t\trt: alternate,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn choices\n}\n\nfunc candidateAddPlanSize(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tremaining lnwire.MilliSatoshi, slots uint32) {\n\n\tif value <= 0 {\n\t\tvalue = 1\n\t}\n\tif value > remaining {\n\t\tvalue = remaining\n\t}\n\tif value != remaining {\n\t\tleft := remaining - value\n\t\tif left < lnwire.MilliSatoshi(slots-1) {\n\t\t\treturn\n\t\t}\n\t}\n\tif !seen[value] {\n\t\tseen[value] = true\n\t\t*values = append(*values, value)\n\t}\n}\n\nfunc (r *candidateRouter) planSizes(remaining lnwire.MilliSatoshi,\n\tslots uint32) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tavg := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tadd := func(value lnwire.MilliSatoshi) {\n\t\tcandidateAddPlanSize(\n\t\t\t&values, seen, value, remaining, slots,\n\t\t)\n\t}\n\n\tadd(remaining)\n\tadd(avg)\n\tadd(avg * 3 / 4)\n\tadd(avg * 5 / 4)\n\tadd(avg * 3 / 2)\n\tadd(avg * 2)\n\tadd(remaining / 3)\n\tadd(remaining / 2)\n\tadd(remaining * 2 / 3)\n\tadd(remaining * 3 / 4)\n\n\tif r.lastFailedShard > 0 {\n\t\tadd(r.lastFailedShard / 2)\n\t\tadd(r.lastFailedShard * 5 / 8)\n\t\tadd(r.lastFailedShard * 3 / 4)\n\t}\n\n\treturn values\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgeLogs map[candidateEdgeKey]float64\n\tscore float64\n\trank float64\n}\n\nfunc (r *candidateRouter) extendPlan(state *candidatePlanState,\n\trt *route.Route, total lnwire.MilliSatoshi) (\n\t*candidatePlanState, bool) {\n\n\tsize := candidateFinalAmount(rt)\n\tif size <= 0 || size > state.remaining {\n\t\treturn nil, false\n\t}\n\n\treservations := candidateCopyReservations(state.reservations)\n\tlogs := candidateCopyLogs(state.edgeLogs)\n\tsaved := r.reserved\n\tr.reserved = reservations\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tincrement := 0.0\n\treused := 0\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn nil, false\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif !ok {\n\t\t\treturn nil, false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tp := r.probability(edge, amt)\n\t\tif p <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\t\tnewLog := math.Log(p)\n\n\t\tif oldLog, ok := logs[key]; ok {\n\t\t\tdelta := newLog - oldLog\n\t\t\tif delta > 0 {\n\t\t\t\tdelta = 0\n\t\t\t}\n\t\t\tincrement += delta\n\t\t\tif key.from != r.source {\n\t\t\t\treused++\n\t\t\t}\n\t\t} else {\n\t\t\tincrement += newLog\n\t\t}\n\t\tlogs[key] = newLog\n\t}\n\n\tfees := rt.TotalAmount - size\n\tscore := state.score + increment -\n\t\tfloat64(fees)/4_000_000 - 0.04 -\n\t\tfloat64(reused)*0.06\n\n\tcandidateReserve(reservations, rt)\n\troutes := append([]*route.Route(nil), state.routes...)\n\troutes = append(routes, rt)\n\tremaining := state.remaining - size\n\tprogress := 1 - float64(remaining)/float64(total)\n\n\treturn &candidatePlanState{\n\t\tremaining: remaining,\n\t\troutes: routes,\n\t\treservations: reservations,\n\t\tedgeLogs: logs,\n\t\tscore: score,\n\t\trank: score + 1.35*progress,\n\t}, true\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\tif parts > 16 {\n\t\tparts = 16\n\t}\n\n\tsaved := r.reserved\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []*candidatePlanState{{\n\t\tremaining: total,\n\t\treservations: candidateCopyReservations(saved),\n\t\tedgeLogs: make(map[candidateEdgeKey]float64),\n\t}}\n\n\tvar best *candidatePlanState\n\tconst beamWidth = 8\n\n\tfor depth := uint32(0); depth < parts && len(states) > 0; depth++ {\n\t\tslots := parts - depth\n\t\tvar next []*candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tif state.remaining == 0 {\n\t\t\t\tif best == nil || state.score > best.score {\n\t\t\t\t\tbest = state\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsizes := r.planSizes(state.remaining, slots)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = state.reservations\n\t\t\t\tfor _, choice := range r.routeChoices(size) {\n\t\t\t\t\textended, ok := r.extendPlan(\n\t\t\t\t\t\tstate, choice.rt, total,\n\t\t\t\t\t)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif extended.remaining == 0 {\n\t\t\t\t\t\tif best == nil ||\n\t\t\t\t\t\t\textended.score > best.score {\n\n\t\t\t\t\t\t\tbest = extended\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif slots > 1 {\n\t\t\t\t\t\tnext = append(next, extended)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(next, func(i, j int) bool {\n\t\t\treturn next[i].rank > next[j].rank\n\t\t})\n\t\tif len(next) > beamWidth {\n\t\t\tnext = next[:beamWidth]\n\t\t}\n\t\tstates = next\n\t}\n\n\tif best == nil || len(best.routes) == 0 {\n\t\tr.reserved = saved\n\t\trt, _, err := r.findRoute(total, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*route.Route{rt}, nil\n\t}\n\n\treturn best.routes, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tpartAmt := candidateFinalAmount(rt)\n\t\tif partAmt > 0 && partAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif amt > belief.estimate {\n\t\tbelief.estimate = amt\n\t}\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt * 5 / 8\n\tif belief.estimate == 0 || belief.estimate >= amt {\n\t\tbelief.estimate = estimate\n\t}\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 20,
|
|
"parent": 0,
|
|
"score": 0.3409,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\trouteFailures map[uint64]uint32\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\trouteFailures: make(map[uint64]uint32),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts) + 5\n\tif r.attemptLimit < 9 {\n\t\tr.attemptLimit = 9\n\t}\n\tif r.attemptLimit > 18 {\n\t\tr.attemptLimit = 18\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tif _, ok := r.edges[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.050)\n\thighMode := 1 / (1 + math.Exp((x-0.92)/0.032))\n\tp := 0.46*lowMode + 0.54*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.986 {\n\t\treturn 0.986\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9998\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.26:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.42:\n\t\t\treturn 0\n\t\tcase ratio > 0.68:\n\t\t\treturn 0\n\t\tcase ratio > 0.52:\n\t\t\tretryScale = 0.16\n\t\tcase ratio > 0.36:\n\t\t\tretryScale = 0.38\n\t\tcase ratio > 0.23:\n\t\t\tretryScale = 0.66\n\t\tdefault:\n\t\t\tretryScale = 0.88\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.72\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.002\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence := 0.996 - 0.993*pos\n\t\tp = 0.10*p + 0.90*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tswitch {\n\t\tcase ratio >= 0.82:\n\t\t\tp *= 0.04\n\t\tcase ratio >= 0.60:\n\t\t\tp *= 0.15\n\t\tcase ratio >= 0.40:\n\t\t\tp *= 0.38\n\t\tcase ratio >= 0.24:\n\t\t\tp *= 0.68\n\t\t}\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tif distance > 0 {\n\t\t\tboost := 0.66 * math.Exp(-distance/0.17)\n\t\t\tp += boost * (1 - p)\n\t\t}\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.065 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.18\n\t\tevidence := belief.successes + belief.failures\n\t\tif evidence >= 2 {\n\t\t\tweight = 0.27\n\t\t}\n\t\tif evidence >= 5 {\n\t\t\tweight = 0.36\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.0005 {\n\t\treturn 0.0005\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\ttotalP := r.liquidityProbability(edge, reserved+amt)\n\tif totalP <= 0 {\n\t\treturn 0\n\t}\n\tif reserved == 0 {\n\t\treturn totalP\n\t}\n\n\tbaseP := r.liquidityProbability(edge, reserved)\n\tif baseP <= 0 {\n\t\treturn 0\n\t}\n\n\tp := totalP / baseP\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\tif p < 0.0005 {\n\t\treturn 0.0005\n\t}\n\treturn p\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tedge *candidateEdge\n\tnext *candidatePathLabel\n\thops int\n\tactive bool\n\tidx int\n}\n\ntype candidatePathQueue []*candidatePathLabel\n\nfunc (q candidatePathQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidatePathQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidatePathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidatePathQueue) Push(x any) {\n\titem := x.(*candidatePathLabel)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidatePathQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidatePathContains(label *candidatePathLabel,\n\tnode route.Vertex) bool {\n\n\tfor cur := label; cur != nil; cur = cur.next {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(labels map[route.Vertex][]*candidatePathLabel,\n\tlabel *candidatePathLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif old.active && old.score <= label.score &&\n\t\t\told.required <= label.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := current[:0]\n\tfor _, old := range current {\n\t\tif old.active && label.score <= old.score &&\n\t\t\tlabel.required <= old.required {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tif old.active {\n\t\t\tkept = append(kept, old)\n\t\t}\n\t}\n\n\tkept = append(kept, label)\n\tif len(kept) > 6 {\n\t\tsort.Slice(kept, func(i, j int) bool {\n\t\t\tli := kept[i].score +\n\t\t\t\tfloat64(kept[i].required)/16_000_000\n\t\t\tlj := kept[j].score +\n\t\t\t\tfloat64(kept[j].required)/16_000_000\n\t\t\treturn li < lj\n\t\t})\n\t\tfor _, old := range kept[6:] {\n\t\t\told.active = false\n\t\t}\n\t\tkept = kept[:6]\n\t}\n\n\tlabels[label.node] = kept\n\treturn label.active\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64,\n\terror) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 1_150_000.0\n\t\thopPenalty = 70_000.0\n\t\tmaxHops = 24\n\t\tmaxExpand = 32000\n\t)\n\n\ttarget := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidatePathLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tpq := &candidatePathQueue{}\n\theap.Push(pq, target)\n\n\texpanded := 0\n\tvar sourceLabel *candidatePathLabel\n\n\tfor pq.Len() > 0 && expanded < maxExpand {\n\t\titem := heap.Pop(pq).(*candidatePathLabel)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLabel = item\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\texpanded++\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tsuspectPenalty := float64(r.suspect[edge.key]) * 650_000\n\t\t\tif failure := r.currentFails[edge.key]; failure.count > 1 {\n\t\t\t\tsuspectPenalty += float64(failure.count-1) * 250_000\n\t\t\t}\n\t\t\tedgeScore += suspectPenalty\n\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\n\t\t\tlabel := &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tedge: edge,\n\t\t\t\tnext: item,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tactive: true,\n\t\t\t}\n\t\t\tif candidateInsertLabel(labels, label) {\n\t\t\t\theap.Push(pq, label)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor cur := sourceLabel; cur != nil && cur.edge != nil; cur = cur.next {\n\t\tpath = append(path, cur.edge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLabel.logProb, nil\n}\n\nfunc (r *candidateRouter) findFreshRoute(amt lnwire.MilliSatoshi,\n\tbasePenalty map[candidateEdgeKey]float64) (*route.Route, float64,\n\terror) {\n\n\trt, logProb, err := r.findRoute(amt, basePenalty)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tfailures := r.routeFailures[candidateRouteSignature(rt)]\n\tif failures == 0 {\n\t\treturn rt, logProb, nil\n\t}\n\n\tpenalty := make(map[candidateEdgeKey]float64)\n\tfor key, value := range basePenalty {\n\t\tpenalty[key] = value\n\t}\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tvalue := 900_000.0 * float64(failures)\n\t\tif key.from != r.source {\n\t\t\tvalue *= 1.5\n\t\t}\n\t\tpenalty[key] += value\n\t}\n\n\talternate, altProb, altErr := r.findRoute(amt, penalty)\n\tif altErr == nil &&\n\t\tcandidateRouteSignature(alternate) != candidateRouteSignature(rt) {\n\n\t\treturn alternate, altProb, nil\n\t}\n\n\treturn rt, logProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tnode := r.source\n\tfor _, edge := range path {\n\t\tif edge.key.from != node {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, errors.New(\"path does not reach target\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, index int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif index == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[index-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\thop := rt.Hops[index]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteSignature(rt *route.Route) uint64 {\n\tvar h uint64 = 1469598103934665603\n\tif rt == nil {\n\t\treturn h\n\t}\n\tfor i, hop := range rt.Hops {\n\t\th ^= candidateMix64(\n\t\t\thop.ChannelID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) reliableLimit(edge *candidateEdge) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif edge == nil {\n\t\treturn 0, false\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\treturn balance, ok\n\t}\n\n\tlimit := edge.capacity * 9 / 10\n\tbelief := r.beliefs[edge.key]\n\n\tif belief.lowerOK > limit {\n\t\tlimit = belief.lowerOK\n\t}\n\tif belief.estimate > 0 && belief.failures > 0 &&\n\t\tbelief.estimate < limit {\n\n\t\tlimit = belief.estimate\n\t}\n\tif belief.upperFail > 0 {\n\t\tupper := belief.upperFail * 13 / 20\n\t\tif upper < limit {\n\t\t\tlimit = upper\n\t\t}\n\t}\n\tif failure := r.currentFails[edge.key]; failure.upper > 0 {\n\t\tupper := failure.upper * 11 / 20\n\t\tif failure.count >= 2 {\n\t\t\tupper = failure.upper * 7 / 20\n\t\t}\n\t\tif upper < limit {\n\t\t\tlimit = upper\n\t\t}\n\t}\n\n\treturn limit, true\n}\n\nfunc (r *candidateRouter) routeHeadroom(rt *route.Route) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tfinalAmt := candidateFinalAmount(rt)\n\tif finalAmt <= 0 {\n\t\treturn 0, false\n\t}\n\n\tratio := math.Inf(1)\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn 0, false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tlimit, ok := r.reliableLimit(edge)\n\t\tif !ok {\n\t\t\treturn 0, false\n\t\t}\n\n\t\tused := r.reserved[key]\n\t\tif limit <= used {\n\t\t\treturn 0, false\n\t\t}\n\t\tavailable := limit - used\n\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || amt <= 0 {\n\t\t\treturn 0, false\n\t\t}\n\n\t\tedgeRatio := float64(available) / float64(amt)\n\t\tif edge.maxHTLC > 0 {\n\t\t\tmaxRatio := float64(edge.maxHTLC) / float64(amt)\n\t\t\tif maxRatio < edgeRatio {\n\t\t\t\tedgeRatio = maxRatio\n\t\t\t}\n\t\t}\n\t\tif edgeRatio < ratio {\n\t\t\tratio = edgeRatio\n\t\t}\n\t}\n\n\tif math.IsInf(ratio, 1) || ratio <= 0 {\n\t\treturn 0, false\n\t}\n\n\theadroom := lnwire.MilliSatoshi(float64(finalAmt) * ratio)\n\tif headroom <= 0 {\n\t\treturn 0, false\n\t}\n\treturn headroom, true\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tavg := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tavg / 2,\n\t\tavg * 2 / 3,\n\t\tavg * 4 / 5,\n\t\tavg,\n\t\tavg * 6 / 5,\n\t\tavg * 3 / 2,\n\t\tavg * 2,\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailedShard / 4,\n\t\t\tr.lastFailedShard * 7 / 20,\n\t\t\tr.lastFailedShard / 2,\n\t\t\tr.lastFailedShard * 3 / 5,\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tprobe, _, err := r.findFreshRoute(avg, nil)\n\tif err == nil {\n\t\tif headroom, ok := r.routeHeadroom(probe); ok {\n\t\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\t\theadroom * 3 / 5,\n\t\t\t\theadroom * 3 / 4,\n\t\t\t\theadroom * 7 / 8,\n\t\t\t\theadroom,\n\t\t\t} {\n\t\t\t\tcandidateAddAmount(\n\t\t\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\tdi := math.Abs(math.Log(\n\t\t\tfloat64(sizes[i]) / float64(avg),\n\t\t))\n\t\tdj := math.Abs(math.Log(\n\t\t\tfloat64(sizes[j]) / float64(avg),\n\t\t))\n\t\tif di == dj {\n\t\t\treturn sizes[i] > sizes[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\tif len(sizes) > 11 {\n\t\tsizes = sizes[:11]\n\t}\n\n\treturn sizes\n}\n\nfunc candidateDiversityPenalty(rt *route.Route) map[candidateEdgeKey]float64 {\n\tpenalty := make(map[candidateEdgeKey]float64)\n\tif rt == nil {\n\t\treturn penalty\n\t}\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == rt.SourcePubKey {\n\t\t\tpenalty[key] += 300_000\n\t\t} else {\n\t\t\tpenalty[key] += 1_600_000\n\t\t}\n\t}\n\treturn penalty\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n\trank float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations, base map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\toldAmount := base[key]\n\t\tif amount <= oldAmount {\n\t\t\tcontinue\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tif oldAmount > 0 {\n\t\t\toldP := r.liquidityProbability(edge, oldAmount)\n\t\t\tif oldP <= 0 {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\t\t\tp /= oldP\n\t\t}\n\t\tif p > 0.999 {\n\t\t\tp = 0.999\n\t\t}\n\t\tif p < 0.0005 {\n\t\t\tp = 0.0005\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmt {\n\t\t\tfees += rt.TotalAmount - finalAmt\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 35_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.012 * float64(len(routes)-1)\n\t}\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.035 * float64(count-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) completionRank(state *candidatePlanState) float64 {\n\tif state.remaining == 0 {\n\t\treturn state.score\n\t}\n\tif state.slots <= 0 {\n\t\treturn math.Inf(-1)\n\t}\n\n\tsaved := r.reserved\n\tr.reserved = candidateCopyReservations(state.reservations)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tavg := (state.remaining + lnwire.MilliSatoshi(state.slots) - 1) /\n\t\tlnwire.MilliSatoshi(state.slots)\n\n\t_, logProb, err := r.findFreshRoute(avg, nil)\n\tif err != nil {\n\t\treturn math.Inf(-1)\n\t}\n\n\tfuture := 0.72 * float64(state.slots) * logProb\n\treturn state.score + future\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 10\n\n\tbase := candidateCopyReservations(r.reserved)\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(base),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\tstate.reservations,\n\t\t\t)\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\tfirst, _, err := r.findFreshRoute(size, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\toptions := []*route.Route{first}\n\t\t\t\talternate, _, altErr := r.findFreshRoute(\n\t\t\t\t\tsize, candidateDiversityPenalty(first),\n\t\t\t\t)\n\t\t\t\tif altErr == nil &&\n\t\t\t\t\tcandidateRouteSignature(alternate) !=\n\t\t\t\t\t\tcandidateRouteSignature(first) {\n\n\t\t\t\t\toptions = append(options, alternate)\n\t\t\t\t}\n\n\t\t\t\tfor _, rt := range options {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\trt,\n\t\t\t\t\t)\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, base,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(next.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tnext.rank = r.completionRank(&next)\n\t\t\t\t\tif math.IsInf(next.rank, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\tif expanded[i].rank == expanded[j].rank {\n\t\t\t\treturn expanded[i].score > expanded[j].score\n\t\t\t}\n\t\t\treturn expanded[i].rank > expanded[j].rank\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tvar best *candidatePlanState\n\tfor i := range states {\n\t\tstate := &states[i]\n\t\tif state.remaining != 0 || state.slots != 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif best == nil || state.score > best.score {\n\t\t\tbest = state\n\t\t}\n\t}\n\tif best == nil {\n\t\treturn nil, 0, false\n\t}\n\treturn best.routes, best.score, true\n}\n\nfunc candidatePartCounts(maxParts int) []int {\n\tseen := make(map[int]bool)\n\tvar counts []int\n\n\tadd := func(value int) {\n\t\tif value >= 2 && value <= maxParts && !seen[value] {\n\t\t\tseen[value] = true\n\t\t\tcounts = append(counts, value)\n\t\t}\n\t}\n\n\tif maxParts <= 7 {\n\t\tfor i := 2; i <= maxParts; i++ {\n\t\t\tadd(i)\n\t\t}\n\t} else {\n\t\tfor _, value := range []int{\n\t\t\t2, 3, 4, 5, 6, 8, 12, maxParts,\n\t\t} {\n\t\t\tadd(value)\n\t\t}\n\t}\n\n\tsort.Ints(counts)\n\treturn counts\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findFreshRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.94) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tbase := candidateCopyReservations(r.reserved)\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(base)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(bestPlan, reservations, base)\n\t}\n\n\tfor _, count := range candidatePartCounts(int(parts)) {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn false\n\t}\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn false\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || r.probability(edge, amt) <= 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt && r.routeFits(rt) {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil && edge.capacity > 0 {\n\t\tratio := float64(amt) / float64(edge.capacity)\n\t\tswitch {\n\t\tcase ratio >= 0.07:\n\t\t\testimate = edge.capacity * 9 / 10\n\t\tcase ratio >= 0.025:\n\t\t\testimate = edge.capacity * 4 / 5\n\t\tdefault:\n\t\t\testimate = amt * 3\n\t\t}\n\t\tif estimate > edge.capacity {\n\t\t\testimate = edge.capacity\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 4\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 20\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tsignature := candidateRouteSignature(rt)\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t\tif r.suspect[key] > 0 {\n\t\t\t\tr.suspect[key]--\n\t\t\t}\n\t\t}\n\n\t\tdelete(r.routeFailures, signature)\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.routeFailures[signature]++\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 3)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key] += 2\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 6\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 4\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 21,
|
|
"parent": 4,
|
|
"score": 0.3321,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeHeat map[candidateEdgeKey]uint32\n\trouteFailures map[uint64]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeHeat: make(map[candidateEdgeKey]uint32),\n\t\trouteFailures: make(map[uint64]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = 12 + 3*int(maxParts)\n\tif r.attemptLimit < 18 {\n\t\tr.attemptLimit = 18\n\t}\n\tif r.attemptLimit > 42 {\n\t\tr.attemptLimit = 42\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.043)\n\thighMode := 1 / (1 + math.Exp((x-0.915)/0.034))\n\tp := 0.46*lowMode + 0.54*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase ratio > 0.80:\n\t\t\tretryScale = 0.015\n\t\tcase ratio > 0.66:\n\t\t\tretryScale = 0.07\n\t\tcase ratio > 0.52:\n\t\t\tretryScale = 0.20\n\t\tcase ratio > 0.38:\n\t\t\tretryScale = 0.42\n\t\tcase ratio > 0.25:\n\t\t\tretryScale = 0.66\n\t\tdefault:\n\t\t\tretryScale = 0.86\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.65\n\t\t}\n\t\tif failure.count >= 4 {\n\t\t\tretryScale *= 0.50\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.004\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(total-belief.lowerOK) / width\n\t\tif position < 0 {\n\t\t\tposition = 0\n\t\t}\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\n\t\tevidence := 0.995 - 0.991*position\n\t\tp = 0.08*p + 0.92*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.20 {\n\t\t\tscale := 1 - 0.95*(ratio-0.20)/0.80\n\t\t\tif scale < 0.04 {\n\t\t\t\tscale = 0.04\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tif distance > 0 {\n\t\t\tboost := 0.72 * math.Exp(-distance/0.18)\n\t\t\tp += boost * (1 - p)\n\t\t}\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.065 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\testimateProbability := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.22\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.34\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateProbability\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(value any) {\n\titem := value.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 245_000.0\n\t\thopPenalty = 900.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tbestScore[r.spec.Target] = 0\n\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProbability float64\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProbability = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOverEdge := item.required\n\t\t\tprobability := r.probability(edge, amountOverEdge)\n\t\t\tif probability <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\tsending := amountOverEdge + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(probability)) +\n\t\t\t\thopPenalty\n\n\t\t\theat := r.edgeHeat[edge.key]\n\t\t\tedgeScore += float64(heat) * 85_000\n\t\t\tedgeScore += extraPenalty[edge.key]\n\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tedgeScore += 65_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\toldScore, found := bestScore[edge.key.from]\n\t\t\tif found && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(probability),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLogProbability, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] + outgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tchannelIndex int) (lnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\n\thop := rt.Hops[channelIndex]\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\th := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\th ^= candidateMix64(\n\t\t\tkey.chanID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th ^= candidateMix64(candidateVertexHash(key.to))\n\t}\n\n\treturn candidateMix64(h)\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\n\treturn result\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tequal := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&values, seen, equal, minimum, maximum)\n\tcandidateAddAmount(&values, seen, equal*2/3, minimum, maximum)\n\tcandidateAddAmount(&values, seen, equal*4/5, minimum, maximum)\n\tcandidateAddAmount(&values, seen, equal*6/5, minimum, maximum)\n\tcandidateAddAmount(&values, seen, equal*3/2, minimum, maximum)\n\tcandidateAddAmount(&values, seen, equal*2, minimum, maximum)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/4, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/3, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard*2/5,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/2, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard*3/5,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tfor _, edge := range r.edges {\n\t\tif edge.key.from != r.source &&\n\t\t\tedge.key.to != r.spec.Target {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tavailable := edge.capacity - r.reserved[edge.key]\n\t\tif available <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, available*2/5, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, available*3/5, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, available*4/5, minimum, maximum,\n\t\t)\n\n\t\tbelief, ok := r.beliefs[edge.key]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK > r.reserved[edge.key] {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\tbelief.lowerOK-r.reserved[edge.key],\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > r.reserved[edge.key] {\n\t\t\troom := belief.upperFail - r.reserved[edge.key]\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, room*2/5, minimum, maximum,\n\t\t\t)\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, room*3/5, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\tdi := math.Abs(float64(values[i] - equal))\n\t\tdj := math.Abs(float64(values[j] - equal))\n\t\treturn di < dj\n\t})\n\n\tconst maximumCandidates = 12\n\tif len(values) > maximumCandidates {\n\t\tvalues = values[:maximumCandidates]\n\t}\n\n\treturn values\n}\n\ntype candidateRouteOption struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeOptions(\n\tamt lnwire.MilliSatoshi) []candidateRouteOption {\n\n\tconst searches = 6\n\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\tvar options []candidateRouteOption\n\tvar rejected *candidateRouteOption\n\n\tfor search := 0; search < searches; search++ {\n\t\trt, logProbability, err := r.findRoute(amt, penalties)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\toption := candidateRouteOption{\n\t\t\t\troute: rt,\n\t\t\t\tlogProb: logProbability,\n\t\t\t}\n\n\t\t\tif r.routeFailures[hash] == 0 {\n\t\t\t\toptions = append(options, option)\n\t\t\t} else if rejected == nil {\n\t\t\t\tcopy := option\n\t\t\t\trejected = ©\n\t\t\t}\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpenalty := 520_000.0\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalty = 210_000\n\t\t\t}\n\t\t\tpenalties[key] += penalty\n\t\t}\n\n\t\tif len(options) >= 4 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif len(options) == 0 && rejected != nil {\n\t\toptions = append(options, *rejected)\n\t}\n\n\treturn options\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\tbase, reservations map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tif amount <= base[key] {\n\t\t\tcontinue\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tprobability := r.liquidityProbability(edge, amount)\n\t\tif probability <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(probability)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 4_000_000\n\tscore -= 0.025 * float64(len(routes)-1)\n\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.11 * float64(count-1)\n\t\t}\n\t\tscore -= 0.06 * float64(r.edgeHeat[key])\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 9\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tbase := candidateCopyReservations(r.reserved)\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(base),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tif size <= 0 || size > state.remaining {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\toptions := r.routeOptions(size)\n\n\t\t\t\tfor _, option := range options {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(\n\t\t\t\t\t\treservations, option.route,\n\t\t\t\t\t)\n\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\toption.route,\n\t\t\t\t\t)\n\n\t\t\t\t\tnextState := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnextState.score = r.planScore(\n\t\t\t\t\t\troutes, base, reservations,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(nextState.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\texpanded = append(expanded, nextState)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.SliceStable(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tparts int) (*route.Route, error) {\n\n\tif parts <= 1 {\n\t\trt, _, err := r.findRoute(total, nil)\n\t\treturn rt, err\n\t}\n\n\tvar best *route.Route\n\tbestUtility := math.Inf(-1)\n\n\tfor slots := parts; slots >= 2; slots-- {\n\t\tfor _, size := range r.shardSizes(total, slots) {\n\t\t\toptions := r.routeOptions(size)\n\t\t\tfor _, option := range options {\n\t\t\t\tprogress := float64(size) / float64(total)\n\t\t\t\tutility := option.logProb + 0.55*progress\n\n\t\t\t\tfee := option.route.TotalAmount -\n\t\t\t\t\tcandidateFinalAmount(option.route)\n\t\t\t\tutility -= float64(fee) / 4_000_000\n\n\t\t\t\tif utility > bestUtility {\n\t\t\t\t\tbestUtility = utility\n\t\t\t\t\tbest = option.route\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProbability, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil &&\n\t\tfullLogProbability >= math.Log(0.965) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\tif maxPlanParts > 14 {\n\t\tmaxPlanParts = 14\n\t}\n\tif lnwire.MilliSatoshi(maxPlanParts) > total {\n\t\tmaxPlanParts = int(total)\n\t}\n\tif maxPlanParts < 1 {\n\t\treturn nil, errors.New(\"payment amount too small\")\n\t}\n\n\tbase := candidateCopyReservations(r.reserved)\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(base)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(bestPlan, base, reservations)\n\t}\n\n\tfor count := 2; count <= maxPlanParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan != nil {\n\t\treturn bestPlan, nil\n\t}\n\n\tshard, err := r.fallbackShard(total, int(parts))\n\tif err != nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\treturn []*route.Route{shard}, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.policyBlocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || !edge.policyAllows(amount) {\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > edge.capacity {\n\t\t\treturn false\n\t\t}\n\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalances[key.chanID]\n\t\t\tif !ok || total > balance {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\tif failure, ok := r.currentFails[key]; ok &&\n\t\t\tfailure.upper > 0 && total >= failure.upper {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt && r.routeFits(rt) {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif heat := r.edgeHeat[key]; heat > 0 {\n\t\tr.edgeHeat[key] = heat - 1\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 17 / 20\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\tr.edgeHeat[key] += 2\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 4\n\tif edge := r.edges[key]; edge != nil {\n\t\tfloor := edge.capacity / 20\n\t\tif estimate > floor {\n\t\t\testimate = floor\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\n\tif belief.estimate == 0 || belief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteUncertain(rt *route.Route,\n\tweight uint32) {\n\n\thash := candidateRouteHash(rt)\n\tr.routeFailures[hash] += weight\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.edgeHeat[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\n\t\tdelete(r.routeFailures, candidateRouteHash(rt))\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteUncertain(rt, 2)\n\t\treturn nil\n\t}\n\n\tpassedEdges := failureIndex\n\tif passedEdges > len(rt.Hops) {\n\t\tpassedEdges = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < passedEdges; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteUncertain(rt, 2)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteUncertain(rt, 2)\n\t\treturn nil\n\t}\n\n\tamountOverEdge, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteUncertain(rt, 2)\n\t\treturn nil\n\t}\n\ttotalRequired := amountOverEdge + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.edgeHeat[key] += 4\n\n\tdefault:\n\t\tr.markRouteUncertain(rt, 2)\n\t\tr.edgeHeat[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 22,
|
|
"parent": 0,
|
|
"score": 0.7006,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts) + 4\n\tif r.attemptLimit < 8 {\n\t\tr.attemptLimit = 8\n\t}\n\tif r.attemptLimit > 18 {\n\t\tr.attemptLimit = 18\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpol := ch.InPolicy\n\t\t\t\tif pol == nil || pol.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: pol.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: pol.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: pol.TimeLockDelta,\n\t\t\t\t\tminHTLC: pol.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif pol.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = pol.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tfor key, belief := range mem.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.052)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.034))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.34:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.52:\n\t\t\treturn 0\n\t\tcase ratio > 0.78:\n\t\t\treturn 0\n\t\tcase ratio > 0.62:\n\t\t\tretryScale = 0.22\n\t\tcase ratio > 0.46:\n\t\t\tretryScale = 0.46\n\t\tcase ratio > 0.30:\n\t\t\tretryScale = 0.68\n\t\tdefault:\n\t\t\tretryScale = 0.86\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.72\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.004\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(total-belief.lowerOK) / width\n\t\tif pos < 0 {\n\t\t\tpos = 0\n\t\t}\n\t\tif pos > 1 {\n\t\t\tpos = 1\n\t\t}\n\t\tevidence := 0.994 - 0.988*pos\n\t\tp = 0.12*p + 0.88*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.20 {\n\t\t\tscale := 1 - 0.91*(ratio-0.20)/0.80\n\t\t\tif scale < 0.09 {\n\t\t\t\tscale = 0.09\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.62 * math.Exp(-distance/0.18)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.07 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.16\n\t\tcount := belief.successes + belief.failures\n\t\tif count >= 2 {\n\t\t\tweight = 0.24\n\t\t}\n\t\tif count >= 4 {\n\t\t\tweight = 0.31\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\ttotalP := r.liquidityProbability(edge, reserved+amt)\n\tif totalP <= 0 {\n\t\treturn 0\n\t}\n\tif reserved == 0 {\n\t\treturn totalP\n\t}\n\n\tbaseP := r.liquidityProbability(edge, reserved)\n\tif baseP <= 0 {\n\t\treturn 0\n\t}\n\n\tp := totalP / baseP\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tedge *candidateEdge\n\tnext *candidatePathLabel\n\thops int\n\tactive bool\n\tidx int\n}\n\ntype candidatePathQueue []*candidatePathLabel\n\nfunc (q candidatePathQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidatePathQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidatePathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].idx = i\n\tq[j].idx = j\n}\n\nfunc (q *candidatePathQueue) Push(x any) {\n\titem := x.(*candidatePathLabel)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidatePathQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc candidatePathContains(label *candidatePathLabel,\n\tnode route.Vertex) bool {\n\n\tfor cur := label; cur != nil; cur = cur.next {\n\t\tif cur.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(labels map[route.Vertex][]*candidatePathLabel,\n\tlabel *candidatePathLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif old.active && old.score <= label.score &&\n\t\t\told.required <= label.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := current[:0]\n\tfor _, old := range current {\n\t\tif old.active && label.score <= old.score &&\n\t\t\tlabel.required <= old.required {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tif old.active {\n\t\t\tkept = append(kept, old)\n\t\t}\n\t}\n\n\tkept = append(kept, label)\n\tif len(kept) > 7 {\n\t\tsort.Slice(kept, func(i, j int) bool {\n\t\t\tli := kept[i].score +\n\t\t\t\tfloat64(kept[i].required)/12_000_000\n\t\t\tlj := kept[j].score +\n\t\t\t\tfloat64(kept[j].required)/12_000_000\n\t\t\treturn li < lj\n\t\t})\n\t\tfor _, old := range kept[7:] {\n\t\t\told.active = false\n\t\t}\n\t\tkept = kept[:7]\n\t}\n\n\tlabels[label.node] = kept\n\treturn label.active\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64,\n\terror) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 360_000.0\n\t\thopPenalty = 18_000.0\n\t\tmaxHops = 32\n\t\tmaxExpand = 50000\n\t)\n\n\ttarget := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidatePathLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tpq := &candidatePathQueue{}\n\theap.Push(pq, target)\n\n\texpanded := 0\n\tvar sourceLabel *candidatePathLabel\n\n\tfor pq.Len() > 0 && expanded < maxExpand {\n\t\titem := heap.Pop(pq).(*candidatePathLabel)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLabel = item\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\texpanded++\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.required\n\t\t\tp := r.probability(edge, amtOver)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t}\n\t\t\tsending := amtOver + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 180_000\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\n\t\t\tlabel := &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tedge: edge,\n\t\t\t\tnext: item,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tactive: true,\n\t\t\t}\n\t\t\tif candidateInsertLabel(labels, label) {\n\t\t\t\theap.Push(pq, label)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor cur := sourceLabel; cur != nil && cur.edge != nil; cur = cur.next {\n\t\tpath = append(path, cur.edge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLabel.logProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tnode := r.source\n\tfor _, edge := range path {\n\t\tif edge.key.from != node {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, errors.New(\"path does not reach target\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations, base map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\toldAmount := base[key]\n\t\tif amount <= oldAmount {\n\t\t\tcontinue\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tif oldAmount > 0 {\n\t\t\toldP := r.liquidityProbability(edge, oldAmount)\n\t\t\tif oldP <= 0 {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\t\t\tp /= oldP\n\t\t}\n\t\tif p < 0.001 {\n\t\t\tp = 0.001\n\t\t}\n\t\tif p > 0.999 {\n\t\t\tp = 0.999\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmt {\n\t\t\tfees += rt.TotalAmount - finalAmt\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 10_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.09 * float64(len(routes)-1)\n\t}\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.035 * float64(count-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tavg := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tavg / 2,\n\t\tavg * 2 / 3,\n\t\tavg * 4 / 5,\n\t\tavg,\n\t\tavg * 6 / 5,\n\t\tavg * 3 / 2,\n\t\tavg * 2,\n\t\tmaximum,\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailedShard * 3 / 10,\n\t\t\tr.lastFailedShard * 2 / 5,\n\t\t\tr.lastFailedShard / 2,\n\t\t\tr.lastFailedShard * 3 / 5,\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor key, edge := range r.edges {\n\t\treserved := r.reserved[key]\n\t\tbelief := r.beliefs[key]\n\n\t\tif belief.lowerOK > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen,\n\t\t\t\t(belief.lowerOK-reserved)*19/20,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > reserved {\n\t\t\tavailable := belief.upperFail - reserved\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*2/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*11/20,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen,\n\t\t\t\t(belief.estimate-reserved)*4/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\n\t\tavailable := edge.capacity - reserved\n\t\tif edge.key.from == r.source {\n\t\t\tif balance, ok := r.localBalances[edge.key.chanID]; ok {\n\t\t\t\tavailable = balance - reserved\n\t\t\t}\n\t\t}\n\t\tif available > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&sizes, seen, available*4/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\tdi := math.Abs(math.Log(\n\t\t\tfloat64(sizes[i]) / float64(avg),\n\t\t))\n\t\tdj := math.Abs(math.Log(\n\t\t\tfloat64(sizes[j]) / float64(avg),\n\t\t))\n\t\tif di == dj {\n\t\t\treturn sizes[i] > sizes[j]\n\t\t}\n\t\treturn di < dj\n\t})\n\tif len(sizes) > 14 {\n\t\tsizes = sizes[:14]\n\t}\n\n\treturn sizes\n}\n\nfunc candidateRouteSignature(rt *route.Route) uint64 {\n\tvar h uint64 = 1469598103934665603\n\tfor i, hop := range rt.Hops {\n\t\th ^= candidateMix64(\n\t\t\thop.ChannelID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateDiversityPenalty(rt *route.Route) map[candidateEdgeKey]float64 {\n\tpenalty := make(map[candidateEdgeKey]float64)\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == rt.SourcePubKey {\n\t\t\tpenalty[key] += 130_000\n\t\t} else {\n\t\t\tpenalty[key] += 620_000\n\t\t}\n\t}\n\treturn penalty\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 10\n\n\tbase := candidateCopyReservations(r.reserved)\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(base),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\tstate.reservations,\n\t\t\t)\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tideal := float64(state.remaining) / float64(state.slots)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\tfirst, _, err := r.findRoute(size, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\toptions := []*route.Route{first}\n\t\t\t\tif depth < 3 {\n\t\t\t\t\talternate, _, altErr := r.findRoute(\n\t\t\t\t\t\tsize, candidateDiversityPenalty(first),\n\t\t\t\t\t)\n\t\t\t\t\tif altErr == nil &&\n\t\t\t\t\t\tcandidateRouteSignature(alternate) !=\n\t\t\t\t\t\t\tcandidateRouteSignature(first) {\n\n\t\t\t\t\t\toptions = append(options, alternate)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, rt := range options {\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend([]*route.Route(nil), state.routes...),\n\t\t\t\t\t\trt,\n\t\t\t\t\t)\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, base,\n\t\t\t\t\t)\n\n\t\t\t\t\tif slots > 0 {\n\t\t\t\t\t\tratio := float64(size) / ideal\n\t\t\t\t\t\tif ratio > 0 {\n\t\t\t\t\t\t\tnext.score -= 0.06 *\n\t\t\t\t\t\t\t\tmath.Abs(math.Log(ratio))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif math.IsInf(next.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\tif expanded[i].score == expanded[j].score {\n\t\t\t\treturn len(expanded[i].routes) <\n\t\t\t\t\tlen(expanded[j].routes)\n\t\t\t}\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\treturn nil, 0, false\n}\n\nfunc candidatePartCounts(maxParts int) []int {\n\tseen := make(map[int]bool)\n\tvar counts []int\n\tadd := func(v int) {\n\t\tif v >= 2 && v <= maxParts && !seen[v] {\n\t\t\tseen[v] = true\n\t\t\tcounts = append(counts, v)\n\t\t}\n\t}\n\n\tif maxParts <= 8 {\n\t\tfor i := 2; i <= maxParts; i++ {\n\t\t\tadd(i)\n\t\t}\n\t} else {\n\t\tfor _, value := range []int{\n\t\t\t2, 3, 4, 5, 6, 8, 10, 12, 16, maxParts,\n\t\t} {\n\t\t\tadd(value)\n\t\t}\n\t}\n\tsort.Ints(counts)\n\treturn counts\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tparts uint32) (*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\tideal := (total + divisor - 1) / divisor\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvar sizes []lnwire.MilliSatoshi\n\n\tadd := func(size lnwire.MilliSatoshi) {\n\t\tif size <= 0 || size > total || seen[size] {\n\t\t\treturn\n\t\t}\n\t\tseen[size] = true\n\t\tsizes = append(sizes, size)\n\t}\n\n\tadd(ideal * 3 / 2)\n\tadd(ideal)\n\tadd(ideal * 4 / 5)\n\tadd(ideal * 2 / 3)\n\tadd(ideal / 2)\n\tadd(total / 2)\n\tadd(total / 3)\n\tadd(total / 4)\n\tadd(total / 6)\n\tadd(total / 8)\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\treturn sizes[i] > sizes[j]\n\t})\n\n\tfor _, size := range sizes {\n\t\trt, _, err := r.findRoute(size, nil)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.97) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tbase := candidateCopyReservations(r.reserved)\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(base)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(bestPlan, reservations, base)\n\t}\n\n\tfor _, count := range candidatePartCounts(int(parts)) {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif bestPlan == nil || score > bestScore {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan != nil {\n\t\treturn bestPlan, nil\n\t}\n\n\tfallback, err := r.fallbackShard(total, parts)\n\tif err != nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{fallback}, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmt := candidateFinalAmount(rt)\n\t\tif finalAmt > 0 && finalAmt <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmem := candidateMemory.networks[r.networkKey]\n\tif mem == nil {\n\t\tmem = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = mem\n\t}\n\tmem.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil && edge.capacity > 0 {\n\t\tratio := float64(amt) / float64(edge.capacity)\n\t\tswitch {\n\t\tcase ratio >= 0.08:\n\t\t\testimate = edge.capacity * 7 / 8\n\t\tcase ratio >= 0.035:\n\t\t\testimate = edge.capacity * 3 / 4\n\t\tdefault:\n\t\t\testimate = amt * 2\n\t\t}\n\t\tif estimate > edge.capacity {\n\t\t\testimate = edge.capacity\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 18\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t\tif r.suspect[key] > 0 {\n\t\t\t\tr.suspect[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIdx = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIdx < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIdx\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamt, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amt+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIdx >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamtOver, ok := candidateRouteAmount(rt, failIdx)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amtOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 4\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 23,
|
|
"parent": 6,
|
|
"score": 0.5843,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateAttemptKey struct {\n\trouteHash uint64\n\tamount lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tedgeUses map[candidateEdgeKey]uint32\n\tsuspect map[candidateEdgeKey]uint32\n\ttried map[candidateAttemptKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tshardCeiling lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\ttried: make(map[candidateAttemptKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*4 + 24\n\tif r.attemptLimit < 36 {\n\t\tr.attemptLimit = 36\n\t}\n\tif r.attemptLimit > 64 {\n\t\tr.attemptLimit = 64\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.047)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.031))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.30:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.48:\n\t\t\treturn 0\n\t\tcase ratio > 0.72:\n\t\t\treturn 0\n\t\tcase ratio > 0.56:\n\t\t\tretryScale = 0.12\n\t\tcase ratio > 0.40:\n\t\t\tretryScale = 0.31\n\t\tcase ratio > 0.25:\n\t\t\tretryScale = 0.55\n\t\tdefault:\n\t\t\tretryScale = 0.78\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.60\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.004\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.994 - 0.988*position\n\t\tp = 0.10*p + 0.90*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.25 {\n\t\t\tscale := 1 - 0.95*(ratio-0.25)/0.75\n\t\t\tif scale < 0.045 {\n\t\t\t\tscale = 0.045\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.68 * math.Exp(-distance/0.20)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.075 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\tq := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.28\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.40\n\t\t}\n\t\tp = (1-weight)*p + weight*q\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(value any) {\n\titem := value.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 390_000.0\n\t\thopPenalty = 4_000.0\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tbestScore[r.spec.Target] = 0\n\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOverEdge := item.required\n\t\t\tp := r.probability(edge, amountOverEdge)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\tsending := amountOverEdge + fee\n\n\t\t\tscore := item.score + float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\n\t\t\tuses := r.edgeUses[edge.key]\n\t\t\tif uses > 8 {\n\t\t\t\tuses = 8\n\t\t\t}\n\t\t\tscore += float64(uses) * 15_000\n\n\t\t\tsuspect := r.suspect[edge.key]\n\t\t\tif suspect > 5 {\n\t\t\t\tsuspect = 5\n\t\t\t}\n\t\t\tscore += float64(suspect) * 115_000\n\n\t\t\tif extraPenalty != nil {\n\t\t\t\tscore += extraPenalty[edge.key]\n\t\t\t}\n\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tscore += 95_000\n\t\t\t}\n\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tforwardingEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\th := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i, hop := range rt.Hops {\n\t\tx := candidateMix64(hop.ChannelID + uint64(i+1))\n\t\tx ^= candidateMix64(candidateVertexHash(hop.PubKeyBytes))\n\t\th = candidateMix64(h ^ x)\n\t}\n\treturn h\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tcopyMap := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tcopyMap[key] = amount\n\t}\n\treturn copyMap\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tstart := len(r.held) - count\n\tfor _, rt := range r.held[start:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\ntype candidateRouteChoice struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) topRoutes(amt lnwire.MilliSatoshi,\n\tlimit int) []candidateRouteChoice {\n\n\tif limit < 1 {\n\t\tlimit = 1\n\t}\n\n\tpenalty := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\tvar choices []candidateRouteChoice\n\n\tfor iteration := 0; iteration < limit*5; iteration++ {\n\t\trt, logProb, err := r.findRoute(amt, penalty)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\tchoices = append(choices, candidateRouteChoice{\n\t\t\t\troute: rt,\n\t\t\t\tlogProb: logProb,\n\t\t\t})\n\t\t\tif len(choices) >= limit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalty[key] += 90_000\n\t\t\t} else {\n\t\t\t\tpenalty[key] += 310_000\n\t\t\t}\n\t\t}\n\t}\n\n\treturn choices\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) evidenceHints(minimum,\n\tmaximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tvar hints []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, edge := range r.incomingEdges[r.spec.Target] {\n\t\tcandidateAddAmount(\n\t\t\t&hints, seen, edge.capacity*7/8, minimum, maximum,\n\t\t)\n\n\t\tbelief := r.beliefs[edge.key]\n\t\tif belief.lowerOK > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&hints, seen, belief.lowerOK,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&hints, seen, belief.estimate*7/8,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&hints, seen, belief.upperFail/2,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor _, failure := range r.currentFails {\n\t\tif failure.upper == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tcandidateAddAmount(\n\t\t\t&hints, seen, failure.upper/2, minimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&hints, seen, failure.upper/3, minimum, maximum,\n\t\t)\n\t}\n\n\tsort.Slice(hints, func(i, j int) bool {\n\t\treturn hints[i] > hints[j]\n\t})\n\tif len(hints) > 8 {\n\t\thints = hints[:8]\n\t}\n\treturn hints\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif remaining <= 0 {\n\t\treturn nil\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(\n\t\t&sizes, seen, remaining, minimum, maximum,\n\t)\n\tif slots <= 1 {\n\t\treturn sizes\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tcandidateAddAmount(\n\t\t&sizes, seen, base, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*3/4, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, base*5/4, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, remaining/2, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, remaining/3, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, remaining*2/3, minimum, maximum,\n\t)\n\n\tif r.shardCeiling > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.shardCeiling, minimum, maximum,\n\t\t)\n\t}\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/3,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tfor _, hint := range r.evidenceHints(minimum, maximum) {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, hint, minimum, maximum,\n\t\t)\n\t}\n\n\tif len(sizes) > 14 {\n\t\tsizes = sizes[:14]\n\t}\n\treturn sizes\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tvar sent lnwire.MilliSatoshi\n\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tsent += finalAmount\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 4_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.055 * float64(len(routes)-1)\n\t}\n\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.075 * float64(count-1)\n\t\t}\n\t}\n\n\tif !complete && total > 0 {\n\t\tprogress := float64(sent) / float64(total)\n\t\tif progress > 1 {\n\t\t\tprogress = 1\n\t\t}\n\t\tscore += 0.72 * progress\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) wasTried(rt *route.Route) bool {\n\tkey := candidateAttemptKey{\n\t\trouteHash: candidateRouteHash(rt),\n\t\tamount: candidateFinalAmount(rt),\n\t}\n\treturn r.tried[key] > 0\n}\n\nfunc (r *candidateRouter) buildJointPlan(\n\ttotal lnwire.MilliSatoshi, parts int) ([]*route.Route, float64, bool) {\n\n\tconst (\n\t\tbeamWidth = 9\n\t\troutesPerAmount = 3\n\t)\n\n\tif total <= 0 || parts <= 0 {\n\t\treturn nil, 0, false\n\t}\n\tif parts > 10 {\n\t\tparts = 10\n\t}\n\n\tsavedReservations := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = savedReservations\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tvar best []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor depth := 0; depth < parts && len(states) > 0; depth++ {\n\t\tvar incomplete []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tfor _, size := range sizes {\n\t\t\t\tif size <= 0 || size > state.remaining {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tchoices := r.topRoutes(size, routesPerAmount)\n\n\t\t\t\tfor _, choice := range choices {\n\t\t\t\t\tif r.wasTried(choice.route) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(\n\t\t\t\t\t\treservations, choice.route,\n\t\t\t\t\t)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tchoice.route,\n\t\t\t\t\t)\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tcomplete := remaining == 0\n\n\t\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, total,\n\t\t\t\t\t\tcomplete,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(next.score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif complete {\n\t\t\t\t\t\tif best == nil ||\n\t\t\t\t\t\t\tnext.score > bestScore {\n\n\t\t\t\t\t\t\tbest = routes\n\t\t\t\t\t\t\tbestScore = next.score\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif slots > 0 {\n\t\t\t\t\t\tincomplete = append(\n\t\t\t\t\t\t\tincomplete, next,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(incomplete, func(i, j int) bool {\n\t\t\treturn incomplete[i].score >\n\t\t\t\tincomplete[j].score\n\t\t})\n\t\tif len(incomplete) > beamWidth {\n\t\t\tincomplete = incomplete[:beamWidth]\n\t\t}\n\t\tstates = incomplete\n\t}\n\n\treturn best, bestScore, best != nil\n}\n\nfunc (r *candidateRouter) partialSizes(\n\ttotal lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif total <= 1 {\n\t\treturn nil\n\t}\n\n\tminimum := total / 32\n\tif minimum < 1 {\n\t\tminimum = 1\n\t}\n\tmaximum := total - 1\n\n\tvar sizes []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tif r.shardCeiling > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.shardCeiling, minimum, maximum,\n\t\t)\n\t}\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard*5/8,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/2,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/3,\n\t\t\tminimum, maximum,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, r.lastFailedShard/4,\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tcandidateAddAmount(\n\t\t&sizes, seen, total*3/4, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, total/2, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, total/3, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, total/4, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, total/8, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, total/16, minimum, maximum,\n\t)\n\tcandidateAddAmount(\n\t\t&sizes, seen, total/32, minimum, maximum,\n\t)\n\n\tfor _, hint := range r.evidenceHints(minimum, maximum) {\n\t\tcandidateAddAmount(\n\t\t\t&sizes, seen, hint, minimum, maximum,\n\t\t)\n\t}\n\n\tsort.Slice(sizes, func(i, j int) bool {\n\t\treturn sizes[i] > sizes[j]\n\t})\n\treturn sizes\n}\n\nfunc (r *candidateRouter) bestPartial(\n\ttotal lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tvar best *route.Route\n\tbestUtility := math.Inf(-1)\n\n\tfor _, size := range r.partialSizes(total) {\n\t\tfraction := float64(size) / float64(total)\n\t\tfor _, choice := range r.topRoutes(size, 3) {\n\t\t\tif r.wasTried(choice.route) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tutility := choice.logProb +\n\t\t\t\t1.15*math.Log(fraction)\n\n\t\t\tfinalAmount := candidateFinalAmount(choice.route)\n\t\t\tif choice.route.TotalAmount > finalAmount {\n\t\t\t\tfee := choice.route.TotalAmount - finalAmount\n\t\t\t\tutility -= float64(fee) / 4_000_000\n\t\t\t}\n\n\t\t\tif best == nil || utility > bestUtility {\n\t\t\t\tbest = choice.route\n\t\t\t\tbestUtility = utility\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif total <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfullChoices := r.topRoutes(total, 1)\n\tif len(fullChoices) > 0 {\n\t\tfull := fullChoices[0]\n\t\tif !r.wasTried(full.route) &&\n\t\t\tfull.logProb >= math.Log(0.78) &&\n\t\t\tlen(r.currentFails) == 0 {\n\n\t\t\treturn []*route.Route{full.route}, nil\n\t\t}\n\t}\n\n\tif parts > 1 {\n\t\tplan, _, ok := r.buildJointPlan(total, int(parts))\n\t\tif ok {\n\t\t\treturn plan, nil\n\t\t}\n\t}\n\n\tif len(fullChoices) > 0 && !r.wasTried(fullChoices[0].route) {\n\t\treturn []*route.Route{fullChoices[0].route}, nil\n\t}\n\n\tpartial, err := r.bestPartial(total)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{partial}, nil\n}\n\nfunc (r *candidateRouter) recordRouteUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n\n\tattemptKey := candidateAttemptKey{\n\t\trouteHash: candidateRouteHash(rt),\n\t\tamount: candidateFinalAmount(rt),\n\t}\n\tr.tried[attemptKey]++\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt &&\n\t\t\t!r.wasTried(rt) {\n\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordRouteUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.recordRouteUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 7 / 8\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tdepletedEstimate := edge.capacity / 20\n\t\tif estimate > depletedEstimate {\n\t\t\testimate = depletedEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) noteFailedShard(rt *route.Route) {\n\tamount := candidateFinalAmount(rt)\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tr.lastFailedShard = amount\n\tceiling := amount * 5 / 8\n\tif ceiling <= 0 {\n\t\tceiling = 1\n\t}\n\tif r.shardCeiling == 0 || ceiling < r.shardCeiling {\n\t\tr.shardCeiling = ceiling\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.noteFailedShard(rt)\n\n\tfailIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIndex < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\treturn nil\n\t}\n\n\tpassed := failIndex\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamountOverEdge, ok := candidateRouteAmount(rt, failIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amountOverEdge + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 3\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 24,
|
|
"parent": 3,
|
|
"score": 0.0229,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\tbaseFee lnwire.MilliSatoshi\n\tfeeRate lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFee + amt*e.feeRate/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tfailures map[candidateEdgeKey]candidateFailure\n\tblocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\tuses map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailed lnwire.MilliSatoshi\n\tattempts int\n\tmaxAttempts int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(candidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(uint64(e.baseFee) + uint64(e.feeRate)<<17)\n\tx ^= candidateMix64(uint64(e.timeLockDelta) + uint64(e.minHTLC)<<16)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincoming: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tfailures: make(map[candidateEdgeKey]candidateFailure),\n\t\tblocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\tuses: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.maxAttempts = 14 + 3*int(maxParts)\n\tif r.maxAttempts < 18 {\n\t\tr.maxAttempts = 18\n\t}\n\tif r.maxAttempts > 32 {\n\t\tr.maxAttempts = 32\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(ch.Capacity),\n\t\t\t\t\tbaseFee: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRate: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(r.incoming[node], edge)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.05)\n\thighMode := 1 / (1 + math.Exp((x-0.93)/0.033))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.989 {\n\t\treturn 0.989\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) localAvailable(chanID uint64) lnwire.MilliSatoshi {\n\tbalance, ok := r.localBalances[chanID]\n\tif !ok {\n\t\treturn 0\n\t}\n\tspent := r.localSpent[chanID]\n\tif spent >= balance {\n\t\treturn 0\n\t}\n\treturn balance - spent\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity || r.blocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif total > r.localAvailable(edge.key.chanID) {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.failures[edge.key]; ok && failure.upper > 0 {\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase ratio > 0.80:\n\t\t\tretryScale = 0.015\n\t\tcase ratio > 0.67:\n\t\t\tretryScale = 0.07\n\t\tcase ratio > 0.53:\n\t\t\tretryScale = 0.20\n\t\tcase ratio > 0.40:\n\t\t\tretryScale = 0.42\n\t\tcase ratio > 0.27:\n\t\t\tretryScale = 0.66\n\t\tdefault:\n\t\t\tretryScale = 0.87\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.72\n\t\t}\n\t\tif failure.count >= 4 {\n\t\t\tretryScale *= 0.60\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif ok {\n\t\tswitch {\n\t\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\t\tp = 0.996\n\n\t\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\t\tp = 0.005\n\n\t\tcase belief.lowerOK > 0 &&\n\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\t\tposition := float64(total-belief.lowerOK) / width\n\t\t\tif position < 0 {\n\t\t\t\tposition = 0\n\t\t\t}\n\t\t\tif position > 1 {\n\t\t\t\tposition = 1\n\t\t\t}\n\t\t\tevidence := 0.994 - 0.988*position\n\t\t\tp = 0.08*p + 0.92*evidence\n\n\t\tcase belief.upperFail > 0:\n\t\t\tratio := float64(total) / float64(belief.upperFail)\n\t\t\tif ratio > 0.18 {\n\t\t\t\tscale := 1 - 0.94*(ratio-0.18)/0.82\n\t\t\t\tif scale < 0.05 {\n\t\t\t\t\tscale = 0.05\n\t\t\t\t}\n\t\t\t\tp *= scale\n\t\t\t}\n\n\t\tcase belief.lowerOK > 0:\n\t\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\t\tfloat64(edge.capacity)\n\t\t\tif distance > 0 {\n\t\t\t\tboost := 0.68 * math.Exp(-distance/0.18)\n\t\t\t\tp += boost * (1 - p)\n\t\t\t}\n\t\t}\n\n\t\tif belief.estimate > 0 {\n\t\t\tscale := 0.065 * float64(edge.capacity)\n\t\t\tif scale < 1 {\n\t\t\t\tscale = 1\n\t\t\t}\n\t\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t\t))\n\t\t\tweight := 0.22\n\t\t\tif belief.successes+belief.failures >= 3 {\n\t\t\t\tweight = 0.34\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*estimateP\n\t\t}\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(value any) {\n\titem := value.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 310_000.0\n\t\thopPenalty = 7_500.0\n\t)\n\n\tbest := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := best[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incoming[item.node] {\n\t\t\toverEdge := item.required\n\t\t\tp := r.edgeProbability(edge, overEdge)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(overEdge)\n\t\t\t}\n\t\t\tsending := overEdge + fee\n\n\t\t\tscore := item.score + float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty +\n\t\t\t\tfloat64(r.uses[edge.key])*18_000 +\n\t\t\t\tfloat64(r.suspect[edge.key])*95_000\n\n\t\t\tif extra != nil {\n\t\t\t\tscore += extra[edge.key]\n\t\t\t}\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tscore += 50_000\n\t\t\t}\n\n\t\t\told, found := best[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := best[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken route at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] + outgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tindex int) (lnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif index == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[index-1].AmtToForward, true\n}\n\nfunc candidateRouteKey(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\th := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\th ^= candidateMix64(\n\t\t\tkey.chanID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th ^= candidateMix64(candidateVertexHash(key.to))\n\t}\n\treturn candidateMix64(h)\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) settleHeldLocally() {\n\tfor _, rt := range r.held {\n\t\tif rt == nil || len(rt.Hops) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, 0)\n\t\tif ok {\n\t\t\tr.localSpent[rt.Hops[0].ChannelID] += amount\n\t\t}\n\t}\n\tr.held = nil\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.settleHeldLocally()\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvar values []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, pair := range [][2]int64{\n\t\t{1, 2}, {2, 3}, {4, 5}, {1, 1},\n\t\t{6, 5}, {3, 2}, {2, 1},\n\t} {\n\t\tvalue := base * lnwire.MilliSatoshi(pair[0]) /\n\t\t\tlnwire.MilliSatoshi(pair[1])\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif slots == 2 {\n\t\tfor numerator := int64(1); numerator <= 4; numerator++ {\n\t\t\tvalue := remaining * lnwire.MilliSatoshi(numerator) / 5\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tif r.lastFailed > 0 {\n\t\tfor _, pair := range [][2]int64{\n\t\t\t{1, 5}, {1, 4}, {1, 3}, {2, 5},\n\t\t\t{1, 2}, {3, 5}, {2, 3},\n\t\t} {\n\t\t\tvalue := r.lastFailed *\n\t\t\t\tlnwire.MilliSatoshi(pair[0]) /\n\t\t\t\tlnwire.MilliSatoshi(pair[1])\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor key, failure := range r.failures {\n\t\tif failure.upper == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, pair := range [][2]int64{{1, 4}, {2, 5}, {1, 2}} {\n\t\t\tvalue := failure.upper *\n\t\t\t\tlnwire.MilliSatoshi(pair[0]) /\n\t\t\t\tlnwire.MilliSatoshi(pair[1])\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor key, belief := range r.beliefs {\n\t\tedge := r.edges[key]\n\t\tif edge == nil ||\n\t\t\t(edge.key.from != r.source &&\n\t\t\t\tedge.key.to != r.spec.Target) {\n\n\t\t\tcontinue\n\t\t}\n\t\tif belief.lowerOK > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, belief.lowerOK,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > 0 {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, belief.upperFail*2/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\tdi := math.Abs(float64(values[i] - base))\n\t\tdj := math.Abs(float64(values[j] - base))\n\t\treturn di < dj\n\t})\n\n\tconst maxCandidates = 18\n\tif len(values) > maxCandidates {\n\t\tvalues = values[:maxCandidates]\n\t}\n\treturn values\n}\n\ntype candidateRouteOption struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeOptions(\n\tamt lnwire.MilliSatoshi) []candidateRouteOption {\n\n\tconst optionCount = 4\n\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\tvar options []candidateRouteOption\n\n\tfor option := 0; option < optionCount; option++ {\n\t\trt, logProb, err := r.findRoute(amt, penalties)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\toptions = append(options, candidateRouteOption{\n\t\t\t\troute: rt,\n\t\t\t\tlogProb: logProb,\n\t\t\t})\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpenalty := 850_000.0\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalty = 260_000\n\t\t\t}\n\t\t\tpenalties[key] += penalty\n\t\t}\n\t}\n\n\treturn options\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 4_500_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.06 * float64(len(routes)-1)\n\t}\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.14 * float64(count-1)\n\t\t}\n\t}\n\n\tif !complete && total > 0 {\n\t\tremaining := total\n\t\tfor _, rt := range routes {\n\t\t\tremaining -= candidateFinalAmount(rt)\n\t\t}\n\t\tprogress := 1 - float64(remaining)/float64(total)\n\t\tscore += 0.32 * progress\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 9\n\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tfor _, size := range r.shardSizes(\n\t\t\t\tstate.remaining, state.slots,\n\t\t\t) {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\tfor _, option := range r.routeOptions(size) {\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif remaining < 0 ||\n\t\t\t\t\t\t((remaining == 0) != (slots == 0)) {\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(\n\t\t\t\t\t\treservations, option.route,\n\t\t\t\t\t)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t\t),\n\t\t\t\t\t\toption.route,\n\t\t\t\t\t)\n\n\t\t\t\t\tnext := candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t}\n\t\t\t\t\tnext.score = r.planScore(\n\t\t\t\t\t\troutes, reservations, total,\n\t\t\t\t\t\tremaining == 0,\n\t\t\t\t\t)\n\t\t\t\t\tif !math.IsInf(next.score, -1) {\n\t\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.SliceStable(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.94) &&\n\t\tr.lastFailed == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxPlanParts := int(parts)\n\tif maxPlanParts > 8 {\n\t\tmaxPlanParts = 8\n\t}\n\tif lnwire.MilliSatoshi(maxPlanParts) > total {\n\t\tmaxPlanParts = int(total)\n\t}\n\tif maxPlanParts < 1 {\n\t\treturn nil, errors.New(\"payment amount too small\")\n\t}\n\n\tvar bestPlan []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbestPlan = []*route.Route{full}\n\t\tbestScore = r.planScore(\n\t\t\tbestPlan, reservations, total, true,\n\t\t)\n\t}\n\n\tfor count := 2; count <= maxPlanParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif ok && (bestPlan == nil || score > bestScore) {\n\t\t\tbestPlan = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\treturn bestPlan, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.blocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || !edge.policyAllows(amount) {\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > edge.capacity {\n\t\t\treturn false\n\t\t}\n\t\tif key.from == r.source &&\n\t\t\ttotal > r.localAvailable(key.chanID) {\n\n\t\t\treturn false\n\t\t}\n\t\tif failure, ok := r.failures[key]; ok &&\n\t\t\tfailure.upper > 0 && total >= failure.upper {\n\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) recordUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif ok {\n\t\t\tr.uses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.maxAttempts {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt && r.routeFits(rt) {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tr.planned = nil\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t}\n\n\tr.recordUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.failures[key]; ok &&\n\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\tdelete(r.failures, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\thigh := edge.capacity * 17 / 20\n\t\tif high > estimate {\n\t\t\testimate = high\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.failures[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.failures[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 4\n\tif edge := r.edges[key]; edge != nil {\n\t\tfloor := edge.capacity / 20\n\t\tif estimate > floor {\n\t\t\testimate = floor\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markSuspect(rt *route.Route, weight uint32) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t\t}\n\t\t}\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailed = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailed = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markSuspect(rt, 2)\n\t\treturn nil\n\t}\n\n\tpassed := failureIndex\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markSuspect(rt, 2)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markSuspect(rt, 2)\n\t\treturn nil\n\t}\n\tamount, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markSuspect(rt, 2)\n\t\treturn nil\n\t}\n\ttotalRequired := amount + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\t\tr.blocked[key] = true\n\t\tr.suspect[key] += 4\n\n\tdefault:\n\t\tr.suspect[key] += 3\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 25,
|
|
"parent": 6,
|
|
"score": 0.5705,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tlocalSpent map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tbalances := make(map[uint64]lnwire.MilliSatoshi, len(localBalances))\n\tfor chanID, balance := range localBalances {\n\t\tbalances[chanID] = balance\n\t}\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: balances,\n\t\tlocalSpent: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts)*3 + 4\n\tif r.attemptLimit < 8 {\n\t\tr.attemptLimit = 8\n\t}\n\tif r.attemptLimit > 28 {\n\t\tr.attemptLimit = 28\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.05)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.032))\n\tp := 0.48*lowMode + 0.52*highMode\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.988 {\n\t\treturn 0.988\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) localAvailable(chanID uint64) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tbalance, ok := r.localBalances[chanID]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\n\tspent := r.localSpent[chanID]\n\tif spent >= balance {\n\t\treturn 0, true\n\t}\n\treturn balance - spent, true\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity {\n\t\treturn 0\n\t}\n\tif r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tavailable, ok := r.localAvailable(edge.key.chanID)\n\t\tif !ok || total > available {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.24:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.40:\n\t\t\treturn 0\n\t\tcase ratio > 0.72:\n\t\t\treturn 0\n\t\tcase ratio > 0.55:\n\t\t\tretryScale = 0.13\n\t\tcase ratio > 0.38:\n\t\t\tretryScale = 0.31\n\t\tcase ratio > 0.23:\n\t\t\tretryScale = 0.55\n\t\tdefault:\n\t\t\tretryScale = 0.78\n\t\t}\n\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.58\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.005\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK &&\n\t\ttotal > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(total-belief.lowerOK) / width\n\t\tevidence := 0.993 - 0.987*position\n\t\tp = 0.10*p + 0.90*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.28 {\n\t\t\tscale := 1 - 0.95*(ratio-0.28)/0.72\n\t\t\tif scale < 0.05 {\n\t\t\t\tscale = 0.05\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0 && total > belief.lowerOK:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.68 * math.Exp(-distance/0.20)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.075 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\testimateProb := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.26\n\t\tevidenceCount := belief.successes + belief.failures\n\t\tif evidenceCount >= 3 {\n\t\t\tweight = 0.38\n\t\t}\n\t\tif evidenceCount >= 7 {\n\t\t\tweight = 0.46\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateProb\n\t}\n\n\tp *= retryScale\n\tif p < 0.0015 {\n\t\treturn 0.0015\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := reservations[edge.key]\n\tif reserved >= edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, reserved+amt)\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\thops int\n\tindex int\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateDijkstraQueue) Push(value any) {\n\titem := value.(*candidateDijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) shortestRoute(amt lnwire.MilliSatoshi,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]uint32) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 700_000.0\n\t\thopPenalty = 1_500.0\n\t\tdiversityWeight = 240_000.0\n\t\treservedPenalty = 130_000.0\n\t\tsuspectPenalty = 420_000.0\n\t\tmaxRouteHops = 20\n\t)\n\n\tbestScore := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tbestScore[r.spec.Target] = 0\n\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tvar sourceLogProb float64\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\t\tknown, ok := bestScore[item.node]\n\t\tif !ok || item.score > known+0.0001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceLogProb = item.logProb\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxRouteHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamountOverEdge := item.required\n\t\t\tp := r.probability(edge, amountOverEdge, reservations)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\tsending := amountOverEdge + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(diversity[edge.key]) *\n\t\t\t\tdiversityWeight\n\t\t\tedgeScore += float64(r.suspect[edge.key]) *\n\t\t\t\tsuspectPenalty\n\n\t\t\tif reservations[edge.key] > 0 {\n\t\t\t\tedgeScore += reservedPenalty\n\t\t\t}\n\n\t\t\tscore := item.score + edgeScore\n\t\t\told, found := bestScore[edge.key.from]\n\t\t\tif found && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\trequired: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\thops: item.hops + 1,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLogProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tvisited := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif visited[node] {\n\t\t\treturn nil, errors.New(\"route contains cycle\")\n\t\t}\n\t\tvisited[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\treturn r.buildFixedRoute(amt, path)\n}\n\nfunc (r *candidateRouter) buildFixedRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif amt <= 0 || len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamountOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamountOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamountOver[i] = amountOver[i+1] +\n\t\t\tforwardingEdge.fee(amountOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tamountToForward = amountOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amountOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, channelIndex int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn 0, false\n\t}\n\tif channelIndex == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[channelIndex-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tchannelIndex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || channelIndex < 0 ||\n\t\tchannelIndex >= len(rt.Hops) {\n\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif channelIndex > 0 {\n\t\tfrom = rt.Hops[channelIndex-1].PubKeyBytes\n\t}\n\thop := rt.Hops[channelIndex]\n\n\treturn candidateEdgeKey{\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\th := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i, hop := range rt.Hops {\n\t\tx := candidateMix64(hop.ChannelID + uint64(i+1))\n\t\tx ^= candidateMix64(candidateVertexHash(hop.PubKeyBytes))\n\t\th = candidateMix64(h ^ x)\n\t}\n\treturn h\n}\n\nfunc candidateCopyReservations(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amount := range src {\n\t\tdst[key] = amount\n\t}\n\treturn dst\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) pathFromRoute(rt *route.Route) (\n\t[]*candidateEdge, bool) {\n\n\tif rt == nil {\n\t\treturn nil, false\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn nil, false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn nil, false\n\t\t}\n\t\tpath = append(path, edge)\n\t}\n\treturn path, true\n}\n\ntype candidateRouteChoice struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeAlternatives(\n\tamt lnwire.MilliSatoshi,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tlimit int) []candidateRouteChoice {\n\n\tif limit < 1 {\n\t\tlimit = 1\n\t}\n\n\tdiversity := make(map[candidateEdgeKey]uint32)\n\tseen := make(map[uint64]bool)\n\tchoices := make([]candidateRouteChoice, 0, limit)\n\n\tmaxTries := limit * 4\n\tfor tries := 0; tries < maxTries && len(choices) < limit; tries++ {\n\t\trt, logProb, err := r.shortestRoute(\n\t\t\tamt, reservations, diversity,\n\t\t)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\tchoices = append(choices, candidateRouteChoice{\n\t\t\t\troute: rt,\n\t\t\t\tlogProb: logProb,\n\t\t\t})\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tdiversity[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\treturn choices\n}\n\nfunc (r *candidateRouter) routeQuality(rt *route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi) bool {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn false\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tp := r.probability(edge, amount, reservations)\n\t\tif p < 0.10 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) pathOffer(rt *route.Route,\n\tmaximum lnwire.MilliSatoshi,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi) (\n\tlnwire.MilliSatoshi, *route.Route) {\n\n\tpath, ok := r.pathFromRoute(rt)\n\tif !ok || maximum <= 0 {\n\t\treturn 0, nil\n\t}\n\n\tanchor := candidateFinalAmount(rt)\n\tif anchor > maximum {\n\t\tanchor = maximum\n\t}\n\n\tvar anchorRoute *route.Route\n\tfor anchor > 0 {\n\t\tcandidate, err := r.buildFixedRoute(anchor, path)\n\t\tif err == nil && r.routeQuality(candidate, reservations) {\n\t\t\tanchorRoute = candidate\n\t\t\tbreak\n\t\t}\n\t\tanchor /= 2\n\t}\n\tif anchorRoute == nil || anchor <= 0 {\n\t\treturn 0, nil\n\t}\n\n\tlow := anchor\n\thigh := maximum\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tcandidate, err := r.buildFixedRoute(mid, path)\n\t\tif err == nil && r.routeQuality(candidate, reservations) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\toffer := low * 9 / 10\n\tif offer < anchor {\n\t\toffer = anchor\n\t}\n\tofferedRoute, err := r.buildFixedRoute(offer, path)\n\tif err != nil {\n\t\treturn 0, nil\n\t}\n\treturn offer, offeredRoute\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif value <= 0 || maximum <= 0 {\n\t\treturn\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tideal := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvalues := make([]lnwire.MilliSatoshi, 0, 9)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tcandidateAddAmount(&values, seen, remaining, remaining)\n\tcandidateAddAmount(&values, seen, ideal, remaining)\n\tcandidateAddAmount(&values, seen, ideal*3/4, remaining)\n\tcandidateAddAmount(&values, seen, ideal*5/4, remaining)\n\tcandidateAddAmount(&values, seen, ideal*3/2, remaining)\n\tcandidateAddAmount(&values, seen, ideal*2, remaining)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/2, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/3, remaining,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/4, remaining,\n\t\t)\n\t}\n\n\treturn values\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t\tscore -= 0.12 * float64(r.suspect[key])\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tvar sent lnwire.MilliSatoshi\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tsent += finalAmount\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 10_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.035 * float64(len(routes)-1)\n\t}\n\n\tif !complete && total > 0 {\n\t\tprogress := float64(sent) / float64(total)\n\t\tscore += 0.72 * progress\n\t}\n\n\treturn score\n}\n\nfunc (r *candidateRouter) extendPlan(state candidatePlanState,\n\trt *route.Route, total lnwire.MilliSatoshi) (\n\tcandidatePlanState, bool) {\n\n\tamount := candidateFinalAmount(rt)\n\tif amount <= 0 || amount > state.remaining || state.slots <= 0 {\n\t\treturn candidatePlanState{}, false\n\t}\n\n\treservations := candidateCopyReservations(state.reservations)\n\tcandidateReserveInto(reservations, rt)\n\n\troutes := append(\n\t\tappend([]*route.Route(nil), state.routes...), rt,\n\t)\n\tnext := candidatePlanState{\n\t\tremaining: state.remaining - amount,\n\t\tslots: state.slots - 1,\n\t\troutes: routes,\n\t\treservations: reservations,\n\t}\n\n\tif next.remaining > 0 && next.slots == 0 {\n\t\treturn candidatePlanState{}, false\n\t}\n\n\tnext.score = r.planScore(\n\t\troutes, reservations, total, next.remaining == 0,\n\t)\n\tif math.IsInf(next.score, -1) {\n\t\treturn candidatePlanState{}, false\n\t}\n\n\treturn next, true\n}\n\nfunc (r *candidateRouter) completePlan(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, bool) {\n\n\tconst (\n\t\tbeamWidth = 9\n\t\talternatives = 4\n\t\tmaxPlanningPart = 10\n\t)\n\n\tif parts > maxPlanningPart {\n\t\tparts = maxPlanningPart\n\t}\n\tif parts < 1 {\n\t\treturn nil, false\n\t}\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(r.reserved),\n\t}}\n\n\tvar best []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor depth := 0; depth < parts && len(states) > 0; depth++ {\n\t\tvar expanded []candidatePlanState\n\n\t\tfor _, state := range states {\n\t\t\tsizes := r.shardSizes(state.remaining, state.slots)\n\t\t\tadded := make(map[uint64]bool)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tchoices := r.routeAlternatives(\n\t\t\t\t\tsize, state.reservations, alternatives,\n\t\t\t\t)\n\n\t\t\t\tfor _, choice := range choices {\n\t\t\t\t\thash := candidateRouteHash(choice.route) ^\n\t\t\t\t\t\tcandidateMix64(uint64(size))\n\t\t\t\t\tif !added[hash] {\n\t\t\t\t\t\tadded[hash] = true\n\t\t\t\t\t\tnext, ok := r.extendPlan(\n\t\t\t\t\t\t\tstate, choice.route, total,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif ok {\n\t\t\t\t\t\t\tif next.remaining == 0 {\n\t\t\t\t\t\t\t\tif next.score > bestScore {\n\t\t\t\t\t\t\t\t\tbestScore = next.score\n\t\t\t\t\t\t\t\t\tbest = next.routes\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\texpanded = append(\n\t\t\t\t\t\t\t\t\texpanded, next,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\toffer, offeredRoute := r.pathOffer(\n\t\t\t\t\t\tchoice.route, state.remaining,\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tif offeredRoute == nil || offer == size {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tofferHash := candidateRouteHash(offeredRoute) ^\n\t\t\t\t\t\tcandidateMix64(uint64(offer))\n\t\t\t\t\tif added[offerHash] {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tadded[offerHash] = true\n\n\t\t\t\t\tnext, ok := r.extendPlan(\n\t\t\t\t\t\tstate, offeredRoute, total,\n\t\t\t\t\t)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif next.remaining == 0 {\n\t\t\t\t\t\tif next.score > bestScore {\n\t\t\t\t\t\t\tbestScore = next.score\n\t\t\t\t\t\t\tbest = next.routes\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\treturn best, best != nil\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tparts uint32) (*route.Route, error) {\n\n\tif parts <= 1 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tvalues := make([]lnwire.MilliSatoshi, 0, 8)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tif r.lastFailedShard > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/2, total,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/3, total,\n\t\t)\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, r.lastFailedShard/4, total,\n\t\t)\n\t}\n\n\tcandidateAddAmount(&values, seen, total/2, total)\n\tcandidateAddAmount(&values, seen, total/3, total)\n\tcandidateAddAmount(\n\t\t&values, seen, total/lnwire.MilliSatoshi(parts), total,\n\t)\n\tcandidateAddAmount(&values, seen, total/4, total)\n\tcandidateAddAmount(&values, seen, total*3/4, total)\n\n\tvar best *route.Route\n\tbestScore := math.Inf(-1)\n\n\tfor _, size := range values {\n\t\tchoices := r.routeAlternatives(size, r.reserved, 5)\n\t\tfor _, choice := range choices {\n\t\t\tfinalAmount := candidateFinalAmount(choice.route)\n\t\t\tif finalAmount <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfees := choice.route.TotalAmount - finalAmount\n\t\t\tprogress := float64(finalAmount) / float64(total)\n\t\t\tscore := choice.logProb + 1.25*progress -\n\t\t\t\tfloat64(fees)/10_000_000\n\n\t\t\tif score > bestScore {\n\t\t\t\tbestScore = score\n\t\t\t\tbest = choice.route\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tif plan, ok := r.completePlan(total, int(parts)); ok {\n\t\treturn plan, nil\n\t}\n\n\tfallback, err := r.fallbackShard(total, parts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{fallback}, nil\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) commitLocalRoute(rt *route.Route) {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, 0)\n\tif !ok || key.from != r.source {\n\t\treturn\n\t}\n\tamount, ok := candidateRouteAmount(rt, 0)\n\tif ok {\n\t\tr.localSpent[key.chanID] += amount\n\t}\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) bool {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif len(r.held) == 0 {\n\t\treturn false\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\n\tsettledCount := len(r.held) - count\n\tsettled := settledCount > 0\n\tfor _, rt := range r.held[:settledCount] {\n\t\tr.commitLocalRoute(rt)\n\t}\n\n\tr.held = append([]*route.Route(nil), r.held[settledCount:]...)\n\tfor _, rt := range r.held {\n\t\tr.reserveRoute(rt)\n\t}\n\n\treturn settled\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tsettled := r.syncReservations(inFlightHtlcs)\n\tif settled {\n\t\tr.planned = nil\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil {\n\t\thighEstimate := edge.capacity * 7 / 8\n\t\tif highEstimate > estimate {\n\t\t\testimate = highEstimate\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tdepletedEstimate := edge.capacity / 18\n\t\tif estimate > depletedEstimate {\n\t\t\testimate = depletedEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIndex < 0 {\n\t\tr.markRouteSuspect(rt, 2)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failIndex\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamountOver, ok := candidateRouteAmount(rt, failIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 1)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amountOver + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key]++\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 3\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 2\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 26,
|
|
"parent": 1,
|
|
"score": 0.4501,
|
|
"accepted": false,
|
|
"frontier": false,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst finalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\tbaseFee lnwire.MilliSatoshi\n\tfeeRate lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFee + amt*e.feeRate/1_000_000\n}\n\nfunc (e *candidateEdge) allows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincoming map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tfailures map[candidateEdgeKey]candidateFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\tedgeUses map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailed lnwire.MilliSatoshi\n\tattempts int\n\tlimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(candidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(uint64(e.baseFee) + uint64(e.feeRate)<<17)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincoming: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tfailures: make(map[candidateEdgeKey]candidateFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\tedgeUses: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.limit = 20 + 5*int(maxParts)\n\tif r.limit < 28 {\n\t\tr.limit = 28\n\t}\n\tif r.limit > 64 {\n\t\tr.limit = 64\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFee: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRate: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incoming[node] = append(r.incoming[node], edge)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlow := math.Exp(-x / 0.040)\n\thigh := 1 / (1 + math.Exp((x-0.91)/0.032))\n\tp := 0.46*low + 0.54*high\n\n\tif p < 0.004 {\n\t\treturn 0.004\n\t}\n\tif p > 0.989 {\n\t\treturn 0.989\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity || r.policyBlocked[edge.key] {\n\t\treturn 0\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.failures[edge.key]; ok && failure.upper > 0 {\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase ratio > 0.78:\n\t\t\tretryScale = 0.02\n\t\tcase ratio > 0.62:\n\t\t\tretryScale = 0.09\n\t\tcase ratio > 0.48:\n\t\t\tretryScale = 0.23\n\t\tcase ratio > 0.34:\n\t\t\tretryScale = 0.45\n\t\tcase ratio > 0.20:\n\t\t\tretryScale = 0.70\n\t\tdefault:\n\t\t\tretryScale = 0.88\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.70\n\t\t}\n\t\tif failure.count >= 4 {\n\t\t\tretryScale *= 0.55\n\t\t}\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.005\n\n\tcase belief.lowerOK > 0 && belief.upperFail > belief.lowerOK:\n\t\tposition := float64(total-belief.lowerOK) /\n\t\t\tfloat64(belief.upperFail-belief.lowerOK)\n\t\tif position < 0 {\n\t\t\tposition = 0\n\t\t}\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\t\tevidence := 0.995 - 0.990*position\n\t\tp = 0.08*p + 0.92*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tif ratio > 0.18 {\n\t\t\tscale := 1 - 0.94*(ratio-0.18)/0.82\n\t\t\tif scale < 0.05 {\n\t\t\t\tscale = 0.05\n\t\t\t}\n\t\t\tp *= scale\n\t\t}\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tboost := 0.72 * math.Exp(-distance/0.18)\n\t\tp += boost * (1 - p)\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := math.Max(1, 0.065*float64(edge.capacity))\n\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\t\tweight := 0.25\n\t\tif belief.successes+belief.failures >= 3 {\n\t\t\tweight = 0.38\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateP\n\t}\n\n\tp *= retryScale\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.allows(amt) {\n\t\treturn 0\n\t}\n\n\ttotal := r.reserved[edge.key] + amt\n\tif total < amt || total > edge.capacity {\n\t\treturn 0\n\t}\n\n\treturn r.liquidityProbability(edge, total)\n}\n\ntype candidateSearchState struct {\n\tnode route.Vertex\n\trequired lnwire.MilliSatoshi\n\tscore float64\n\tlogProb float64\n\tpath []*candidateEdge\n\tindex int\n}\n\ntype candidateSearchQueue []*candidateSearchState\n\nfunc (q candidateSearchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateSearchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateSearchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateSearchQueue) Push(value any) {\n\titem := value.(*candidateSearchState)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateSearchQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\ntype candidateLabel struct {\n\trequired lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc candidateDominated(labels []candidateLabel,\n\trequired lnwire.MilliSatoshi, score float64) bool {\n\n\tfor _, label := range labels {\n\t\tif label.required <= required && label.score <= score {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAddLabel(labels []candidateLabel,\n\trequired lnwire.MilliSatoshi, score float64) []candidateLabel {\n\n\tout := labels[:0]\n\tfor _, label := range labels {\n\t\tif required <= label.required && score <= label.score {\n\t\t\tcontinue\n\t\t}\n\t\tout = append(out, label)\n\t}\n\tout = append(out, candidateLabel{\n\t\trequired: required,\n\t\tscore: score,\n\t})\n\n\tsort.Slice(out, func(i, j int) bool {\n\t\treturn out[i].score < out[j].score\n\t})\n\tif len(out) > 10 {\n\t\tout = out[:10]\n\t}\n\treturn out\n}\n\nfunc candidatePathContains(path []*candidateEdge, node route.Vertex) bool {\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\tpenalties map[candidateEdgeKey]float64) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 285_000.0\n\t\thopPenalty = 750.0\n\t\tmaxHops = 16\n\t\tmaxExpansions = 50000\n\t)\n\n\tqueue := &candidateSearchQueue{}\n\theap.Push(queue, &candidateSearchState{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tlabels := make(map[route.Vertex][]candidateLabel)\n\tlabels[r.spec.Target] = []candidateLabel{{\n\t\trequired: amt,\n\t\tscore: 0,\n\t}}\n\n\texpansions := 0\n\tfor queue.Len() > 0 && expansions < maxExpansions {\n\t\tstate := heap.Pop(queue).(*candidateSearchState)\n\t\texpansions++\n\n\t\tif state.node == r.source {\n\t\t\trt, err := r.buildRoute(amt, state.path)\n\t\t\treturn rt, state.logProb, err\n\t\t}\n\t\tif len(state.path) >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, edge := range r.incoming[state.node] {\n\t\t\tif candidatePathContains(state.path, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOverEdge := state.required\n\t\t\tp := r.probability(edge, amountOverEdge)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\trequired := amountOverEdge + fee\n\t\t\tif required < amountOverEdge {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(r.edgeUses[edge.key]) * 8_000\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 45_000\n\t\t\tif penalties != nil {\n\t\t\t\tedgeScore += penalties[edge.key]\n\t\t\t}\n\n\t\t\tscore := state.score + edgeScore\n\t\t\tif candidateDominated(\n\t\t\t\tlabels[edge.key.from], required, score,\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlabels[edge.key.from] = candidateAddLabel(\n\t\t\t\tlabels[edge.key.from], required, score,\n\t\t\t)\n\n\t\t\tpath := make([]*candidateEdge, len(state.path)+1)\n\t\t\tpath[0] = edge\n\t\t\tcopy(path[1:], state.path)\n\n\t\t\theap.Push(queue, &candidateSearchState{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\trequired: required,\n\t\t\t\tscore: score,\n\t\t\t\tlogProb: state.logProb + math.Log(p),\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 || path[0].key.from != r.source ||\n\t\tpath[len(path)-1].key.to != r.spec.Target {\n\n\t\treturn nil, errors.New(\"invalid route path\")\n\t}\n\n\tfor i := 1; i < len(path); i++ {\n\t\tif path[i-1].key.to != path[i].key.from {\n\t\t\treturn nil, errors.New(\"disconnected route path\")\n\t\t}\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamounts[i] = amounts[i+1] + outgoing.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tindex int) (lnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif index == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[index-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteHash(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\th := candidateMix64(candidateVertexHash(rt.SourcePubKey))\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\th ^= candidateMix64(\n\t\t\tkey.chanID + uint64(i+1)*0x9e3779b97f4a7c15,\n\t\t)\n\t\th ^= candidateMix64(candidateVertexHash(key.to))\n\t}\n\treturn candidateMix64(h)\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tbase := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\tvalues := make([]lnwire.MilliSatoshi, 0, 28)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, pair := range [][2]int64{\n\t\t{1, 1}, {3, 4}, {5, 4}, {1, 2}, {3, 2},\n\t\t{2, 1}, {1, 3}, {2, 3}, {4, 3},\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen,\n\t\t\tbase*lnwire.MilliSatoshi(pair[0])/\n\t\t\t\tlnwire.MilliSatoshi(pair[1]),\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailed > 0 {\n\t\tfor _, pair := range [][2]int64{\n\t\t\t{1, 5}, {1, 4}, {1, 3}, {2, 5}, {1, 2}, {3, 5},\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\tr.lastFailed*lnwire.MilliSatoshi(pair[0])/\n\t\t\t\t\tlnwire.MilliSatoshi(pair[1]),\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor key, belief := range r.beliefs {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\t\treserved := r.reserved[key]\n\n\t\tif belief.lowerOK > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, belief.lowerOK-reserved,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, belief.estimate-reserved,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > reserved {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\t(belief.upperFail-reserved)*2/5,\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\tdi := math.Abs(math.Log(\n\t\t\t(float64(values[i]) + 1) / (float64(base) + 1),\n\t\t))\n\t\tdj := math.Abs(math.Log(\n\t\t\t(float64(values[j]) + 1) / (float64(base) + 1),\n\t\t))\n\t\treturn di < dj\n\t})\n\n\tif len(values) > 18 {\n\t\tvalues = values[:18]\n\t}\n\treturn values\n}\n\ntype candidateRouteOption struct {\n\troute *route.Route\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) routeOptions(\n\tamt lnwire.MilliSatoshi, count int) []candidateRouteOption {\n\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tseen := make(map[uint64]bool)\n\toptions := make([]candidateRouteOption, 0, count)\n\n\tfor len(options) < count {\n\t\trt, logProb, err := r.findRoute(amt, penalties)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\thash := candidateRouteHash(rt)\n\t\tif !seen[hash] {\n\t\t\tseen[hash] = true\n\t\t\toptions = append(options, candidateRouteOption{\n\t\t\t\troute: rt,\n\t\t\t\tlogProb: logProb,\n\t\t\t})\n\t\t}\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif key.from == r.source {\n\t\t\t\tpenalties[key] += 170_000\n\t\t\t} else {\n\t\t\t\tpenalties[key] += 520_000\n\t\t\t}\n\t\t}\n\n\t\tif len(seen) > count*2 {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn options\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\tslots int\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, amount := range reservations {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, amount)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tsent := lnwire.MilliSatoshi(0)\n\tuses := make(map[candidateEdgeKey]uint32)\n\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tsent += finalAmount\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif ok {\n\t\t\t\tuses[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 5_000_000\n\tscore -= 0.035 * float64(len(routes)-1)\n\n\tfor key, count := range uses {\n\t\tif count > 1 && key.from != r.source {\n\t\t\tscore -= 0.075 * float64(count-1)\n\t\t}\n\t}\n\n\tif total > 0 {\n\t\tscore += 0.25 * float64(sent) / float64(total)\n\t}\n\treturn score\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 12\n\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\tslots: parts,\n\t\treservations: candidateCopyReservations(saved),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\texpanded := make([]candidatePlanState, 0, beamWidth*8)\n\n\t\tfor _, state := range states {\n\t\t\tfor _, size := range r.shardSizes(\n\t\t\t\tstate.remaining, state.slots,\n\t\t\t) {\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\n\t\t\t\tfor _, option := range r.routeOptions(size, 4) {\n\t\t\t\t\tremaining := state.remaining - size\n\t\t\t\t\tslots := state.slots - 1\n\t\t\t\t\tif remaining < 0 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif (remaining == 0) != (slots == 0) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\t\tstate.reservations,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateReserveInto(\n\t\t\t\t\t\treservations, option.route,\n\t\t\t\t\t)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\tappend([]*route.Route(nil), state.routes...),\n\t\t\t\t\t\toption.route,\n\t\t\t\t\t)\n\t\t\t\t\tscore := r.planScore(\n\t\t\t\t\t\troutes, reservations, total,\n\t\t\t\t\t)\n\t\t\t\t\tif math.IsInf(score, -1) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\texpanded = append(expanded, candidatePlanState{\n\t\t\t\t\t\tremaining: remaining,\n\t\t\t\t\t\tslots: slots,\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\treservations: reservations,\n\t\t\t\t\t\tscore: score,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.SliceStable(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 && state.slots == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\treturn nil, 0, false\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfull, fullLogProb, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.88) &&\n\t\tr.lastFailed == 0 {\n\n\t\treturn []*route.Route{full}, nil\n\t}\n\n\tmaxParts := int(parts)\n\tif maxParts > 10 {\n\t\tmaxParts = 10\n\t}\n\tif lnwire.MilliSatoshi(maxParts) > total {\n\t\tmaxParts = int(total)\n\t}\n\n\tvar best []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(r.reserved)\n\t\tcandidateReserveInto(reservations, full)\n\t\tbest = []*route.Route{full}\n\t\tbestScore = r.planScore(best, reservations, total)\n\t}\n\n\tfor count := 2; count <= maxParts; count++ {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif ok && (best == nil || score > bestScore) {\n\t\t\tbest = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif best == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{full}, nil\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) fallbackRoute(\n\tremaining lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif remaining <= 1 {\n\t\treturn nil, errors.New(\"no smaller shard available\")\n\t}\n\n\tvalues := make([]lnwire.MilliSatoshi, 0, 20)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - 1\n\n\tfor _, pair := range [][2]int64{\n\t\t{3, 4}, {2, 3}, {3, 5}, {1, 2}, {2, 5},\n\t\t{1, 3}, {1, 4}, {1, 6}, {1, 8}, {1, 12},\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen,\n\t\t\tremaining*lnwire.MilliSatoshi(pair[0])/\n\t\t\t\tlnwire.MilliSatoshi(pair[1]),\n\t\t\tminimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailed > 0 {\n\t\tfor _, pair := range [][2]int64{\n\t\t\t{3, 5}, {1, 2}, {2, 5}, {1, 3}, {1, 4}, {1, 6},\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen,\n\t\t\t\tr.lastFailed*lnwire.MilliSatoshi(pair[0])/\n\t\t\t\t\tlnwire.MilliSatoshi(pair[1]),\n\t\t\t\tminimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.SliceStable(values, func(i, j int) bool {\n\t\treturn values[i] > values[j]\n\t})\n\n\tvar best *route.Route\n\tbestUtility := math.Inf(-1)\n\n\tfor _, amount := range values {\n\t\tfor _, option := range r.routeOptions(amount, 4) {\n\t\t\tfraction := float64(amount) / float64(remaining)\n\t\t\tfee := option.route.TotalAmount -\n\t\t\t\tcandidateFinalAmount(option.route)\n\t\t\tutility := option.logProb +\n\t\t\t\t1.45*math.Log(fraction) -\n\t\t\t\tfloat64(fee)/4_000_000\n\n\t\t\tif utility > bestUtility {\n\t\t\t\tbestUtility = utility\n\t\t\t\tbest = option.route\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) routeFits(rt *route.Route) bool {\n\tadded := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil || r.policyBlocked[key] {\n\t\t\treturn false\n\t\t}\n\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif !ok || !edge.allows(amount) {\n\t\t\treturn false\n\t\t}\n\n\t\tadded[key] += amount\n\t\ttotal := r.reserved[key] + added[key]\n\t\tif total > edge.capacity {\n\t\t\treturn false\n\t\t}\n\n\t\tif key.from == r.source {\n\t\t\tbalance, ok := r.localBalances[key.chanID]\n\t\t\tif !ok || total > balance {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\tif failure, ok := r.failures[key]; ok &&\n\t\t\tfailure.upper > 0 && total >= failure.upper {\n\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) recordUse(rt *route.Route) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif ok {\n\t\t\tr.edgeUses[key]++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.limit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) > 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt && r.routeFits(rt) {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.recordUse(rt)\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, planErr := r.makePlan(amt, partsLeft)\n\tif planErr == nil && len(plan) > 0 {\n\t\trt := plan[0]\n\t\tif len(plan) > 1 {\n\t\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t\t} else {\n\t\t\tr.planned = nil\n\t\t}\n\n\t\tr.recordUse(rt)\n\t\tr.attempts++\n\t\treturn rt, nil\n\t}\n\n\trt, fallbackErr := r.fallbackRoute(amt)\n\tif fallbackErr != nil {\n\t\tif planErr != nil {\n\t\t\treturn nil, planErr\n\t\t}\n\t\treturn nil, fallbackErr\n\t}\n\n\tr.planned = nil\n\tr.recordUse(rt)\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.failures[key]; ok &&\n\t\tfailure.upper > 0 && amount >= failure.upper {\n\n\t\tdelete(r.failures, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amount > belief.lowerOK {\n\t\tbelief.lowerOK = amount\n\t}\n\tif belief.upperFail > 0 && amount >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amount\n\tif edge := r.edges[key]; edge != nil {\n\t\thigh := edge.capacity * 17 / 20\n\t\tif high > estimate {\n\t\t\testimate = high\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.failures[key]\n\tif current.upper == 0 || amount < current.upper {\n\t\tcurrent.upper = amount\n\t}\n\tcurrent.count++\n\tr.failures[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amount < belief.upperFail {\n\t\tbelief.upperFail = amount\n\t}\n\tif belief.lowerOK >= amount {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amount / 4\n\tif edge := r.edges[key]; edge != nil {\n\t\tlow := edge.capacity / 20\n\t\tif estimate > low {\n\t\t\testimate = low\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amount ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markSuspect(rt *route.Route, weight uint32) {\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif ok {\n\t\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailed = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\tr.lastFailed = candidateFinalAmount(rt)\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tpassed := failureIndex\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markSuspect(rt, 1)\n\t\treturn nil\n\t}\n\tamount, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markSuspect(rt, 1)\n\t\treturn nil\n\t}\n\n\ttotal := amount + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, total)\n\t\tr.suspect[key]++\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 3\n\n\tdefault:\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 2\n\t}\n\n\treturn nil\n}\n\nvar _ routing.SimRouter = (*candidateRouter)(nil)\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\"candidate-router(%x)\", r.source[:4])\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 27,
|
|
"parent": 3,
|
|
"score": 0.4195,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst candidateFinalCltvDelta = 40\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuccesses uint32\n\tfailures uint32\n}\n\ntype candidateCurrentFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint32\n}\n\ntype candidateNetworkKey struct {\n\tsource route.Vertex\n\tfingerprint uint64\n}\n\ntype candidateNetworkMemory struct {\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[candidateNetworkKey]*candidateNetworkMemory\n}{\n\tnetworks: make(map[candidateNetworkKey]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tnetworkKey candidateNetworkKey\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\tcurrentFails map[candidateEdgeKey]candidateCurrentFailure\n\tpolicyBlocked map[candidateEdgeKey]bool\n\tsuspect map[candidateEdgeKey]uint32\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\theld []*route.Route\n\tplanned []*route.Route\n\n\tlastFailedShard lnwire.MilliSatoshi\n\tattempts int\n\tattemptLimit int\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\treturn x ^ (x >> 31)\n}\n\nfunc candidateVertexHash(v route.Vertex) uint64 {\n\th := uint64(1469598103934665603)\n\tfor i, b := range v {\n\t\th ^= uint64(b) + uint64(i+1)<<8\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\tx := candidateMix64(e.key.chanID)\n\tx ^= candidateMix64(candidateVertexHash(e.key.from))\n\tx ^= candidateMix64(\n\t\tcandidateVertexHash(e.key.to) + 0x9e3779b97f4a7c15,\n\t)\n\tx ^= candidateMix64(uint64(e.capacity))\n\tx ^= candidateMix64(\n\t\tuint64(e.baseFeeMsat) + uint64(e.feeRatePPM)<<17,\n\t)\n\tx ^= candidateMix64(\n\t\tuint64(e.timeLockDelta) + uint64(e.minHTLC)<<16,\n\t)\n\tx ^= candidateMix64(uint64(e.maxHTLC))\n\n\treturn candidateMix64(x)\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrentFails: make(map[candidateEdgeKey]candidateCurrentFailure),\n\t\tpolicyBlocked: make(map[candidateEdgeKey]bool),\n\t\tsuspect: make(map[candidateEdgeKey]uint32),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tr.attemptLimit = int(maxParts) + 12\n\tif r.attemptLimit < 16 {\n\t\tr.attemptLimit = 16\n\t}\n\tif r.attemptLimit > 36 {\n\t\tr.attemptLimit = 36\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar fingerprint uint64\n\n\tfor len(queue) != 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(\n\t\t\tctx, node, func(ch *graphdb.DirectedChannel) error {\n\t\t\t\tif !seen[ch.OtherNode] {\n\t\t\t\t\tseen[ch.OtherNode] = true\n\t\t\t\t\tqueue = append(queue, ch.OtherNode)\n\t\t\t\t}\n\n\t\t\t\tpolicy := ch.InPolicy\n\t\t\t\tif policy == nil || policy.IsDisabled {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: candidateEdgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t\tto: node,\n\t\t\t\t\t},\n\t\t\t\t\tcapacity: lnwire.NewMSatFromSatoshis(\n\t\t\t\t\t\tch.Capacity,\n\t\t\t\t\t),\n\t\t\t\t\tbaseFeeMsat: policy.FeeBaseMSat,\n\t\t\t\t\tfeeRatePPM: policy.FeeProportionalMillionths,\n\t\t\t\t\ttimeLockDelta: policy.TimeLockDelta,\n\t\t\t\t\tminHTLC: policy.MinHTLC,\n\t\t\t\t}\n\t\t\t\tif policy.HasMaxHTLC {\n\t\t\t\t\tedge.maxHTLC = policy.MaxHTLC\n\t\t\t\t}\n\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\tr.edges[edge.key] = edge\n\t\t\t\tfingerprint ^= candidateEdgeHash(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.networkKey = candidateNetworkKey{\n\t\tsource: source,\n\t\tfingerprint: fingerprint,\n\t}\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tfor key, belief := range memory.beliefs {\n\t\tif r.edges[key] != nil {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 || capacity <= 0 || amt > capacity {\n\t\treturn 0\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.050)\n\thighMode := 1 / (1 + math.Exp((x-0.925)/0.033))\n\tp := 0.47*lowMode + 0.53*highMode\n\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) liquidityProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif total <= 0 || total > edge.capacity ||\n\t\tr.policyBlocked[edge.key] {\n\n\t\treturn 0\n\t}\n\n\tretryScale := 1.0\n\tif failure, ok := r.currentFails[edge.key]; ok &&\n\t\tfailure.upper > 0 {\n\n\t\tif total >= failure.upper {\n\t\t\treturn 0\n\t\t}\n\n\t\tratio := float64(total) / float64(failure.upper)\n\t\tswitch {\n\t\tcase failure.count >= 3 && ratio > 0.34:\n\t\t\treturn 0\n\t\tcase failure.count >= 2 && ratio > 0.50:\n\t\t\treturn 0\n\t\tcase ratio > 0.76:\n\t\t\treturn 0\n\t\tcase ratio > 0.62:\n\t\t\tretryScale = 0.13\n\t\tcase ratio > 0.48:\n\t\t\tretryScale = 0.30\n\t\tcase ratio > 0.32:\n\t\t\tretryScale = 0.57\n\t\tdefault:\n\t\t\tretryScale = 0.84\n\t\t}\n\t\tif failure.count >= 2 {\n\t\t\tretryScale *= 0.68\n\t\t}\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance, ok := r.localBalances[edge.key.chanID]\n\t\tif !ok || total > balance {\n\t\t\treturn 0\n\t\t}\n\t\treturn 0.9995 * retryScale\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn p * retryScale\n\t}\n\n\tswitch {\n\tcase belief.lowerOK > 0 && total <= belief.lowerOK:\n\t\tp = 0.996\n\n\tcase belief.upperFail > 0 && total >= belief.upperFail:\n\t\tp = 0.002\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\twidth := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(total-belief.lowerOK) / width\n\t\tif position < 0 {\n\t\t\tposition = 0\n\t\t}\n\t\tif position > 1 {\n\t\t\tposition = 1\n\t\t}\n\n\t\tevidence := 0.996 - 0.993*position\n\t\tp = 0.08*p + 0.92*evidence\n\n\tcase belief.upperFail > 0:\n\t\tratio := float64(total) / float64(belief.upperFail)\n\t\tswitch {\n\t\tcase ratio >= 0.80:\n\t\t\tp *= 0.04\n\t\tcase ratio >= 0.60:\n\t\t\tp *= 0.13\n\t\tcase ratio >= 0.42:\n\t\t\tp *= 0.32\n\t\tcase ratio >= 0.25:\n\t\t\tp *= 0.60\n\t\t}\n\n\tcase belief.lowerOK > 0:\n\t\tdistance := float64(total-belief.lowerOK) /\n\t\t\tfloat64(edge.capacity)\n\t\tif distance > 0 {\n\t\t\tboost := 0.66 * math.Exp(-distance/0.17)\n\t\t\tp += boost * (1 - p)\n\t\t}\n\t}\n\n\tif belief.estimate > 0 {\n\t\tscale := 0.065 * float64(edge.capacity)\n\t\tif scale < 1 {\n\t\t\tscale = 1\n\t\t}\n\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t(float64(total)-float64(belief.estimate))/scale,\n\t\t))\n\n\t\tweight := 0.14\n\t\tevidence := belief.successes + belief.failures\n\t\tif evidence >= 2 {\n\t\t\tweight = 0.23\n\t\t}\n\t\tif evidence >= 4 {\n\t\t\tweight = 0.32\n\t\t}\n\t\tp = (1-weight)*p + weight*estimateP\n\t}\n\n\tp *= retryScale\n\tswitch {\n\tcase p < 0.001:\n\t\treturn 0.001\n\tcase p > 0.997:\n\t\treturn 0.997\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif !edge.policyAllows(amt) {\n\t\treturn 0\n\t}\n\n\treserved := r.reserved[edge.key]\n\tif reserved > edge.capacity || amt > edge.capacity-reserved {\n\t\treturn 0\n\t}\n\n\ttotalP := r.liquidityProbability(edge, reserved+amt)\n\tif totalP <= 0 {\n\t\treturn 0\n\t}\n\tif reserved == 0 {\n\t\treturn totalP\n\t}\n\n\tbaseP := r.liquidityProbability(edge, reserved)\n\tif baseP <= 0 {\n\t\treturn 0\n\t}\n\n\tp := totalP / baseP\n\tswitch {\n\tcase p < 0.001:\n\t\treturn 0.001\n\tcase p > 0.997:\n\t\treturn 0.997\n\tdefault:\n\t\treturn p\n\t}\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n\tlogProb float64\n\tedge *candidateEdge\n\tnext *candidatePathLabel\n\thops int\n\tactive bool\n\tindex int\n}\n\ntype candidatePathQueue []*candidatePathLabel\n\nfunc (q candidatePathQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidatePathQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidatePathQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidatePathQueue) Push(value any) {\n\tlabel := value.(*candidatePathLabel)\n\tlabel.index = len(*q)\n\t*q = append(*q, label)\n}\n\nfunc (q *candidatePathQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\tlabel := old[last]\n\t*q = old[:last]\n\treturn label\n}\n\nfunc candidatePathContains(label *candidatePathLabel,\n\tnode route.Vertex) bool {\n\n\tfor current := label; current != nil; current = current.next {\n\t\tif current.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(labels map[route.Vertex][]*candidatePathLabel,\n\tlabel *candidatePathLabel) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif old.active && old.score <= label.score &&\n\t\t\told.required <= label.required {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := current[:0]\n\tfor _, old := range current {\n\t\tif old.active && label.score <= old.score &&\n\t\t\tlabel.required <= old.required {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tif old.active {\n\t\t\tkept = append(kept, old)\n\t\t}\n\t}\n\tkept = append(kept, label)\n\n\tif len(kept) > 4 {\n\t\tsort.Slice(kept, func(i, j int) bool {\n\t\t\tleft := kept[i].score +\n\t\t\t\tfloat64(kept[i].required)/20_000_000\n\t\t\tright := kept[j].score +\n\t\t\t\tfloat64(kept[j].required)/20_000_000\n\t\t\treturn left < right\n\t\t})\n\t\tfor _, old := range kept[4:] {\n\t\t\told.active = false\n\t\t}\n\t\tkept = kept[:4]\n\t}\n\n\tlabels[label.node] = kept\n\treturn label.active\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textraPenalty map[candidateEdgeKey]float64) (*route.Route, float64,\n\terror) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, 0, errors.New(\"source equals target\")\n\t}\n\n\tconst (\n\t\triskWeight = 920_000.0\n\t\thopPenalty = 42_000.0\n\t\tmaxHops = 30\n\t\tmaxExpand = 16000\n\t)\n\n\ttarget := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidatePathLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tqueue := &candidatePathQueue{}\n\theap.Push(queue, target)\n\n\tvar sourceLabel *candidatePathLabel\n\texpanded := 0\n\n\tfor queue.Len() != 0 && expanded < maxExpand {\n\t\titem := heap.Pop(queue).(*candidatePathLabel)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLabel = item\n\t\t\tbreak\n\t\t}\n\t\tif item.hops >= maxHops {\n\t\t\tcontinue\n\t\t}\n\n\t\texpanded++\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif candidatePathContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamountOverEdge := item.required\n\t\t\tp := r.probability(edge, amountOverEdge)\n\t\t\tif p <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amountOverEdge)\n\t\t\t}\n\t\t\trequired := amountOverEdge + fee\n\n\t\t\tedgeScore := float64(fee) +\n\t\t\t\triskWeight*(-math.Log(p)) + hopPenalty\n\t\t\tedgeScore += float64(r.suspect[edge.key]) * 440_000\n\t\t\tif extraPenalty != nil {\n\t\t\t\tedgeScore += extraPenalty[edge.key]\n\t\t\t}\n\n\t\t\tlabel := &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score + edgeScore,\n\t\t\t\trequired: required,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tedge: edge,\n\t\t\t\tnext: item,\n\t\t\t\thops: item.hops + 1,\n\t\t\t\tactive: true,\n\t\t\t}\n\t\t\tif candidateInsertLabel(labels, label) {\n\t\t\t\theap.Push(queue, label)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0, sourceLabel.hops)\n\tfor current := sourceLabel; current != nil && current.edge != nil;\n\t\tcurrent = current.next {\n\n\t\tpath = append(path, current.edge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn rt, sourceLabel.logProb, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tnode := r.source\n\tfor _, edge := range path {\n\t\tif edge.key.from != node {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tnode = edge.key.to\n\t}\n\tif node != r.spec.Target {\n\t\treturn nil, errors.New(\"path does not reach target\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = candidateFinalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tforwardingEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(candidateFinalCltvDelta)\n\t\tif i < last {\n\t\t\tamountToForward = amounts[i+1]\n\t\t\toutgoingExpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amountToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route, index int) (\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\tif index == 0 {\n\t\treturn rt.TotalAmount, true\n\t}\n\treturn rt.Hops[index-1].AmtToForward, true\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tindex int) (candidateEdgeKey, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn candidateEdgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, true\n}\n\nfunc candidateFinalAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateCopyReservations(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi, len(source),\n\t)\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc candidateReserveInto(\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\treservations[key] += amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tcandidateReserveInto(r.reserved, rt)\n}\n\nfunc (r *candidateRouter) syncReservations(inFlight uint32) {\n\tr.reserved = make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tif inFlight == 0 {\n\t\tr.held = nil\n\t\treturn\n\t}\n\n\tcount := int(inFlight)\n\tif count > len(r.held) {\n\t\tcount = len(r.held)\n\t}\n\tfor _, rt := range r.held[len(r.held)-count:] {\n\t\tr.reserveRoute(rt)\n\t}\n}\n\nfunc candidateAddAmount(values *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif maximum < minimum {\n\t\treturn\n\t}\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > maximum {\n\t\tvalue = maximum\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn\n\t}\n\n\tseen[value] = true\n\t*values = append(*values, value)\n}\n\nfunc (r *candidateRouter) shardAnchors(\n\tavg lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tanchors := make([]lnwire.MilliSatoshi, 0, 32)\n\n\tfor key, edge := range r.edges {\n\t\tendpoint := edge.key.from == r.source ||\n\t\t\tedge.key.to == r.spec.Target\n\t\tbelief, learned := r.beliefs[key]\n\t\tif !endpoint && !learned {\n\t\t\tcontinue\n\t\t}\n\n\t\treserved := r.reserved[key]\n\t\tlimit := edge.capacity\n\t\tif edge.maxHTLC > 0 && edge.maxHTLC < limit {\n\t\t\tlimit = edge.maxHTLC\n\t\t}\n\t\tif edge.key.from == r.source {\n\t\t\tif balance, ok := r.localBalances[key.chanID]; ok &&\n\t\t\t\tbalance < limit {\n\n\t\t\t\tlimit = balance\n\t\t\t}\n\t\t}\n\t\tif limit > reserved {\n\t\t\tanchors = append(\n\t\t\t\tanchors, (limit-reserved)*9/10,\n\t\t\t)\n\t\t}\n\n\t\tif failure, ok := r.currentFails[key]; ok &&\n\t\t\tfailure.upper > reserved {\n\n\t\t\tanchors = append(\n\t\t\t\tanchors, (failure.upper-reserved)*9/20,\n\t\t\t)\n\t\t}\n\t\tif belief.lowerOK > reserved {\n\t\t\tanchors = append(\n\t\t\t\tanchors, (belief.lowerOK-reserved)*19/20,\n\t\t\t)\n\t\t}\n\t\tif belief.upperFail > reserved {\n\t\t\tanchors = append(\n\t\t\t\tanchors, (belief.upperFail-reserved)*9/20,\n\t\t\t)\n\t\t}\n\t\tif belief.estimate > reserved {\n\t\t\tanchors = append(\n\t\t\t\tanchors, (belief.estimate-reserved)*4/5,\n\t\t\t)\n\t\t}\n\t}\n\n\tsort.Slice(anchors, func(i, j int) bool {\n\t\tif anchors[i] <= 0 {\n\t\t\treturn false\n\t\t}\n\t\tif anchors[j] <= 0 {\n\t\t\treturn true\n\t\t}\n\t\tleft := math.Abs(math.Log(\n\t\t\tfloat64(anchors[i]) / float64(avg),\n\t\t))\n\t\tright := math.Abs(math.Log(\n\t\t\tfloat64(anchors[j]) / float64(avg),\n\t\t))\n\t\treturn left < right\n\t})\n\tif len(anchors) > 5 {\n\t\tanchors = anchors[:5]\n\t}\n\treturn anchors\n}\n\nfunc (r *candidateRouter) shardSizes(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\tif slots <= 1 {\n\t\treturn []lnwire.MilliSatoshi{remaining}\n\t}\n\n\tminimum := lnwire.MilliSatoshi(1)\n\tmaximum := remaining - lnwire.MilliSatoshi(slots-1)\n\tif maximum < minimum {\n\t\treturn nil\n\t}\n\n\tavg := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tvalues := make([]lnwire.MilliSatoshi, 0, 14)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tfor _, value := range []lnwire.MilliSatoshi{\n\t\tavg / 3,\n\t\tavg / 2,\n\t\tavg * 2 / 3,\n\t\tavg,\n\t\tavg * 4 / 3,\n\t\tavg * 3 / 2,\n\t\tavg * 2,\n\t} {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\tif r.lastFailedShard > 0 {\n\t\tfor _, value := range []lnwire.MilliSatoshi{\n\t\t\tr.lastFailedShard / 3,\n\t\t\tr.lastFailedShard * 9 / 20,\n\t\t\tr.lastFailedShard * 3 / 5,\n\t\t} {\n\t\t\tcandidateAddAmount(\n\t\t\t\t&values, seen, value, minimum, maximum,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor _, value := range r.shardAnchors(avg) {\n\t\tcandidateAddAmount(\n\t\t\t&values, seen, value, minimum, maximum,\n\t\t)\n\t}\n\n\treturn values\n}\n\nfunc candidateSharedPenalty(routes []*route.Route) map[candidateEdgeKey]float64 {\n\tpenalties := make(map[candidateEdgeKey]float64)\n\tfor _, rt := range routes {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch {\n\t\t\tcase key.from == rt.SourcePubKey:\n\t\t\t\tpenalties[key] += 600_000\n\t\t\tcase key.to == rt.Hops[len(rt.Hops)-1].PubKeyBytes:\n\t\t\t\tpenalties[key] += 2_000_000\n\t\t\tdefault:\n\t\t\t\tpenalties[key] += 2_700_000\n\t\t\t}\n\t\t}\n\t}\n\treturn penalties\n}\n\nfunc (r *candidateRouter) planScore(routes []*route.Route,\n\treservations, base map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tscore := 0.0\n\tfor key, total := range reservations {\n\t\told := base[key]\n\t\tif total <= old {\n\t\t\tcontinue\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\n\t\tp := r.liquidityProbability(edge, total)\n\t\tif p <= 0 {\n\t\t\treturn math.Inf(-1)\n\t\t}\n\t\tif old > 0 {\n\t\t\toldP := r.liquidityProbability(edge, old)\n\t\t\tif oldP <= 0 {\n\t\t\t\treturn math.Inf(-1)\n\t\t\t}\n\t\t\tp /= oldP\n\t\t}\n\n\t\tif p < 0.001 {\n\t\t\tp = 0.001\n\t\t}\n\t\tif p > 0.999 {\n\t\t\tp = 0.999\n\t\t}\n\t\tscore += math.Log(p)\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\tuses := make(map[candidateEdgeKey]uint32)\n\tfor _, rt := range routes {\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif rt.TotalAmount > finalAmount {\n\t\t\tfees += rt.TotalAmount - finalAmount\n\t\t}\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tuses[key]++\n\t\t\tscore -= 0.035 * float64(r.suspect[key])\n\t\t}\n\t}\n\n\tscore -= float64(fees) / 25_000_000\n\tif len(routes) > 1 {\n\t\tscore -= 0.025 * float64(len(routes)-1)\n\t}\n\tfor key, count := range uses {\n\t\tif count <= 1 {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source {\n\t\t\tscore -= 0.045 * float64(count-1)\n\t\t} else {\n\t\t\tscore -= 0.16 * float64(count-1)\n\t\t}\n\t}\n\n\treturn score\n}\n\ntype candidatePlanState struct {\n\tremaining lnwire.MilliSatoshi\n\troutes []*route.Route\n\treservations map[candidateEdgeKey]lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc (r *candidateRouter) planWithParts(total lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, float64, bool) {\n\n\tconst beamWidth = 3\n\n\tbase := candidateCopyReservations(r.reserved)\n\tsaved := candidateCopyReservations(r.reserved)\n\tdefer func() {\n\t\tr.reserved = saved\n\t}()\n\n\tstates := []candidatePlanState{{\n\t\tremaining: total,\n\t\treservations: candidateCopyReservations(base),\n\t}}\n\n\tfor depth := 0; depth < parts; depth++ {\n\t\tslots := parts - depth\n\t\texpanded := make([]candidatePlanState, 0, beamWidth*12)\n\n\t\tfor _, state := range states {\n\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\tstate.reservations,\n\t\t\t)\n\t\t\tsizes := r.shardSizes(state.remaining, slots)\n\t\t\tpenalty := candidateSharedPenalty(state.routes)\n\n\t\t\tfor _, size := range sizes {\n\t\t\t\tif size <= 0 || size > state.remaining {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tremaining := state.remaining - size\n\t\t\t\tif slots == 1 && remaining != 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif slots > 1 &&\n\t\t\t\t\tremaining < lnwire.MilliSatoshi(slots-1) {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tr.reserved = candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\trt, _, err := r.findRoute(size, penalty)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treservations := candidateCopyReservations(\n\t\t\t\t\tstate.reservations,\n\t\t\t\t)\n\t\t\t\tcandidateReserveInto(reservations, rt)\n\n\t\t\t\troutes := append(\n\t\t\t\t\tappend([]*route.Route(nil), state.routes...),\n\t\t\t\t\trt,\n\t\t\t\t)\n\t\t\t\tscore := r.planScore(routes, reservations, base)\n\t\t\t\tif math.IsInf(score, -1) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\texpanded = append(expanded, candidatePlanState{\n\t\t\t\t\tremaining: remaining,\n\t\t\t\t\troutes: routes,\n\t\t\t\t\treservations: reservations,\n\t\t\t\t\tscore: score,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif len(expanded) == 0 {\n\t\t\treturn nil, 0, false\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\tif expanded[i].score == expanded[j].score {\n\t\t\t\treturn expanded[i].remaining <\n\t\t\t\t\texpanded[j].remaining\n\t\t\t}\n\t\t\treturn expanded[i].score > expanded[j].score\n\t\t})\n\t\tif len(expanded) > beamWidth {\n\t\t\texpanded = expanded[:beamWidth]\n\t\t}\n\t\tstates = expanded\n\t}\n\n\tfor _, state := range states {\n\t\tif state.remaining == 0 {\n\t\t\treturn state.routes, state.score, true\n\t\t}\n\t}\n\n\treturn nil, 0, false\n}\n\nfunc candidatePartCounts(maxParts int) []int {\n\tif maxParts < 2 {\n\t\treturn nil\n\t}\n\n\tseen := make(map[int]bool)\n\tcounts := make([]int, 0, 5)\n\tadd := func(count int) {\n\t\tif count >= 2 && count <= maxParts && !seen[count] {\n\t\t\tseen[count] = true\n\t\t\tcounts = append(counts, count)\n\t\t}\n\t}\n\n\tadd(2)\n\tadd(3)\n\tadd(4)\n\tadd(6)\n\n\tlargest := maxParts\n\tif largest > 8 {\n\t\tlargest = 8\n\t}\n\tadd(largest)\n\n\tsort.Ints(counts)\n\treturn counts\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) ([]*route.Route, error) {\n\n\tif parts == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tfullRoute, fullLogProb, fullErr := r.findRoute(total, nil)\n\tif parts == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{fullRoute}, nil\n\t}\n\n\tif fullErr == nil && fullLogProb >= math.Log(0.985) &&\n\t\tr.lastFailedShard == 0 {\n\n\t\treturn []*route.Route{fullRoute}, nil\n\t}\n\n\tbase := candidateCopyReservations(r.reserved)\n\tvar best []*route.Route\n\tbestScore := math.Inf(-1)\n\n\tif fullErr == nil {\n\t\treservations := candidateCopyReservations(base)\n\t\tcandidateReserveInto(reservations, fullRoute)\n\t\tbest = []*route.Route{fullRoute}\n\t\tbestScore = r.planScore(best, reservations, base)\n\t}\n\n\tfor _, count := range candidatePartCounts(int(parts)) {\n\t\tplan, score, ok := r.planWithParts(total, count)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif best == nil || score > bestScore {\n\t\t\tbest = plan\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif best == nil {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\t\treturn []*route.Route{fullRoute}, nil\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\tif r.attempts >= r.attemptLimit {\n\t\treturn nil, errors.New(\"routing attempt budget exhausted\")\n\t}\n\n\tr.syncReservations(inFlightHtlcs)\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.planned) != 0 {\n\t\trt := r.planned[0]\n\t\tfinalAmount := candidateFinalAmount(rt)\n\t\tif finalAmount > 0 && finalAmount <= amt {\n\t\t\tr.planned = r.planned[1:]\n\t\t\tr.attempts++\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.planned = nil\n\t}\n\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(plan) == 0 {\n\t\treturn nil, errors.New(\"empty route plan\")\n\t}\n\n\trt := plan[0]\n\tif len(plan) > 1 {\n\t\tr.planned = append([]*route.Route(nil), plan[1:]...)\n\t} else {\n\t\tr.planned = nil\n\t}\n\n\tr.attempts++\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.networkKey]\n\tif memory == nil {\n\t\tmemory = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.networkKey] = memory\n\t}\n\tmemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tif failure, ok := r.currentFails[key]; ok &&\n\t\tfailure.upper > 0 && amt >= failure.upper {\n\n\t\tdelete(r.currentFails, key)\n\t}\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\n\testimate := amt\n\tif edge := r.edges[key]; edge != nil && edge.capacity > 0 {\n\t\tratio := float64(amt) / float64(edge.capacity)\n\t\tswitch {\n\t\tcase ratio >= 0.08:\n\t\t\testimate = edge.capacity * 7 / 8\n\t\tcase ratio >= 0.035:\n\t\t\testimate = edge.capacity * 3 / 4\n\t\tdefault:\n\t\t\testimate = amt * 2\n\t\t}\n\t\tif estimate > edge.capacity {\n\t\t\testimate = edge.capacity\n\t\t}\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.successes++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) learnFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tif amt <= 0 {\n\t\treturn\n\t}\n\n\tcurrent := r.currentFails[key]\n\tif current.upper == 0 || amt < current.upper {\n\t\tcurrent.upper = amt\n\t}\n\tcurrent.count++\n\tr.currentFails[key] = current\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\tbelief.upperFail = amt\n\t}\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\testimate := amt / 3\n\tif edge := r.edges[key]; edge != nil {\n\t\tlowEstimate := edge.capacity / 18\n\t\tif estimate > lowEstimate {\n\t\t\testimate = lowEstimate\n\t\t}\n\t}\n\tif estimate <= 0 {\n\t\testimate = 1\n\t}\n\tif belief.estimate == 0 || belief.estimate >= amt ||\n\t\testimate < belief.estimate {\n\n\t\tbelief.estimate = estimate\n\t}\n\n\tbelief.failures++\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) markRouteSuspect(rt *route.Route,\n\tweight uint32) {\n\n\tif rt == nil {\n\t\treturn\n\t}\n\tfor i := range rt.Hops {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif key.from == r.source && len(rt.Hops) > 1 {\n\t\t\tcontinue\n\t\t}\n\t\tr.suspect[key] += weight\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t\tif r.suspect[key] > 0 {\n\t\t\t\tr.suspect[key]--\n\t\t\t}\n\t\t}\n\n\t\tr.held = append(r.held, rt)\n\t\tr.lastFailedShard = 0\n\t\treturn nil\n\t}\n\n\tr.planned = nil\n\n\tfailureIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailureIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailureIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failureIndex < 0 {\n\t\tr.markRouteSuspect(rt, 6)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tpassed := failureIndex\n\tif passed > len(rt.Hops) {\n\t\tpassed = len(rt.Hops)\n\t}\n\tfor i := 0; i < passed; i++ {\n\t\tkey, ok := candidateRouteEdgeKey(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tamount, ok := candidateRouteAmount(rt, i)\n\t\tif ok {\n\t\t\tr.learnSuccess(key, amount+r.reserved[key])\n\t\t}\n\t}\n\n\tif failureIndex >= len(rt.Hops) {\n\t\tr.markRouteSuspect(rt, 3)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\tkey, ok := candidateRouteEdgeKey(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 3)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\tamount, ok := candidateRouteAmount(rt, failureIndex)\n\tif !ok {\n\t\tr.markRouteSuspect(rt, 3)\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t\treturn nil\n\t}\n\n\ttotalRequired := amount + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learnFailure(key, totalRequired)\n\t\tr.suspect[key] += 2\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBlocked[key] = true\n\t\tr.suspect[key] += 8\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\n\tdefault:\n\t\tr.suspect[key] += 5\n\t\tr.lastFailedShard = candidateFinalAmount(rt)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
}
|
|
]
|
|
} |