mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-17 13:06:14 +02:00
151 lines
No EOL
280 KiB
JSON
151 lines
No EOL
280 KiB
JSON
{
|
|
"run_id": "code_hybrid1",
|
|
"reflection_lm": "codex:gpt-5.6-sol",
|
|
"mode": "generalization",
|
|
"status": "complete",
|
|
"seed_score": 0.5278,
|
|
"best_score": 0.9789,
|
|
"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": "rejected"
|
|
}
|
|
],
|
|
"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\"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}"
|
|
},
|
|
"stats": {
|
|
"evals_done": 77,
|
|
"distinct_candidates": 8
|
|
},
|
|
"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": 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\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}"
|
|
}
|
|
}
|
|
]
|
|
} |