lnd/simulation/command-center/data/run.json
Olaoluwa Osuntokun c811978d12 command-center: point run telemetry at code_deg1
In this commit, we swap the live-run panel's lineage data from the
exp-018 gepa arm to code_deg1, the most recently completed run, so
the telemetry matches the runs the page now discusses.
2026-07-27 21:53:48 -07:00

616 lines
No EOL
964 KiB
JSON

{
"run_id": "code_deg1",
"reflection_lm": "codex:gpt-5.6-sol",
"mode": "generalization",
"status": "complete",
"seed_score": 0.4517,
"best_score": 0.9962,
"iterations": [
{
"i": 0,
"candidate_score": 0.4517,
"best_score": 0.4517,
"note": "seed"
},
{
"i": 1,
"candidate_score": 0.0,
"best_score": 0.4517,
"note": "rejected"
},
{
"i": 2,
"candidate_score": 0.4493,
"best_score": 0.4517,
"note": "accepted"
},
{
"i": 3,
"candidate_score": 0.4776,
"best_score": 0.4776,
"note": "accepted"
},
{
"i": 4,
"candidate_score": 0.551,
"best_score": 0.551,
"note": "accepted"
},
{
"i": 5,
"candidate_score": 0.0,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 6,
"candidate_score": 0.5458,
"best_score": 0.551,
"note": "accepted"
},
{
"i": 7,
"candidate_score": 0.5632,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 8,
"candidate_score": 0.1123,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 9,
"candidate_score": 0.4415,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 10,
"candidate_score": 0.1461,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 11,
"candidate_score": 0.5773,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 12,
"candidate_score": 0.2315,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 13,
"candidate_score": 0.6289,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 14,
"candidate_score": 0.3639,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 15,
"candidate_score": 0.3926,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 16,
"candidate_score": 0.5653,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 17,
"candidate_score": 0.1295,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 18,
"candidate_score": 0.1587,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 19,
"candidate_score": 0.5413,
"best_score": 0.551,
"note": "rejected"
},
{
"i": 20,
"candidate_score": 0.621,
"best_score": 0.621,
"note": "accepted"
},
{
"i": 21,
"candidate_score": 0.1765,
"best_score": 0.621,
"note": "rejected"
},
{
"i": 22,
"candidate_score": 0.3321,
"best_score": 0.621,
"note": "rejected"
},
{
"i": 23,
"candidate_score": 0.0,
"best_score": 0.621,
"note": "rejected"
},
{
"i": 24,
"candidate_score": 0.6102,
"best_score": 0.621,
"note": "rejected"
},
{
"i": 25,
"candidate_score": 0.0,
"best_score": 0.621,
"note": "rejected"
},
{
"i": 26,
"candidate_score": 0.6693,
"best_score": 0.6693,
"note": "accepted"
},
{
"i": 27,
"candidate_score": 0.4464,
"best_score": 0.6693,
"note": "rejected"
},
{
"i": 28,
"candidate_score": 0.1714,
"best_score": 0.6693,
"note": "rejected"
},
{
"i": 29,
"candidate_score": 0.4733,
"best_score": 0.6693,
"note": "rejected"
},
{
"i": 30,
"candidate_score": 0.0,
"best_score": 0.6693,
"note": "rejected"
},
{
"i": 31,
"candidate_score": 0.3944,
"best_score": 0.6693,
"note": "rejected"
},
{
"i": 32,
"candidate_score": 0.4525,
"best_score": 0.6693,
"note": "accepted"
},
{
"i": 33,
"candidate_score": 0.0,
"best_score": 0.6693,
"note": "rejected"
},
{
"i": 34,
"candidate_score": 0.5738,
"best_score": 0.6693,
"note": "accepted"
},
{
"i": 35,
"candidate_score": 0.2446,
"best_score": 0.6693,
"note": "rejected"
},
{
"i": 36,
"candidate_score": 0.3071,
"best_score": 0.6693,
"note": "accepted"
}
],
"seed_params": {
"source": "package main\n\n// This file is the CANDIDATE SLOT for evolved routing algorithms. During\n// optimization, the entire file is replaced (via go build -overlay) with a\n// generated implementation. The contract is a single constructor:\n//\n//\tnewCandidateRouter(view, source, localBalances, spec)\n//\n// returning a routing.SimRouter. The router sees only the public gossip\n// graph, its own channel balances and per-attempt feedback \u2014 the same\n// information a real Lightning sender has. The in-tree implementation below\n// is the seed algorithm: a deliberately simple fee-optimizing Dijkstra with\n// failure blacklisting and halving-based MPP splitting.\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\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\n// candidateEdge is one directed edge of the public graph: a channel from\n// one node to another, with the policy the sending node announced.\ntype candidateEdge struct {\n\tchanID uint64\n\tfrom, to route.Vertex\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\n// fee returns the fee the sending node charges to forward amt over this\n// edge.\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\n// usable reports whether the edge can carry the given amount per its\n// announced policy.\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\t// The public capacity is a hard upper bound on what can flow.\n\treturn amt <= e.capacity\n}\n\n// candidateRouter is the seed algorithm: cheapest-path routing with a\n// failure blacklist and amount halving when no route is found.\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\t// incomingEdges maps a node to the directed edges arriving at it,\n\t// the natural shape for backward Dijkstra.\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\n\t// localBalances is the exact outbound liquidity of our own channels.\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// failedAmt records, per directed channel, the lowest amount that\n\t// failed with a liquidity error; routes are built to stay below it.\n\tfailedAmt map[uint64]lnwire.MilliSatoshi\n\n\t// shardAmt is the current shard size for MPP splitting.\n\tshardAmt lnwire.MilliSatoshi\n\n\t// partsUsed counts the successful shards so far.\n\tpartsUsed uint32\n\n\t// pending maps in-flight attempt ids to their routes.\n\tpending map[uint64]*route.Route\n}\n\n// newCandidateRouter builds the router for one payment. This signature is\n// the stable contract between the harness and generated candidates.\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\trouter := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tfailedAmt: make(map[uint64]lnwire.MilliSatoshi),\n\t\tshardAmt: spec.Amount,\n\t\tpending: make(map[uint64]*route.Route),\n\t}\n\n\t// Build the adjacency list from gossip. Iterating a node's channels\n\t// yields, per channel, the policy the OTHER node announced toward us\n\t// (InPolicy). That is exactly the policy governing the directed edge\n\t// other -> node, so we record the reversed edge at each visit.\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(ctx, node,\n\t\t\tfunc(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\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\trouter.incomingEdges[edge.to] = append(\n\t\t\t\t\trouter.incomingEdges[edge.to], edge,\n\t\t\t\t)\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\treturn router, nil\n}\n\n// dijkstraItem is a priority queue entry.\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tcost lnwire.MilliSatoshi\n\tidx int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int { return len(q) }\nfunc (q dijkstraQueue) Less(i, j int) bool { return q[i].cost < q[j].cost }\nfunc (q dijkstraQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i]; q[i].idx = i; q[j].idx = j }\nfunc (q *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *dijkstraQueue) 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\n// findRoute computes the cheapest usable path delivering amt to the target,\n// walking backward from the target so fees accumulate correctly.\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (*route.Route,\n\terror) {\n\n\t// dist[node] = amount that must arrive at node to deliver amt.\n\tdist := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = amt\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{node: r.spec.Target, cost: amt})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode, arriving := item.node, item.cost\n\n\t\tif arriving > dist[node] {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\t// Consider all edges INTO node: for edge u->node, u must\n\t\t// send arriving plus u's fee.\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tamtOver := arriving\n\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Skip channels whose liquidity failure bound says\n\t\t\t// this amount cannot pass.\n\t\t\tif bound, ok := r.failedAmt[edge.chanID]; ok &&\n\t\t\t\tamtOver >= bound {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Our own channels: check exact local balance.\n\t\t\tif edge.from == r.source {\n\t\t\t\tif r.localBalances[edge.chanID] < amtOver {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar sending lnwire.MilliSatoshi\n\t\t\tif edge.from == r.source {\n\t\t\t\t// We pay no fee to ourselves.\n\t\t\t\tsending = amtOver\n\t\t\t} else {\n\t\t\t\tsending = amtOver + edge.fee(amtOver)\n\t\t\t}\n\n\t\t\tbest, ok := dist[edge.from]\n\t\t\tif !ok || sending < best {\n\t\t\t\tdist[edge.from] = sending\n\t\t\t\tnext[edge.from] = edge\n\t\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\t\tnode: edge.from,\n\t\t\t\t\tcost: sending,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tif _, ok := dist[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\n// buildRoute walks the next-pointers from source to target and constructs a\n// route with correctly accumulated fees and cltv deltas.\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tconst finalCltvDelta = 40\n\n\t// Collect the path edges source -> target.\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\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.to\n\t}\n\n\t// Amounts and expiries per channel, computed backward.\n\tnumHops := len(path)\n\tamtOver := make([]lnwire.MilliSatoshi, numHops)\n\texpiryOver := make([]uint32, numHops)\n\n\tamtOver[numHops-1] = amt\n\texpiryOver[numHops-1] = finalCltvDelta\n\n\tfor i := numHops - 2; i >= 0; i-- {\n\t\tfwd := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] + fwd.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(fwd.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, numHops)\n\tfor i, edge := range path {\n\t\tamtToFwd := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < numHops-1 {\n\t\t\tamtToFwd = 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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: amtToFwd,\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\n// RequestRoute returns the next route to try: the cheapest path for the\n// current shard size, halving the shard when no route exists.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif r.shardAmt > amt {\n\t\tr.shardAmt = amt\n\t}\n\n\tfor {\n\t\trt, err := r.findRoute(r.shardAmt)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\t// No route at this shard size: split if we're allowed more\n\t\t// parts and the shard is still meaningfully large.\n\t\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\t\tif partsLeft <= 1 || r.shardAmt < 10_000_000 {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.shardAmt /= 2\n\t}\n}\n\n// ReportAttempt learns from an attempt: liquidity failures set an upper\n// bound on the failing channel.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\treturn nil\n\t}\n\n\t// Locate the failing hop and record the amount bound on its\n\t// outgoing channel.\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\tfailIdx = i + 1\n\t\t}\n\t}\n\n\t// The failing node could not forward over its outgoing channel,\n\t// which is rt.Hops[failIdx].\n\tif failIdx >= 0 && failIdx < len(rt.Hops) {\n\t\thop := rt.Hops[failIdx]\n\t\tamtOver := rt.TotalAmount\n\t\tif failIdx > 0 {\n\t\t\tamtOver = rt.Hops[failIdx-1].AmtToForward\n\t\t}\n\n\t\tbound, ok := r.failedAmt[hop.ChannelID]\n\t\tif !ok || amtOver < bound {\n\t\t\tr.failedAmt[hop.ChannelID] = amtOver\n\t\t}\n\t}\n\n\treturn nil\n}\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 (\n\tfinalCltvDelta = 40\n\triskWeight = 2_500_000.0\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n\tcap lnwire.MilliSatoshi\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupper lnwire.MilliSatoshi\n\n\testimate lnwire.MilliSatoshi\n\tconfidence float64\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tfailVotes uint8\n\tlastFailPath uint64\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity\n}\n\ntype edgeUse struct {\n\tedge *candidateEdge\n\tamount lnwire.MilliSatoshi\n}\n\ntype searchResult struct {\n\trt *route.Route\n\tpath []*candidateEdge\n\tuses []edgeUse\n\tlogProb float64\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\tresult *searchResult\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\t// reserved includes successful shards and routes returned but not yet\n\t// reported. This prevents atomic MPP siblings from double-spending a\n\t// corridor.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route][]edgeUse\n\n\t// penalty is payment-local. Unlike shared beliefs, it can react\n\t// aggressively without permanently poisoning later payments.\n\tpenalty map[edgeKey]float64\n\n\t// tried records the smallest failed delivery amount for an exact path.\n\t// The same path remains eligible at a lower amount.\n\ttried map[uint64]lnwire.MilliSatoshi\n\n\tplan []plannedRoute\n\tretryCeiling lnwire.MilliSatoshi\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\tedgeByKey: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route][]edgeUse),\n\t\tpenalty: make(map[edgeKey]float64),\n\t\ttried: make(map[uint64]lnwire.MilliSatoshi),\n\t}\n\n\tfor id, balance := range localBalances {\n\t\tr.localBalances[id] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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\tcapacity := lnwire.NewMSatFromSatoshis(ch.Capacity)\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcap: capacity,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: capacity,\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[edge.to] = append(\n\t\t\t\t\tr.incomingEdges[edge.to], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeByKey[key] = edge\n\n\t\t\t\tsharedBeliefs.Lock()\n\t\t\t\tb := sharedBeliefs.m[key]\n\t\t\t\tsharedBeliefs.Unlock()\n\t\t\t\tr.beliefs[key] = b\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\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// bimodalPrior models a channel as roughly equally likely to be in a\n// depleted mode or a well-funded mode. Tiny HTLCs can pass either mode,\n// while amounts near capacity fall off a logistic cliff.\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\n\tz := (x - 0.90) / 0.045\n\tvar highMode float64\n\tswitch {\n\tcase z > 40:\n\t\thighMode = 0\n\tcase z < -40:\n\t\thighMode = 1\n\tdefault:\n\t\thighMode = 1 / (1 + math.Exp(z))\n\t}\n\n\treturn clampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) totalReserved(key edgeKey,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\ttotal := r.reserved[key]\n\tif extra != nil {\n\t\ttotal += extra[key]\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\treserved := r.totalReserved(edge.key, extra)\n\tneeded := reserved + amt\n\n\tif edge.from == r.source {\n\t\tif needed <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.995\n\t\t}\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(needed, edge.capacity)\n\tb := r.beliefs[edge.key]\n\n\tif b.lowerOK > edge.capacity {\n\t\tb.lowerOK = edge.capacity\n\t}\n\tif b.upper > edge.capacity {\n\t\tb.upper = edge.capacity\n\t}\n\tif b.estimate > edge.capacity {\n\t\tb.estimate = edge.capacity\n\t}\n\n\t// Truthful successes dominate uncertain failures.\n\tif b.lowerOK > 0 && needed <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\tif b.upper > 0 && needed >= b.upper {\n\t\treturn 0.008\n\t}\n\n\tp := prior\n\tif b.confidence > 0 && b.estimate > 0 {\n\t\tscale := math.Max(float64(edge.capacity)*0.10, 1)\n\t\tz := (float64(needed) - float64(b.estimate)) / scale\n\n\t\tvar estimated float64\n\t\tswitch {\n\t\tcase z > 40:\n\t\t\testimated = 0\n\t\tcase z < -40:\n\t\t\testimated = 1\n\t\tdefault:\n\t\t\testimated = 1 / (1 + math.Exp(z))\n\t\t}\n\n\t\tweight := math.Min(0.70, b.confidence*0.70)\n\t\tp = (1-weight)*p + weight*estimated\n\t}\n\n\t// A single uncorroborated blame is only soft evidence.\n\tif b.failVotes == 1 && b.suspectAmt > 0 &&\n\t\tneeded >= b.suspectAmt*3/4 {\n\n\t\tp *= 0.62\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) edgeAvailable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) bool {\n\n\tif !edge.usableHTLC(amt) {\n\t\treturn false\n\t}\n\n\treserved := r.totalReserved(edge.key, extra)\n\tif reserved+amt > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.from == r.source &&\n\t\treserved+amt > r.localBalances[edge.chanID] {\n\n\t\treturn false\n\t}\n\n\treturn true\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamount lnwire.MilliSatoshi\n\tindex int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int { return len(q) }\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\nfunc (q *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *dijkstraQueue) 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\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int, bias map[edgeKey]float64) (*searchResult, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tbestScore := make(map[route.Vertex]float64)\n\trequired := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tlogProbability := make(map[route.Vertex]float64)\n\n\tbestScore[r.spec.Target] = 0\n\trequired[r.spec.Target] = amt\n\tlogProbability[r.spec.Target] = 0\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamount: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode := item.node\n\n\t\tcurrent, ok := bestScore[node]\n\t\tif !ok || item.score > current+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := required[node]\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tif !r.edgeAvailable(edge, arriving, extra) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, arriving, extra)\n\t\t\tedgeFee := lnwire.MilliSatoshi(0)\n\t\t\tsending := arriving\n\t\t\tif edge.from != r.source {\n\t\t\t\tedgeFee = edge.fee(arriving)\n\t\t\t\tsending += edgeFee\n\t\t\t}\n\n\t\t\tedgeCost := float64(edgeFee) -\n\t\t\t\triskWeight*math.Log(p) + 2_000\n\n\t\t\tedgeCost += r.penalty[edge.key]\n\t\t\tif bias != nil {\n\t\t\t\tedgeCost += bias[edge.key]\n\t\t\t}\n\t\t\tif reuse != nil && reuse[edge.key] > 0 {\n\t\t\t\tedgeCost += float64(reuse[edge.key]) * 4_000_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeCost\n\t\t\told, exists := bestScore[edge.from]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.from] = score\n\t\t\trequired[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\tlogProbability[edge.from] =\n\t\t\t\tlogProbability[node] + math.Log(p)\n\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: score,\n\t\t\t\tamount: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuses := routeUses(rt, path)\n\treturn &searchResult{\n\t\trt: rt,\n\t\tpath: path,\n\t\tuses: uses,\n\t\tlogProb: logProbability[r.source],\n\t\tfee: rt.TotalAmount - amt,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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\tnextEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tnextEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\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}, path, nil\n}\n\nfunc routeUses(rt *route.Route, path []*candidateEdge) []edgeUse {\n\tuses := make([]edgeUse, len(path))\n\tfor i, edge := range path {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tuses[i] = edgeUse{edge: edge, amount: amount}\n\t}\n\treturn uses\n}\n\nfunc deliveredAmount(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 routeHash(rt *route.Route) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, hop := range rt.Hops {\n\t\th ^= hop.ChannelID\n\t\th *= 1099511628211\n\t\tfor _, b := range hop.PubKeyBytes {\n\t\t\th ^= uint64(b)\n\t\t\th *= 1099511628211\n\t\t}\n\t}\n\treturn h\n}\n\nfunc (r *candidateRouter) findFreshRoute(amt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int) (*searchResult, error) {\n\n\tbias := make(map[edgeKey]float64)\n\tvar lastErr error\n\n\tfor n := 0; n < 10; n++ {\n\t\tresult, err := r.findRoute(amt, extra, reuse, bias)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thash := routeHash(result.rt)\n\t\tfailedAt, failed := r.tried[hash]\n\t\tif !failed || amt < failedAt {\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = errors.New(\"all candidate routes already failed\")\n\t\tif len(result.path) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// Exclude a different edge on successive duplicate paths. This\n\t\t// uses only payment-local information and does not turn an\n\t\t// unreadable onion error into a permanent channel accusation.\n\t\tindex := int((hash + uint64(n)) % uint64(len(result.path)))\n\t\tbias[result.path[index].key] += 7_000_000\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no fresh route found\")\n\t}\n\treturn nil, lastErr\n}\n\nfunc (r *candidateRouter) reliableDeliveryLimit(result *searchResult,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tdelivery := deliveredAmount(result.rt)\n\tlimit := delivery * 8\n\tif limit < delivery {\n\t\tlimit = delivery\n\t}\n\n\tfor _, use := range result.uses {\n\t\tedge := use.edge\n\t\treserved := r.totalReserved(edge.key, extra)\n\n\t\tvar safeTotal lnwire.MilliSatoshi\n\t\tif edge.from == r.source {\n\t\t\tsafeTotal = r.localBalances[edge.chanID]\n\t\t} else {\n\t\t\tb := r.beliefs[edge.key]\n\t\t\tsafeTotal = edge.capacity * 78 / 100\n\n\t\t\tif b.estimate > 0 && b.confidence > 0 {\n\t\t\t\tw := math.Min(0.85, b.confidence)\n\t\t\t\tsafeTotal = lnwire.MilliSatoshi(\n\t\t\t\t\t(1-w)*float64(safeTotal) +\n\t\t\t\t\t\tw*float64(b.estimate),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif b.lowerOK > safeTotal {\n\t\t\t\tsafeTotal = b.lowerOK\n\t\t\t}\n\t\t\tif b.upper > 0 && safeTotal >= b.upper {\n\t\t\t\tsafeTotal = b.upper * 9 / 10\n\t\t\t}\n\t\t}\n\n\t\tif safeTotal > edge.capacity {\n\t\t\tsafeTotal = edge.capacity\n\t\t}\n\t\tif safeTotal <= reserved {\n\t\t\treturn 0\n\t\t}\n\n\t\tavailable := safeTotal - reserved\n\t\tedgeLimit := delivery * available / use.amount\n\n\t\tif edge.maxHTLC != 0 && use.amount > 0 {\n\t\t\tpolicyLimit := delivery * edge.maxHTLC / use.amount\n\t\t\tif policyLimit < edgeLimit {\n\t\t\t\tedgeLimit = policyLimit\n\t\t\t}\n\t\t}\n\t\tif edgeLimit < limit {\n\t\t\tlimit = edgeLimit\n\t\t}\n\t}\n\n\treturn limit\n}\n\nfunc addReservations(dst map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tdst[use.edge.key] += use.amount\n\t}\n}\n\nfunc planMetric(results []*searchResult) float64 {\n\tmetric := -0.04 * float64(len(results))\n\tfor _, result := range results {\n\t\tmetric += result.logProb\n\t\tmetric -= float64(result.fee) / 5_000_000\n\t}\n\treturn metric\n}\n\nfunc (r *candidateRouter) buildPlanForParts(amt lnwire.MilliSatoshi,\n\tparts int) ([]*searchResult, bool) {\n\n\tremaining := amt\n\tvirtual := make(map[edgeKey]lnwire.MilliSatoshi)\n\treuse := make(map[edgeKey]int)\n\tresults := make([]*searchResult, 0, parts)\n\n\tfor i := 0; i < parts; i++ {\n\t\tslots := parts - i\n\t\tif remaining <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tshare := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tresult, err := r.findFreshRoute(share, virtual, reuse)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tsafe := r.reliableDeliveryLimit(result, virtual)\n\t\tif safe < share {\n\t\t\tif slots == 1 || safe <= 0 {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tshare = safe\n\t\t\tresult, err = r.findFreshRoute(share, virtual, reuse)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t}\n\n\t\tif share <= 0 || share >= remaining && slots > 1 {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tresults = append(results, result)\n\t\taddReservations(virtual, result.uses)\n\t\tfor _, use := range result.uses {\n\t\t\treuse[use.edge.key]++\n\t\t}\n\t\tremaining -= share\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, false\n\t}\n\treturn results, true\n}\n\n// preparePlan compares a direct attempt with jointly constructed route sets.\n// The set planner accounts for all earlier shards as virtual reservations,\n// so atomic siblings cannot unknowingly depend on the same liquidity twice.\nfunc (r *candidateRouter) preparePlan(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) error {\n\n\tr.plan = nil\n\tif partsLeft == 0 {\n\t\treturn errors.New(\"maximum in-flight parts reached\")\n\t}\n\n\tvar best []*searchResult\n\tbestMetric := math.Inf(-1)\n\n\tdirect, directErr := r.findFreshRoute(amt, nil, nil)\n\tif directErr == nil {\n\t\tbest = []*searchResult{direct}\n\t\tbestMetric = planMetric(best)\n\t}\n\n\tmaxParts := int(partsLeft)\n\tif maxParts > 16 {\n\t\tmaxParts = 16\n\t}\n\n\tfor parts := 2; parts <= maxParts; parts++ {\n\t\tresults, ok := r.buildPlanForParts(amt, parts)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tmetric := planMetric(results)\n\t\tif metric > bestMetric+0.08 {\n\t\t\tbest = results\n\t\t\tbestMetric = metric\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\tif directErr != nil {\n\t\t\treturn directErr\n\t\t}\n\t\treturn errors.New(\"no route set found\")\n\t}\n\n\tr.plan = make([]plannedRoute, len(best))\n\tfor i, result := range best {\n\t\tr.plan[i] = plannedRoute{result: result}\n\t}\n\treturn nil\n}\n\nfunc (r *candidateRouter) issue(result *searchResult) *route.Route {\n\taddReservations(r.reserved, result.uses)\n\tr.issued[result.rt] = result.uses\n\treturn result.rt\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum in-flight parts reached\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 {\n\t\tresult := r.plan[0].result\n\t\tif deliveredAmount(result.rt) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tif err := r.preparePlan(amt, partsLeft); err == nil &&\n\t\tlen(r.plan) > 0 {\n\n\t\tresult := r.plan[0].result\n\t\tr.plan = r.plan[1:]\n\t\treturn r.issue(result), nil\n\t}\n\n\t// If joint planning cannot complete the amount, retain a conservative\n\t// lower-amount escape hatch. It never proposes a shard so small that\n\t// the currently available atomic slots could not finish the payment.\n\tif partsLeft <= 1 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tminShard := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\ttryAmt := amt\n\tif r.retryCeiling > 0 && r.retryCeiling < tryAmt {\n\t\ttryAmt = r.retryCeiling\n\t}\n\tif tryAmt < minShard {\n\t\ttryAmt = minShard\n\t}\n\n\tfor tryAmt >= minShard {\n\t\tresult, err := r.findFreshRoute(tryAmt, nil, nil)\n\t\tif err == nil {\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t\tif tryAmt == minShard {\n\t\t\tbreak\n\t\t}\n\n\t\tnext := tryAmt * 3 / 5\n\t\tif next < minShard {\n\t\t\tnext = minShard\n\t\t}\n\t\tif next == tryAmt {\n\t\t\tbreak\n\t\t}\n\t\ttryAmt = next\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tb liquidityBelief) {\n\n\tr.beliefs[key] = b\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = b\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(use edgeUse,\n\ttotalObserved lnwire.MilliSatoshi) {\n\n\tkey := use.edge.key\n\tb := r.beliefs[key]\n\n\tif totalObserved > b.lowerOK {\n\t\tb.lowerOK = totalObserved\n\t}\n\tif totalObserved > b.estimate {\n\t\tb.estimate = totalObserved\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.35)\n\n\t// A success is truthful and therefore disproves any ceiling at or\n\t// below the amount that just passed.\n\tif b.upper > 0 && totalObserved >= b.upper {\n\t\tb.upper = 0\n\t}\n\tif b.suspectAmt > 0 && totalObserved >= b.suspectAmt {\n\t\tb.suspectAmt = 0\n\t\tb.failVotes = 0\n\t\tb.lastFailPath = 0\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) learnFailure(edge *candidateEdge,\n\ttotalObserved lnwire.MilliSatoshi, pathHash uint64) {\n\n\tkey := edge.key\n\tb := r.beliefs[key]\n\n\tif b.suspectAmt == 0 ||\n\t\ttotalObserved < b.suspectAmt/2 ||\n\t\ttotalObserved > b.suspectAmt*2 {\n\n\t\tb.suspectAmt = totalObserved\n\t\tb.failVotes = 1\n\t\tb.lastFailPath = pathHash\n\t} else {\n\t\tif totalObserved < b.suspectAmt {\n\t\t\tb.suspectAmt = totalObserved\n\t\t}\n\t\t// Repeated blame on the identical route does not corroborate the\n\t\t// reported hop. A second, distinct route does.\n\t\tif pathHash != b.lastFailPath && b.failVotes < 255 {\n\t\t\tb.failVotes++\n\t\t\tb.lastFailPath = pathHash\n\t\t}\n\t}\n\n\tfailedEstimate := totalObserved * 3 / 4\n\tif b.estimate == 0 {\n\t\tb.estimate = failedEstimate\n\t} else {\n\t\tb.estimate = (b.estimate*3 + failedEstimate) / 4\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.15)\n\n\tif b.failVotes >= 2 {\n\t\tif b.upper == 0 || b.suspectAmt < b.upper {\n\t\t\tb.upper = b.suspectAmt\n\t\t}\n\t\tif b.lowerOK >= b.upper {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc releaseReservations(reserved map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tcurrent := reserved[use.edge.key]\n\t\tif current <= use.amount {\n\t\t\tdelete(reserved, use.edge.key)\n\t\t} else {\n\t\t\treserved[use.edge.key] = current - use.amount\n\t\t}\n\t}\n}\n\nfunc findFailureNode(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tuses, ok := r.issued[rt]\n\tif !ok {\n\t\t// The simulator normally returns the same route pointer. Rebuild\n\t\t// the edge uses defensively if it returns an equivalent route.\n\t\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\t\tfrom := rt.SourcePubKey\n\t\tfor _, hop := range rt.Hops {\n\t\t\tvar found *candidateEdge\n\t\t\tfor _, edge := range r.incomingEdges[hop.PubKeyBytes] {\n\t\t\t\tif edge.from == from && edge.chanID == hop.ChannelID {\n\t\t\t\t\tfound = edge\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif found == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tpath = append(path, found)\n\t\t\tfrom = hop.PubKeyBytes\n\t\t}\n\t\tif len(path) == len(rt.Hops) {\n\t\t\tuses = routeUses(rt, path)\n\t\t}\n\t}\n\tdelete(r.issued, rt)\n\n\tif result.Failure == nil {\n\t\tfor _, use := range uses {\n\t\t\t// reserved includes this shard and any prior successful\n\t\t\t// siblings, which is the total liquidity proven to have\n\t\t\t// existed before those reservations.\n\t\t\tr.learnSuccess(use, r.reserved[use.edge.key])\n\t\t}\n\t\treturn nil\n\t}\n\n\thash := routeHash(rt)\n\tdelivered := deliveredAmount(rt)\n\tif old, exists := r.tried[hash]; !exists || delivered < old {\n\t\tr.tried[hash] = delivered\n\t}\n\n\t// Capture total demand before releasing this failed attempt.\n\ttotalDemand := make(map[edgeKey]lnwire.MilliSatoshi, len(uses))\n\tfor _, use := range uses {\n\t\ttotalDemand[use.edge.key] = r.reserved[use.edge.key]\n\t}\n\treleaseReservations(r.reserved, uses)\n\tr.plan = nil\n\n\tnextCeiling := delivered * 3 / 5\n\tif nextCeiling > 0 &&\n\t\t(r.retryCeiling == 0 || nextCeiling < r.retryCeiling) {\n\n\t\tr.retryCeiling = nextCeiling\n\t}\n\n\tfailNode := findFailureNode(rt, result.FailureSource)\n\tif failNode < 0 {\n\t\t// Unreadable attribution is not written to the persistent\n\t\t// channel store. The exact failed route is still avoided at this\n\t\t// amount through r.tried.\n\t\treturn nil\n\t}\n\n\tcode := result.Failure.Code()\n\tisLiquidity := code == lnwire.CodeTemporaryChannelFailure\n\n\t// A node failure refers to its outgoing channel, whose hop index is\n\t// the node index. The target has no outgoing channel.\n\tif failNode >= len(uses) {\n\t\treturn nil\n\t}\n\n\tprimary := failNode\n\tif isLiquidity {\n\t\tuse := uses[primary]\n\t\tr.penalty[use.edge.key] += 5_000_000\n\t\tr.learnFailure(\n\t\t\tuse.edge, totalDemand[use.edge.key], hash,\n\t\t)\n\n\t\t// Shifted attribution is plausible. Adjacent edges receive only\n\t\t// payment-local soft penalties; they never receive persistent\n\t\t// hard bounds without being reported directly and corroborated.\n\t\tif primary > 0 {\n\t\t\tr.penalty[uses[primary-1].edge.key] += 900_000\n\t\t}\n\t\tif primary+1 < len(uses) {\n\t\t\tr.penalty[uses[primary+1].edge.key] += 900_000\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Policy failures cannot safely establish a liquidity ceiling,\n\t// especially when the reported source may have shifted.\n\tr.penalty[uses[primary].edge.key] += 8_000_000\n\tif primary > 0 {\n\t\tr.penalty[uses[primary-1].edge.key] += 1_000_000\n\t}\n\tif primary+1 < len(uses) {\n\t\tr.penalty[uses[primary+1].edge.key] += 1_000_000\n\t}\n\n\treturn nil\n}"
},
"stats": {
"evals_done": 384,
"distinct_candidates": 37
},
"candidates": [
{
"id": 0,
"parent": null,
"score": 0.4517,
"accepted": true,
"frontier": true,
"role": "seed",
"params": {
"source": "package main\n\n// This file is the CANDIDATE SLOT for evolved routing algorithms. During\n// optimization, the entire file is replaced (via go build -overlay) with a\n// generated implementation. The contract is a single constructor:\n//\n//\tnewCandidateRouter(view, source, localBalances, spec)\n//\n// returning a routing.SimRouter. The router sees only the public gossip\n// graph, its own channel balances and per-attempt feedback \u2014 the same\n// information a real Lightning sender has. The in-tree implementation below\n// is the seed algorithm: a deliberately simple fee-optimizing Dijkstra with\n// failure blacklisting and halving-based MPP splitting.\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\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\n// candidateEdge is one directed edge of the public graph: a channel from\n// one node to another, with the policy the sending node announced.\ntype candidateEdge struct {\n\tchanID uint64\n\tfrom, to route.Vertex\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\n// fee returns the fee the sending node charges to forward amt over this\n// edge.\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\n// usable reports whether the edge can carry the given amount per its\n// announced policy.\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\t// The public capacity is a hard upper bound on what can flow.\n\treturn amt <= e.capacity\n}\n\n// candidateRouter is the seed algorithm: cheapest-path routing with a\n// failure blacklist and amount halving when no route is found.\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\t// incomingEdges maps a node to the directed edges arriving at it,\n\t// the natural shape for backward Dijkstra.\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\n\t// localBalances is the exact outbound liquidity of our own channels.\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\t// failedAmt records, per directed channel, the lowest amount that\n\t// failed with a liquidity error; routes are built to stay below it.\n\tfailedAmt map[uint64]lnwire.MilliSatoshi\n\n\t// shardAmt is the current shard size for MPP splitting.\n\tshardAmt lnwire.MilliSatoshi\n\n\t// partsUsed counts the successful shards so far.\n\tpartsUsed uint32\n\n\t// pending maps in-flight attempt ids to their routes.\n\tpending map[uint64]*route.Route\n}\n\n// newCandidateRouter builds the router for one payment. This signature is\n// the stable contract between the harness and generated candidates.\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\trouter := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tfailedAmt: make(map[uint64]lnwire.MilliSatoshi),\n\t\tshardAmt: spec.Amount,\n\t\tpending: make(map[uint64]*route.Route),\n\t}\n\n\t// Build the adjacency list from gossip. Iterating a node's channels\n\t// yields, per channel, the policy the OTHER node announced toward us\n\t// (InPolicy). That is exactly the policy governing the directed edge\n\t// other -> node, so we record the reversed edge at each visit.\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\n\n\tfor len(queue) > 0 {\n\t\tnode := queue[0]\n\t\tqueue = queue[1:]\n\n\t\terr := view.ForEachNodeDirectedChannel(ctx, node,\n\t\t\tfunc(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\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\trouter.incomingEdges[edge.to] = append(\n\t\t\t\t\trouter.incomingEdges[edge.to], edge,\n\t\t\t\t)\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\treturn router, nil\n}\n\n// dijkstraItem is a priority queue entry.\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tcost lnwire.MilliSatoshi\n\tidx int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int { return len(q) }\nfunc (q dijkstraQueue) Less(i, j int) bool { return q[i].cost < q[j].cost }\nfunc (q dijkstraQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i]; q[i].idx = i; q[j].idx = j }\nfunc (q *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.idx = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *dijkstraQueue) 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\n// findRoute computes the cheapest usable path delivering amt to the target,\n// walking backward from the target so fees accumulate correctly.\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi) (*route.Route,\n\terror) {\n\n\t// dist[node] = amount that must arrive at node to deliver amt.\n\tdist := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = amt\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{node: r.spec.Target, cost: amt})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode, arriving := item.node, item.cost\n\n\t\tif arriving > dist[node] {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\t// Consider all edges INTO node: for edge u->node, u must\n\t\t// send arriving plus u's fee.\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tamtOver := arriving\n\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Skip channels whose liquidity failure bound says\n\t\t\t// this amount cannot pass.\n\t\t\tif bound, ok := r.failedAmt[edge.chanID]; ok &&\n\t\t\t\tamtOver >= bound {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Our own channels: check exact local balance.\n\t\t\tif edge.from == r.source {\n\t\t\t\tif r.localBalances[edge.chanID] < amtOver {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar sending lnwire.MilliSatoshi\n\t\t\tif edge.from == r.source {\n\t\t\t\t// We pay no fee to ourselves.\n\t\t\t\tsending = amtOver\n\t\t\t} else {\n\t\t\t\tsending = amtOver + edge.fee(amtOver)\n\t\t\t}\n\n\t\t\tbest, ok := dist[edge.from]\n\t\t\tif !ok || sending < best {\n\t\t\t\tdist[edge.from] = sending\n\t\t\t\tnext[edge.from] = edge\n\t\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\t\tnode: edge.from,\n\t\t\t\t\tcost: sending,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tif _, ok := dist[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\n}\n\n// buildRoute walks the next-pointers from source to target and constructs a\n// route with correctly accumulated fees and cltv deltas.\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tconst finalCltvDelta = 40\n\n\t// Collect the path edges source -> target.\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\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.to\n\t}\n\n\t// Amounts and expiries per channel, computed backward.\n\tnumHops := len(path)\n\tamtOver := make([]lnwire.MilliSatoshi, numHops)\n\texpiryOver := make([]uint32, numHops)\n\n\tamtOver[numHops-1] = amt\n\texpiryOver[numHops-1] = finalCltvDelta\n\n\tfor i := numHops - 2; i >= 0; i-- {\n\t\tfwd := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] + fwd.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(fwd.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, numHops)\n\tfor i, edge := range path {\n\t\tamtToFwd := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < numHops-1 {\n\t\t\tamtToFwd = 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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: amtToFwd,\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\n// RequestRoute returns the next route to try: the cheapest path for the\n// current shard size, halving the shard when no route exists.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif r.shardAmt > amt {\n\t\tr.shardAmt = amt\n\t}\n\n\tfor {\n\t\trt, err := r.findRoute(r.shardAmt)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\t// No route at this shard size: split if we're allowed more\n\t\t// parts and the shard is still meaningfully large.\n\t\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\t\tif partsLeft <= 1 || r.shardAmt < 10_000_000 {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.shardAmt /= 2\n\t}\n}\n\n// ReportAttempt learns from an attempt: liquidity failures set an upper\n// bound on the failing channel.\n//\n// NOTE: Part of the routing.SimRouter interface.\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\treturn nil\n\t}\n\n\t// Locate the failing hop and record the amount bound on its\n\t// outgoing channel.\n\tfailIdx := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\tfailIdx = i + 1\n\t\t}\n\t}\n\n\t// The failing node could not forward over its outgoing channel,\n\t// which is rt.Hops[failIdx].\n\tif failIdx >= 0 && failIdx < len(rt.Hops) {\n\t\thop := rt.Hops[failIdx]\n\t\tamtOver := rt.TotalAmount\n\t\tif failIdx > 0 {\n\t\t\tamtOver = rt.Hops[failIdx-1].AmtToForward\n\t\t}\n\n\t\tbound, ok := r.failedAmt[hop.ChannelID]\n\t\tif !ok || amtOver < bound {\n\t\t\tr.failedAmt[hop.ChannelID] = amtOver\n\t\t}\n\t}\n\n\treturn nil\n}\n"
}
},
{
"id": 1,
"parent": 0,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskWeight = 0.012\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\n\tupperFail lnwire.MilliSatoshi\n\n\tsuspectUpper lnwire.MilliSatoshi\n\tfailVotes uint8\n}\n\ntype sharedKnowledge struct {\n\tmu sync.RWMutex\n\tbeliefs map[candidateEdgeKey]*liquidityBelief\n}\n\ntype knowledgeSlot struct {\n\tview routing.SimNetworkView\n\tknowledge *sharedKnowledge\n}\n\nvar candidateKnowledgeRegistry = struct {\n\tsync.Mutex\n\tslots map[uintptr]*knowledgeSlot\n}{\n\tslots: make(map[uintptr]*knowledgeSlot),\n}\n\nfunc knowledgeForView(view routing.SimNetworkView) *sharedKnowledge {\n\tvalue := reflect.ValueOf(view)\n\tfor value.IsValid() && value.Kind() == reflect.Interface {\n\t\tvalue = value.Elem()\n\t}\n\n\tvar identity uintptr\n\tif value.IsValid() {\n\t\tswitch value.Kind() {\n\t\tcase reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr,\n\t\t\treflect.Slice, reflect.UnsafePointer:\n\n\t\t\tidentity = value.Pointer()\n\t\t}\n\t}\n\n\tif identity == 0 {\n\t\treturn &sharedKnowledge{\n\t\t\tbeliefs: make(map[candidateEdgeKey]*liquidityBelief),\n\t\t}\n\t}\n\n\tcandidateKnowledgeRegistry.Lock()\n\tdefer candidateKnowledgeRegistry.Unlock()\n\n\tif slot, ok := candidateKnowledgeRegistry.slots[identity]; ok {\n\t\treturn slot.knowledge\n\t}\n\n\tknowledge := &sharedKnowledge{\n\t\tbeliefs: make(map[candidateEdgeKey]*liquidityBelief),\n\t}\n\tcandidateKnowledgeRegistry.slots[identity] = &knowledgeSlot{\n\t\tview: view,\n\t\tknowledge: knowledge,\n\t}\n\n\treturn knowledge\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.48 * math.Exp(-x/0.05)\n\thighMode := 0.50 / (1 + math.Exp((x-0.92)/0.035))\n\tp := lowMode + 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 (k *sharedKnowledge) probability(key candidateEdgeKey,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tk.mu.RLock()\n\tbelief := k.beliefs[key]\n\tif belief == nil {\n\t\tk.mu.RUnlock()\n\t\treturn prior\n\t}\n\tsnapshot := *belief\n\tk.mu.RUnlock()\n\n\tif snapshot.lowerOK > 0 && amt <= snapshot.lowerOK {\n\t\treturn 0.995\n\t}\n\tif snapshot.upperFail > 0 && amt >= snapshot.upperFail {\n\t\treturn 0.003\n\t}\n\n\tp := prior\n\n\tif snapshot.lowerOK > 0 && snapshot.upperFail > snapshot.lowerOK &&\n\t\tamt > snapshot.lowerOK && amt < snapshot.upperFail {\n\n\t\tposition := float64(amt-snapshot.lowerOK) /\n\t\t\tfloat64(snapshot.upperFail-snapshot.lowerOK)\n\t\tbounded := 0.97*(1-position) + 0.03*position\n\t\tp = 0.35*prior + 0.65*bounded\n\t}\n\n\tif snapshot.suspectUpper > 0 && amt >= snapshot.suspectUpper {\n\t\tp *= 0.18\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (k *sharedKnowledge) noteSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tk.mu.Lock()\n\tdefer k.mu.Unlock()\n\n\tbelief := k.beliefs[key]\n\tif belief == nil {\n\t\tbelief = &liquidityBelief{}\n\t\tk.beliefs[key] = belief\n\t}\n\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\t// Successes are trustworthy. They override any contradictory failure\n\t// attribution, including a previously corroborated but shifted blame.\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectUpper > 0 && amt >= belief.suspectUpper {\n\t\tbelief.suspectUpper = 0\n\t\tbelief.failVotes = 0\n\t}\n}\n\nfunc similarAmounts(a, b lnwire.MilliSatoshi) bool {\n\tif a <= 0 || b <= 0 {\n\t\treturn false\n\t}\n\n\tlarger, smaller := a, b\n\tif smaller > larger {\n\t\tlarger, smaller = smaller, larger\n\t}\n\n\treturn float64(smaller)/float64(larger) >= 0.70\n}\n\nfunc (k *sharedKnowledge) noteFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tk.mu.Lock()\n\tdefer k.mu.Unlock()\n\n\tbelief := k.beliefs[key]\n\tif belief == nil {\n\t\tbelief = &liquidityBelief{}\n\t\tk.beliefs[key] = belief\n\t}\n\n\t// A new failure below an old success is evidence that liquidity moved.\n\tif belief.lowerOK >= amt {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tswitch {\n\tcase belief.suspectUpper == 0:\n\t\tbelief.suspectUpper = amt\n\t\tbelief.failVotes = 1\n\n\tcase similarAmounts(belief.suspectUpper, amt):\n\t\tif amt < belief.suspectUpper {\n\t\t\tbelief.suspectUpper = amt\n\t\t}\n\t\tif belief.failVotes < 255 {\n\t\t\tbelief.failVotes++\n\t\t}\n\n\tcase amt < belief.suspectUpper:\n\t\tbelief.suspectUpper = amt\n\t\tbelief.failVotes = 1\n\t}\n\n\t// One report is only a suspect observation because failure attribution\n\t// may be shifted. A second consistent report establishes the bound.\n\tif belief.failVotes >= 2 {\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectUpper < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectUpper\n\t\t}\n\t}\n}\n\ntype edgeUse struct {\n\tkey candidateEdgeKey\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tknowledge *sharedKnowledge\n\n\t// reserved includes routes already returned during this payment. Failed\n\t// routes are released; successful routes remain reserved so atomic MPP\n\t// siblings account for liquidity held by earlier shards.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tpending map[*route.Route][]edgeUse\n\n\t// attemptPenalty promotes route diversity without turning unreadable\n\t// failures into false liquidity bounds.\n\tattemptPenalty map[candidateEdgeKey]float64\n\n\tpolicyVotes map[candidateEdgeKey]uint8\n\tpolicyBlock map[candidateEdgeKey]bool\n\n\tlastFailedShard lnwire.MilliSatoshi\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\trouter := &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\tknowledge: knowledgeForView(view),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tpending: make(map[*route.Route][]edgeUse),\n\t\tattemptPenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyVotes: make(map[candidateEdgeKey]uint8),\n\t\tpolicyBlock: make(map[candidateEdgeKey]bool),\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\trouter.incomingEdges[node] = append(\n\t\t\t\t\trouter.incomingEdges[node], edge,\n\t\t\t\t)\n\t\t\t\trouter.edges[key] = edge\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn router, nil\n}\n\ntype candidateLabel struct {\n\tscore float64\n\tamount lnwire.MilliSatoshi\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamount lnwire.MilliSatoshi\n}\n\ntype candidateQueue []*candidateQueueItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\tif q[i].score == q[j].score {\n\t\treturn q[i].amount < q[j].amount\n\t}\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateQueueItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treserved := r.reserved[edge.key]\n\trequired := amt + reserved\n\tif required < amt {\n\t\treturn 0.003\n\t}\n\n\tif required > edge.capacity {\n\t\treturn 0.003\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif r.localBalances[edge.key.chanID] < required {\n\t\t\treturn 0.003\n\t\t}\n\t\treturn 0.999\n\t}\n\n\treturn r.knowledge.probability(\n\t\tedge.key, required, edge.capacity,\n\t)\n}\n\nfunc (r *candidateRouter) edgeUsable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) bool {\n\n\tif r.policyBlock[edge.key] || !edge.policyAllows(amt) {\n\t\treturn false\n\t}\n\n\trequired := amt + r.reserved[edge.key]\n\tif required < amt || required > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.key.from == r.source &&\n\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\treturn false\n\t}\n\n\treturn true\n}\n\ntype candidateRouteChoice struct {\n\troute *route.Route\n\tpath []*candidateEdge\n\tlogProb float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tdeliver lnwire.MilliSatoshi) (*candidateRouteChoice, error) {\n\n\tif deliver <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tlabels := make(map[route.Vertex]candidateLabel)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tlabels[r.spec.Target] = candidateLabel{\n\t\tscore: 0,\n\t\tamount: deliver,\n\t}\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamount: deliver,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateQueueItem)\n\t\tlabel, ok := labels[item.node]\n\t\tif !ok || item.score > label.score+0.000001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amount\n\t\t\tif !r.edgeUsable(edge, amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\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\tif fee < 0 || sending > math.MaxInt64-fee {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, amtOver)\n\t\t\tif probability < 0.003 {\n\t\t\t\tprobability = 0.003\n\t\t\t}\n\n\t\t\triskCost := riskWeight * float64(deliver) *\n\t\t\t\t(-math.Log(probability))\n\n\t\t\t// This is deliberately a soft, payment-local diversity cost.\n\t\t\t// It prevents an unreadable failure from retrying the identical\n\t\t\t// path forever without poisoning persistent liquidity beliefs.\n\t\t\tdiversityCost := riskWeight * float64(deliver) *\n\t\t\t\tr.attemptPenalty[edge.key]\n\n\t\t\tscore := item.score + float64(fee) +\n\t\t\t\triskCost + diversityCost + 250\n\n\t\t\told, exists := labels[edge.key.from]\n\t\t\tif !exists || score < old.score {\n\t\t\t\tlabels[edge.key.from] = candidateLabel{\n\t\t\t\t\tscore: score,\n\t\t\t\t\tamount: sending,\n\t\t\t\t}\n\t\t\t\tnext[edge.key.from] = edge\n\t\t\t\theap.Push(pq, &candidateQueueItem{\n\t\t\t\t\tnode: edge.key.from,\n\t\t\t\t\tscore: score,\n\t\t\t\t\tamount: sending,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tif _, ok := labels[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\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\trt, err := r.buildRoute(deliver, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlogProb := 0.0\n\tfor index, edge := range path {\n\t\tamtOver := rt.TotalAmount\n\t\tif index > 0 {\n\t\t\tamtOver = rt.Hops[index-1].AmtToForward\n\t\t}\n\t\tlogProb += math.Log(r.edgeProbability(edge, amtOver))\n\t}\n\n\treturn &candidateRouteChoice{\n\t\troute: rt,\n\t\tpath: path,\n\t\tlogProb: logProb,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(deliver 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\tnumHops := len(path)\n\tamounts := make([]lnwire.MilliSatoshi, numHops)\n\texpiries := make([]uint32, numHops)\n\n\tamounts[numHops-1] = deliver\n\texpiries[numHops-1] = finalCltvDelta\n\n\tfor i := numHops - 2; i >= 0; i-- {\n\t\tfee := path[i+1].fee(amounts[i+1])\n\t\tif fee < 0 || amounts[i+1] > math.MaxInt64-fee {\n\t\t\treturn nil, errors.New(\"route amount overflow\")\n\t\t}\n\t\tamounts[i] = amounts[i+1] + fee\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, numHops)\n\tfor i, edge := range path {\n\t\tamountToForward := deliver\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < numHops-1 {\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 routeDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc usesForRoute(rt *route.Route) []edgeUse {\n\tuses := make([]edgeUse, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tuses = append(uses, edgeUse{\n\t\t\tkey: candidateEdgeKey{\n\t\t\t\tchanID: hop.ChannelID,\n\t\t\t\tfrom: from,\n\t\t\t\tto: hop.PubKeyBytes,\n\t\t\t},\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn uses\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tuses := usesForRoute(rt)\n\tfor _, use := range uses {\n\t\tr.reserved[use.key] += use.amt\n\t}\n\tr.pending[rt] = uses\n}\n\nfunc (r *candidateRouter) releaseFailedRoute(rt *route.Route) []edgeUse {\n\tuses, ok := r.pending[rt]\n\tif !ok {\n\t\treturn usesForRoute(rt)\n\t}\n\tdelete(r.pending, rt)\n\n\tfor _, use := range uses {\n\t\tcurrent := r.reserved[use.key]\n\t\tif current <= use.amt {\n\t\t\tdelete(r.reserved, use.key)\n\t\t} else {\n\t\t\tr.reserved[use.key] = current - use.amt\n\t\t}\n\t}\n\n\treturn uses\n}\n\nfunc appendCandidateAmount(amounts []lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, amount, minimum,\n\tmaximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amount < minimum {\n\t\tamount = minimum\n\t}\n\tif amount > maximum {\n\t\tamount = maximum\n\t}\n\tif amount <= 0 || seen[amount] {\n\t\treturn amounts\n\t}\n\n\tseen[amount] = true\n\treturn append(amounts, amount)\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(\"invalid payment amount\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tminimum := amt\n\tif partsLeft > 1 {\n\t\tdivisor := lnwire.MilliSatoshi(partsLeft)\n\t\tminimum = (amt + divisor - 1) / divisor\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvar amounts []lnwire.MilliSatoshi\n\n\tamounts = appendCandidateAmount(amounts, seen, amt, minimum, amt)\n\tamounts = appendCandidateAmount(\n\t\tamounts, seen, amt*7/8, minimum, amt,\n\t)\n\tamounts = appendCandidateAmount(\n\t\tamounts, seen, amt*3/4, minimum, amt,\n\t)\n\tamounts = appendCandidateAmount(\n\t\tamounts, seen, amt*2/3, minimum, amt,\n\t)\n\tamounts = appendCandidateAmount(\n\t\tamounts, seen, amt/2, minimum, amt,\n\t)\n\tamounts = appendCandidateAmount(\n\t\tamounts, seen, amt/3, minimum, amt,\n\t)\n\tamounts = appendCandidateAmount(\n\t\tamounts, seen, amt/4, minimum, amt,\n\t)\n\n\tif r.lastFailedShard > 0 {\n\t\tamounts = appendCandidateAmount(\n\t\t\tamounts, seen, r.lastFailedShard*7/10,\n\t\t\tminimum, amt,\n\t\t)\n\t\tamounts = appendCandidateAmount(\n\t\t\tamounts, seen, r.lastFailedShard/2,\n\t\t\tminimum, amt,\n\t\t)\n\t}\n\n\tamounts = appendCandidateAmount(\n\t\tamounts, seen, minimum, minimum, amt,\n\t)\n\n\tvar best *candidateRouteChoice\n\tbestUtility := math.Inf(-1)\n\n\tfor _, shard := range amounts {\n\t\tchoice, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfee := choice.route.TotalAmount - shard\n\t\tfeeFraction := float64(fee) /\n\t\t\tmath.Max(1, float64(shard))\n\n\t\t// Maximize expected useful progress. The minimum shard constraint\n\t\t// guarantees that the remainder can still fit into the available\n\t\t// MPP slots, while the probability term naturally selects unequal\n\t\t// shards when corridors have different capacities.\n\t\tutility := math.Log(float64(shard)) +\n\t\t\tchoice.logProb - 2*feeFraction\n\n\t\tif best == nil || utility > bestUtility {\n\t\t\tbest = choice\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\n\tr.reserveRoute(best.route)\n\treturn best.route, nil\n}\n\nfunc failureEdgeIndex(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tuses, ok := r.pending[rt]\n\t\tif !ok {\n\t\t\tuses = usesForRoute(rt)\n\t\t} else {\n\t\t\tdelete(r.pending, rt)\n\t\t}\n\n\t\tfor _, use := range uses {\n\t\t\tproven := r.reserved[use.key]\n\t\t\tif proven < use.amt {\n\t\t\t\tproven = use.amt\n\t\t\t}\n\t\t\tr.knowledge.noteSuccess(use.key, proven)\n\n\t\t\tr.attemptPenalty[use.key] *= 0.25\n\t\t\tr.policyVotes[use.key] = 0\n\t\t\tdelete(r.policyBlock, use.key)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tuses := r.releaseFailedRoute(rt)\n\tr.lastFailedShard = routeDeliveredAmount(rt)\n\n\t// Every failed route gets only a soft, local diversity cost. This is\n\t// safe even when the onion failure is unreadable because it does not\n\t// become a persistent liquidity claim.\n\tfor _, use := range uses {\n\t\tr.attemptPenalty[use.key] += 0.22\n\t}\n\n\tfailIndex := failureEdgeIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(uses) {\n\t\t// The reported source is not an eligible forwarding node on the\n\t\t// route. This is explicitly no-information attribution.\n\t\treturn nil\n\t}\n\n\tfailedUse := uses[failIndex]\n\trequired := failedUse.amt + r.reserved[failedUse.key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t// A single plausible attribution is only a suspect observation.\n\t\t// Repeated consistent blame promotes it to a strong upper bound.\n\t\tr.knowledge.noteFailure(failedUse.key, required)\n\t\tr.attemptPenalty[failedUse.key] += 1.15\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// Shifted blame can also affect policy errors, so require a second\n\t\t// observation before excluding the edge for this payment.\n\t\tif r.policyVotes[failedUse.key] < 255 {\n\t\t\tr.policyVotes[failedUse.key]++\n\t\t}\n\t\tr.attemptPenalty[failedUse.key] += 1.5\n\t\tif r.policyVotes[failedUse.key] >= 2 {\n\t\t\tr.policyBlock[failedUse.key] = true\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 2,
"parent": 0,
"score": 0.4493,
"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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tswitch {\n\tcase amt <= 25_000_000:\n\t\treturn 1\n\tcase amt <= 250_000_000:\n\t\treturn 4\n\tcase amt <= 1_000_000_000:\n\t\treturn 6\n\tcase amt <= 2_000_000_000:\n\t\treturn 8\n\tdefault:\n\t\treturn 12\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\t// A compact bimodal prior: a small low-balance mode that decays\n\t// quickly, and a high-balance mode with a cliff near capacity.\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.07 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\tz := (float64(amt) - float64(estimate)) / scale\n\tif z > 30 {\n\t\treturn 0.005\n\t}\n\tif z < -30 {\n\t\treturn 0.995\n\t}\n\treturn 1 / (1 + math.Exp(z))\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\t// Our own outgoing balance is exact and should not be overridden by\n\t// noisy remote failure attribution.\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tb, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *b\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.14 * float64(belief.conf)\n\t\t\tif weight > 0.70 {\n\t\t\t\tweight = 0.70\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.008)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tif pos > 0 {\n\t\t\t\t\tbounded := 0.995*(1-pos) + 0.008*pos\n\t\t\t\t\tp = 0.35*p + 0.65*bounded\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, ok := r.localFails[e.key]; ok &&\n\t\trequired >= local.upper {\n\n\t\tif local.count >= 2 {\n\t\t\tp = math.Min(p, 0.004)\n\t\t} else {\n\t\t\t// A single attribution is only a suspect observation.\n\t\t\t// It strongly encourages a different route without turning\n\t\t\t// a shifted blame report into a permanent blacklist.\n\t\t\tp *= 0.08\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.01 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tcost += 0.04 +\n\t\t\t0.35*float64(reserved)/float64(e.capacity)\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tscore := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscore[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tbestScore, ok := score[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.amt != arriving[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 18 * float64(fee) / denom\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(edge, amtOver) + feeCost\n\n\t\t\toldScore, exists := score[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\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: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := score[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\tpathProbability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tpathProbability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, pathProbability, 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\tfor node := r.source; node != r.spec.Target; {\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts\n\n\t// Failure feedback opens spare part slots gradually. This keeps the\n\t// initial route set compact while allowing smaller lower-bound\n\t// retries when a corridor proves depleted.\n\ttarget += r.failures / 2\n\tif r.unknownFails >= 2 {\n\t\ttarget++\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\treturn target\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminimumShard := candidateCeilDiv(amt, partsLeft)\n\n\tvar wholeRoute *route.Route\n\tif rt, probability, err := r.findRoute(amt); err == nil {\n\t\twholeRoute = rt\n\n\t\t// Whole-payment routing is retained when the path is already\n\t\t// well-supported by tiny-amount priors or truthful successes.\n\t\tif r.failures == 0 &&\n\t\t\t(r.plannedParts == 1 || probability >= 0.75) {\n\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\ttargetParts := r.targetPartCount()\n\tvar desiredSlots uint32\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t} else {\n\t\tdesiredSlots = 1\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\tif desiredSlots == 0 {\n\t\tdesiredSlots = 1\n\t}\n\n\tshard := candidateCeilDiv(amt, desiredSlots)\n\tif r.retryCap > 0 && r.retryCap < shard {\n\t\tshard = r.retryCap\n\t}\n\tif shard < minimumShard {\n\t\tshard = minimumShard\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tfor {\n\t\trt, _, err := r.findRoute(shard)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\tif shard <= minimumShard {\n\t\t\tif wholeRoute != nil && shard != amt {\n\t\t\t\treturn wholeRoute, nil\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnextShard := shard * 2 / 3\n\t\tif nextShard < minimumShard {\n\t\t\tnextShard = minimumShard\n\t\t}\n\t\tif nextShard == shard {\n\t\t\tnextShard--\n\t\t}\n\t\tshard = nextShard\n\t}\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tb := candidateBeliefStore.beliefs[traversal.key]\n\t\tif b == nil {\n\t\t\tb = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t\t}\n\n\t\tif required > b.lowerOK {\n\t\t\tb.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > b.estimate {\n\t\t\tb.estimate = highEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\n\t\t// Truthful success invalidates any conflicting attribution.\n\t\tif b.upperFail > 0 && required >= b.upperFail {\n\t\t\tb.upperFail = 0\n\t\t}\n\t\tif b.suspectAmt > 0 && required >= b.suspectAmt {\n\t\t\tb.suspectAmt = 0\n\t\t\tb.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif b := candidateBeliefStore.beliefs[key]; b != nil {\n\t\t\tif b.lowerOK > used {\n\t\t\t\tb.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tb.lowerOK = 0\n\t\t\t}\n\t\t\tif b.estimate > used {\n\t\t\t\tb.estimate -= used\n\t\t\t} else {\n\t\t\t\tb.estimate = 0\n\t\t\t}\n\t\t\tif b.upperFail > used {\n\t\t\t\tb.upperFail -= used\n\t\t\t} else {\n\t\t\t\tb.upperFail = 0\n\t\t\t}\n\t\t\tif b.suspectAmt > used {\n\t\t\t\tb.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tb.suspectAmt = 0\n\t\t\t\tb.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureEdge(rt *route.Route,\n\tsource route.Vertex) (candidateTraversal, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\tindex := -1\n\tif source == rt.SourcePubKey {\n\t\tindex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tindex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif index < 0 || index >= len(traversals) {\n\t\treturn candidateTraversal{}, false\n\t}\n\treturn traversals[index], true\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversal candidateTraversal) {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tif local.count < 255 {\n\t\tlocal.count++\n\t}\n\tr.localFails[traversal.key] = local\n\tr.edgePenalty[traversal.key] += 1.25\n\n\t// Exact local balances outrank a shifted remote attribution.\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tb := candidateBeliefStore.beliefs[traversal.key]\n\tif b == nil {\n\t\tb = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t}\n\n\tif b.suspectVotes == 0 {\n\t\tb.suspectAmt = required\n\t\tb.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif required < b.suspectAmt || b.suspectAmt == 0 {\n\t\tb.suspectAmt = required\n\t}\n\tif b.suspectVotes < 255 {\n\t\tb.suspectVotes++\n\t}\n\n\t// Two independent reports promote a suspect to a soft upper bound.\n\t// It remains routable at a very low probability so drift or a pair\n\t// of shifted reports cannot permanently poison the channel.\n\tif b.suspectVotes >= 2 {\n\t\tif b.upperFail == 0 || b.suspectAmt < b.upperFail {\n\t\t\tb.upperFail = b.suspectAmt\n\t\t}\n\n\t\tfailedEstimate := required * 70 / 100\n\t\tif b.estimate == 0 || failedEstimate < b.estimate {\n\t\t\tb.estimate = failedEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\ttraversals := r.routeTraversals(rt)\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.03\n\t}\n\n\tcode := result.Failure.Code()\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tr.recordAttributedLiquidityFailure(traversal)\n\n\t\t\tdelivered := candidateDeliveredAmount(rt)\n\t\t\tif delivered > 1 {\n\t\t\t\tr.retryCap = delivered * 62 / 100\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// An off-route source carries no channel information. Route-wide\n\t// penalties only diversify the next attempt and never enter the\n\t// persistent liquidity store.\n\tr.unknownFails++\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.15\n\t}\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tr.edgePenalty[traversal.key] += 2.0\n\t\t}\n\t}\n\n\tif r.unknownFails%2 == 0 {\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tif delivered > 1 {\n\t\t\tr.retryCap = delivered * 72 / 100\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 3,
"parent": 1,
"score": 0.4776,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\tpolicyFails map[candidateEdgeKey]uint8\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\trequestNo uint64\n\tsearchSalt uint64\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyFails: make(map[candidateEdgeKey]uint8),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tswitch {\n\tcase amt <= 20_000_000:\n\t\treturn 1\n\tcase amt <= 100_000_000:\n\t\treturn 2\n\tcase amt <= 400_000_000:\n\t\treturn 4\n\tcase amt <= 1_000_000_000:\n\t\treturn 6\n\tcase amt <= 3_000_000_000:\n\t\treturn 10\n\tdefault:\n\t\tif r.spec.MaxParts != 0 {\n\t\t\treturn r.spec.MaxParts\n\t\t}\n\t\treturn 12\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.065 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tswitch {\n\tcase z > 30:\n\t\treturn 0.005\n\tcase z < -30:\n\t\treturn 0.995\n\tdefault:\n\t\treturn 1 / (1 + math.Exp(z))\n\t}\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.11 * float64(belief.conf)\n\t\t\tif weight > 0.62 {\n\t\t\t\tweight = 0.62\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.997)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.025)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK &&\n\t\t\t\trequired > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tbounded := 0.997*(1-pos) + 0.025*pos\n\t\t\t\tp = 0.45*p + 0.55*bounded\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, exists := r.localFails[e.key]; exists &&\n\t\trequired >= local.upper {\n\n\t\tif local.count >= 3 {\n\t\t\tp *= 0.06\n\t\t} else if local.count == 2 {\n\t\t\tp *= 0.15\n\t\t} else {\n\t\t\tp *= 0.38\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc candidateEdgeHash(key candidateEdgeKey, salt uint64) float64 {\n\th := key.chanID ^ salt ^ 0x9e3779b97f4a7c15\n\tfor _, b := range key.from {\n\t\th ^= uint64(b)\n\t\th *= 0x100000001b3\n\t}\n\tfor _, b := range key.to {\n\t\th ^= uint64(b) + 0x9d\n\t\th *= 0x100000001b3\n\t}\n\n\th ^= h >> 30\n\th *= 0xbf58476d1ce4e5b9\n\th ^= h >> 27\n\th *= 0x94d049bb133111eb\n\th ^= h >> 31\n\n\treturn float64(h>>11) / float64(uint64(1)<<53)\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.01 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tcost += 0.85 +\n\t\t\t1.20*float64(reserved)/float64(e.capacity)\n\t}\n\n\tif r.failures > 0 {\n\t\texploration := 0.10 + 0.025*float64(r.failures)\n\t\tif exploration > 0.70 {\n\t\t\texploration = 0.70\n\t\t}\n\t\tcost += exploration * candidateEdgeHash(\n\t\t\te.key, r.searchSalt,\n\t\t)\n\t}\n\n\treturn cost\n}\n\ntype candidateLabel struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n\tnextEdge *candidateEdge\n\tnext *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 candidateLabelContains(label *candidateLabel,\n\tnode route.Vertex) bool {\n\n\tfor current := label; current != nil; current = current.next {\n\t\tif current.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAddLabel(labels map[route.Vertex][]*candidateLabel,\n\tlabel *candidateLabel) bool {\n\n\texisting := labels[label.node]\n\tactiveCount := 0\n\n\tfor _, old := range existing {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tactiveCount++\n\n\t\tif old.score <= label.score && old.amt <= label.amt {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor _, old := range existing {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif label.score <= old.score && label.amt <= old.amt {\n\t\t\told.active = false\n\t\t\tactiveCount--\n\t\t}\n\t}\n\n\tif activeCount >= 10 {\n\t\tvar worst *candidateLabel\n\t\tfor _, old := range existing {\n\t\t\tif !old.active {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst == nil || old.score > worst.score {\n\t\t\t\tworst = old\n\t\t\t}\n\t\t}\n\t\tif worst != nil && worst.score <= label.score {\n\t\t\treturn false\n\t\t}\n\t\tif worst != nil {\n\t\t\tworst.active = false\n\t\t}\n\t}\n\n\tlabel.active = true\n\tlabels[label.node] = append(existing, label)\n\treturn true\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\ttarget := &candidateLabel{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamt: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidateLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tpq := &candidateLabelQueue{}\n\theap.Push(pq, target)\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\tif candidateLabelContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 16 * float64(fee) / denom\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 + r.edgeCost(edge, amtOver) + feeCost,\n\t\t\t\tamt: sending,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tnext: item,\n\t\t\t}\n\t\t\tif candidateAddLabel(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\tpath := make([]*candidateEdge, 0, 8)\n\tfor label := sourceLabel; label.nextEdge != nil; label = label.next {\n\t\tpath = append(path, label.nextEdge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tprobability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tprobability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, probability, 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\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts + (r.failures+1)/2\n\tif r.unknownFails >= 2 {\n\t\ttarget++\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\treturn target\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamt, minimum, maximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amt < minimum {\n\t\tamt = minimum\n\t}\n\tif amt > maximum {\n\t\tamt = maximum\n\t}\n\tif amt <= 0 {\n\t\treturn amounts\n\t}\n\n\tfor _, existing := range amounts {\n\t\tif existing == amt {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amt)\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tr.requestNo++\n\tr.searchSalt = r.requestNo*0x9e3779b97f4a7c15 +\n\t\tuint64(r.failures)*0xbf58476d1ce4e5b9\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminimumShard := candidateCeilDiv(amt, partsLeft)\n\n\twholeRoute, wholeProbability, wholeErr := r.findRoute(amt)\n\tif wholeErr == nil && r.failures == 0 {\n\t\tif maxParts == 1 || r.plannedParts == 1 ||\n\t\t\twholeProbability >= 0.90 {\n\n\t\t\treturn wholeRoute, nil\n\t\t}\n\t}\n\n\ttargetParts := r.targetPartCount()\n\tdesiredSlots := uint32(1)\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\n\tbase := candidateCeilDiv(amt, desiredSlots)\n\tmaximum := amt\n\tif r.retryCap > 0 && r.retryCap < maximum {\n\t\tmaximum = r.retryCap\n\t}\n\tif maximum < minimumShard {\n\t\tmaximum = minimumShard\n\t}\n\tif base > maximum {\n\t\tbase = maximum\n\t}\n\tif base < minimumShard {\n\t\tbase = minimumShard\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, 0, 7)\n\tamounts = candidateAppendAmount(\n\t\tamounts, base, minimumShard, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, minimumShard, minimumShard, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, (minimumShard+base)/2,\n\t\tminimumShard, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, base*5/4, minimumShard, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, base*3/2, minimumShard, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, base*2, minimumShard, maximum,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, maximum, minimumShard, maximum,\n\t)\n\n\tvar bestRoute *route.Route\n\tbestScore := math.Inf(-1)\n\tvar lastErr error\n\n\tfor _, shard := range amounts {\n\t\trt, probability, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tif probability < 0.0000001 {\n\t\t\tprobability = 0.0000001\n\t\t}\n\n\t\tprogress := float64(shard) / float64(base)\n\t\tif progress < 0.0000001 {\n\t\t\tprogress = 0.0000001\n\t\t}\n\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tfeeRatio := 0.0\n\t\tif delivered > 0 && rt.TotalAmount > delivered {\n\t\t\tfeeRatio = float64(rt.TotalAmount-delivered) /\n\t\t\t\tfloat64(delivered)\n\t\t}\n\n\t\tscore := math.Log(probability) +\n\t\t\t0.18*math.Log(progress) - 4*feeRatio\n\n\t\tif score > bestScore {\n\t\t\tbestScore = score\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute != nil {\n\t\treturn bestRoute, nil\n\t}\n\tif wholeRoute != nil {\n\t\treturn wholeRoute, nil\n\t}\n\tif lastErr != nil {\n\t\treturn nil, lastErr\n\t}\n\treturn nil, wholeErr\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\t\tif belief == nil {\n\t\t\tbelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t\t}\n\n\t\tif required > belief.lowerOK {\n\t\t\tbelief.lowerOK = required\n\t\t}\n\n\t\testimate := traversal.edge.capacity * 90 / 100\n\t\tif required > estimate {\n\t\t\testimate = required\n\t\t}\n\t\tif estimate > belief.estimate {\n\t\t\tbelief.estimate = estimate\n\t\t}\n\t\tif belief.conf < 8 {\n\t\t\tbelief.conf++\n\t\t}\n\n\t\tif belief.upperFail > 0 &&\n\t\t\trequired >= belief.upperFail {\n\n\t\t\tbelief.upperFail = 0\n\t\t}\n\t\tif belief.suspectAmt > 0 &&\n\t\t\trequired >= belief.suspectAmt {\n\n\t\t\tbelief.suspectAmt = 0\n\t\t\tbelief.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.edgePenalty[traversal.key] *= 0.25\n\t\tr.policyFails[traversal.key] = 0\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief := candidateBeliefStore.beliefs[key]; belief != nil {\n\t\t\tif belief.lowerOK > used {\n\t\t\t\tbelief.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tbelief.lowerOK = 0\n\t\t\t}\n\t\t\tif belief.estimate > used {\n\t\t\t\tbelief.estimate -= used\n\t\t\t} else {\n\t\t\t\tbelief.estimate = 0\n\t\t\t}\n\t\t\tif belief.upperFail > used {\n\t\t\t\tbelief.upperFail -= used\n\t\t\t} else {\n\t\t\t\tbelief.upperFail = 0\n\t\t\t}\n\t\t\tif belief.suspectAmt > used {\n\t\t\t\tbelief.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tbelief.suspectAmt = 0\n\t\t\t\tbelief.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\tcapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\tcapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\tcapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\t\tif capacity > 0 {\n\t\t\tif reverseBelief.lowerOK > capacity {\n\t\t\t\treverseBelief.lowerOK = capacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > capacity {\n\t\t\t\treverseBelief.estimate = capacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif capacity > 0 &&\n\t\t\t\treverseBelief.upperFail > capacity {\n\n\t\t\t\treverseBelief.upperFail = capacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureEdge(rt *route.Route,\n\tsource route.Vertex) (candidateTraversal, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\tindex := -1\n\tif source == rt.SourcePubKey {\n\t\tindex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tindex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif index < 0 || index >= len(traversals) {\n\t\treturn candidateTraversal{}, false\n\t}\n\treturn traversals[index], true\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversal candidateTraversal) {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tif traversal.key.from != r.source {\n\t\tlocal := r.localFails[traversal.key]\n\t\tif local.upper == 0 || required < local.upper {\n\t\t\tlocal.upper = required\n\t\t}\n\t\tif local.count < 255 {\n\t\t\tlocal.count++\n\t\t}\n\t\tr.localFails[traversal.key] = local\n\t\tr.edgePenalty[traversal.key] += 0.75\n\t} else {\n\t\tr.edgePenalty[traversal.key] += 0.20\n\t\treturn\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\tif belief == nil {\n\t\tbelief = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t}\n\n\tif belief.suspectVotes == 0 {\n\t\tbelief.suspectAmt = required\n\t\tbelief.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif belief.suspectAmt == 0 || required < belief.suspectAmt {\n\t\tbelief.suspectAmt = required\n\t}\n\tif belief.suspectVotes < 255 {\n\t\tbelief.suspectVotes++\n\t}\n\n\tvotesNeeded := uint8(2)\n\tif belief.lowerOK > 0 &&\n\t\tbelief.suspectAmt <= belief.lowerOK {\n\n\t\tvotesNeeded = 3\n\t}\n\n\tif belief.suspectVotes < votesNeeded {\n\t\treturn\n\t}\n\n\tif belief.lowerOK > 0 &&\n\t\tbelief.suspectAmt <= belief.lowerOK {\n\n\t\tif belief.suspectAmt > 1 {\n\t\t\tbelief.lowerOK = belief.suspectAmt - 1\n\t\t} else {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\t}\n\n\tif belief.upperFail == 0 ||\n\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\tbelief.upperFail = belief.suspectAmt\n\t}\n\n\tfailedEstimate := belief.suspectAmt * 65 / 100\n\tif belief.estimate == 0 || failedEstimate < belief.estimate {\n\t\tbelief.estimate = failedEstimate\n\t}\n\tif belief.conf < 8 {\n\t\tbelief.conf++\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\ttraversals := r.routeTraversals(rt)\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.10\n\t}\n\n\tcode := result.Failure.Code()\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tr.recordAttributedLiquidityFailure(traversal)\n\n\t\t\tdelivered := candidateDeliveredAmount(rt)\n\t\t\tif delivered > 1 {\n\t\t\t\tfactor := lnwire.MilliSatoshi(70)\n\t\t\t\tif local := r.localFails[traversal.key]; local.count >= 2 {\n\t\t\t\t\tfactor = 58\n\t\t\t\t}\n\t\t\t\tr.retryCap = delivered * factor / 100\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tr.unknownFails++\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.24\n\t}\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tcount := r.policyFails[traversal.key]\n\t\t\tif count < 255 {\n\t\t\t\tcount++\n\t\t\t}\n\t\t\tr.policyFails[traversal.key] = count\n\n\t\t\tif count >= 2 {\n\t\t\t\tr.edgePenalty[traversal.key] += 8.0\n\t\t\t} else {\n\t\t\t\tr.edgePenalty[traversal.key] += 2.5\n\t\t\t}\n\t\t}\n\t}\n\n\tif r.unknownFails%2 == 0 {\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tif delivered > 1 {\n\t\t\tr.retryCap = delivered * 72 / 100\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 4,
"parent": 0,
"score": 0.551,
"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\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usable(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\n// Beliefs are directed. A success is trustworthy, while a reported failure\n// must be corroborated before it becomes a hard upper bound.\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\n\tupperFail lnwire.MilliSatoshi\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n\n\tstamp uint64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\t// reserved is liquidity consumed or held by successful shards of the\n\t// current payment. It prevents atomic-MPP siblings from assuming that\n\t// the same corridor's liquidity is still entirely available.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// Per-payment soft penalties encourage route diversity without turning\n\t// an unreadable failure into false channel-level knowledge.\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tretryLimit lnwire.MilliSatoshi\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\tcandidateMemory.Lock()\n\tcandidateMemory.epoch++\n\tepoch := candidateMemory.epoch\n\n\tsnapshot := make(map[candidateEdgeKey]candidateBelief,\n\t\tlen(candidateMemory.beliefs))\n\tfor key, belief := range candidateMemory.beliefs {\n\t\tsnapshot[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: snapshot,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// candidatePrior models a channel as roughly half nearly empty and half\n// nearly full. Tiny transfers can fit in either mode, medium transfers\n// mostly require the full mode, and transfers near capacity encounter a\n// steep cliff.\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\thighMode := 1 / (1 + math.Exp(18*(x-0.82)))\n\n\treturn candidateClampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) evidenceConfidence(b candidateBelief) float64 {\n\tif b.stamp == 0 || r.epoch <= b.stamp+3 {\n\t\treturn 1\n\t}\n\n\tage := float64(r.epoch - b.stamp - 3)\n\treturn math.Max(0.12, math.Exp(-age/12))\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tavailable := r.localBalances[edge.chanID]\n\t\tif totalAmt <= available {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := candidatePrior(totalAmt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.stamp == 0 {\n\t\treturn prior\n\t}\n\n\tlearned := prior\n\n\tswitch {\n\tcase belief.lowerOK > 0 && totalAmt <= belief.lowerOK:\n\t\tlearned = 0.995\n\n\tcase belief.upperFail > 0 &&\n\t\ttotalAmt >= belief.upperFail:\n\n\t\tlearned = 0.008\n\n\tcase belief.lowerOK > 0 && belief.upperFail > belief.lowerOK:\n\t\tspan := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(totalAmt-belief.lowerOK) / span\n\t\tpos = math.Max(0, math.Min(1, pos))\n\t\tlearned = 0.995*(1-pos) + 0.008*pos\n\n\tcase belief.lowerOK > 0 && totalAmt > belief.lowerOK:\n\t\tratio := float64(belief.lowerOK) / float64(totalAmt)\n\t\tlearned = prior + (0.995-prior)*ratio*ratio\n\n\tcase belief.upperFail > 0 && totalAmt < belief.upperFail:\n\t\tratio := float64(totalAmt) / float64(belief.upperFail)\n\t\tlearned = prior * (1 - 0.75*ratio*ratio)\n\t}\n\n\t// One uncorroborated blame is only a soft hint. This is deliberately\n\t// insufficient to exclude a channel because attribution can be shifted.\n\tif belief.upperFail == 0 && belief.suspectAmt > 0 &&\n\t\ttotalAmt >= belief.suspectAmt {\n\n\t\tlearned *= 0.55\n\t}\n\n\tconfidence := r.evidenceConfidence(belief)\n\tp := confidence*learned + (1-confidence)*prior\n\n\treturn candidateClampProbability(p)\n}\n\nfunc (r *candidateRouter) hardFailed(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) bool {\n\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.upperFail == 0 {\n\t\treturn false\n\t}\n\n\t// Recent corroborated evidence is a bound. Old evidence merely affects\n\t// probability because background traffic may have moved liquidity.\n\treturn r.evidenceConfidence(belief) >= 0.72 &&\n\t\ttotalAmt >= belief.upperFail\n}\n\ntype candidateItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype candidateQueue []*candidateItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype candidatePath struct {\n\troute *route.Route\n\tedges []*candidateEdge\n\tprobability float64\n\tpressure float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tamounts := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tamounts[r.spec.Target] = amt\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateItem)\n\t\tbestScore, ok := scores[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amounts[item.node]\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usable(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := arriving + r.reserved[edge.key]\n\t\t\tif totalLiquidity < arriving ||\n\t\t\t\ttotalLiquidity > edge.capacity {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tif totalLiquidity >\n\t\t\t\t\tr.localBalances[edge.chanID] {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else if r.hardFailed(edge, totalLiquidity) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, totalLiquidity)\n\t\t\tedgeCost := -math.Log(probability) + 0.025\n\t\t\tif arriving > 0 {\n\t\t\t\tedgeCost += 5 * float64(fee) /\n\t\t\t\t\tfloat64(arriving)\n\t\t\t}\n\t\t\tedgeCost += r.edgePenalty[edge.key]\n\n\t\t\tnewScore := item.score + edgeCost\n\t\t\toldScore, visited := scores[edge.from]\n\t\t\tif visited && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.from] = newScore\n\t\t\tamounts[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &candidateItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tpressure := 0.0\n\n\tfor i, edge := range path {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\ttotalLiquidity := amtOver + r.reserved[edge.key]\n\n\t\tprobability *= r.probability(edge, totalLiquidity)\n\n\t\tdenominator := edge.capacity\n\t\tif edge.from == r.source {\n\t\t\tdenominator = r.localBalances[edge.chanID]\n\t\t} else if belief, ok := r.beliefs[edge.key]; ok &&\n\t\t\tbelief.upperFail > 0 &&\n\t\t\tr.evidenceConfidence(belief) >= 0.72 &&\n\t\t\tbelief.upperFail < denominator {\n\n\t\t\tdenominator = belief.upperFail\n\t\t}\n\n\t\tif denominator > 0 {\n\t\t\tratio := float64(totalLiquidity) /\n\t\t\t\tfloat64(denominator)\n\t\t\tif ratio > pressure {\n\t\t\t\tpressure = ratio\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &candidatePath{\n\t\troute: rt,\n\t\tedges: path,\n\t\tprobability: probability,\n\t\tpressure: pressure,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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 := 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.to,\n\t\t\tChannelID: edge.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}, path, nil\n}\n\nfunc candidateCeilDiv(a lnwire.MilliSatoshi,\n\tb uint32) lnwire.MilliSatoshi {\n\n\tif b <= 1 {\n\t\treturn a\n\t}\n\tdivisor := lnwire.MilliSatoshi(b)\n\treturn (a + divisor - 1) / divisor\n}\n\n// RequestRoute commits large payments in a small route set when a single\n// route is near its capacity cliff. Failed channels remain eligible at a\n// lower amount rather than being permanently blacklisted.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\t// The last available part must cover the entire remainder.\n\tif partsLeft <= 1 {\n\t\tpath, err := r.findRoute(amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn path.route, nil\n\t}\n\n\tif r.retryLimit == 0 {\n\t\tfull, fullErr := r.findRoute(amt)\n\n\t\t// Small or low-pressure payments are best sent without needless\n\t\t// splitting. Learned high-confidence paths are also tried whole.\n\t\tif fullErr == nil &&\n\t\t\t(amt < 50_000_000 ||\n\t\t\t\tfull.pressure < 0.30 ||\n\t\t\t\tfull.probability >= 0.70) {\n\n\t\t\treturn full.route, nil\n\t\t}\n\n\t\tnumParts := uint32(2)\n\t\tif fullErr != nil {\n\t\t\tnumParts = 4\n\t\t} else {\n\t\t\tnumParts = uint32(math.Ceil(\n\t\t\t\tfull.pressure / 0.28,\n\t\t\t))\n\t\t\tif numParts < 2 {\n\t\t\t\tnumParts = 2\n\t\t\t}\n\t\t\tif numParts > 4 {\n\t\t\t\tnumParts = 4\n\t\t\t}\n\t\t}\n\t\tif numParts > partsLeft {\n\t\t\tnumParts = partsLeft\n\t\t}\n\n\t\tr.retryLimit = candidateCeilDiv(amt, numParts)\n\t}\n\n\tdesired := r.retryLimit\n\tif desired <= 0 || desired > amt {\n\t\tdesired = amt\n\t}\n\n\tconst minimumShard = lnwire.MilliSatoshi(1_000_000)\n\tvar lastErr error\n\n\tfor {\n\t\tpath, err := r.findRoute(desired)\n\t\tif err == nil {\n\t\t\treturn path.route, nil\n\t\t}\n\t\tlastErr = err\n\n\t\tif desired <= minimumShard {\n\t\t\treturn nil, lastErr\n\t\t}\n\n\t\tsmaller := desired * 72 / 100\n\t\tif smaller < minimumShard {\n\t\t\tsmaller = minimumShard\n\t\t}\n\t\tif smaller >= desired {\n\t\t\treturn nil, lastErr\n\t\t}\n\n\t\tdesired = smaller\n\t\tr.retryLimit = smaller\n\t}\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tedgeIndex int) lnwire.MilliSatoshi {\n\n\tif edgeIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[edgeIndex-1].AmtToForward\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tedgeIndex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif edgeIndex > 0 {\n\t\tfrom = rt.Hops[edgeIndex-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[edgeIndex].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc candidateFailureEdge(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc candidateIsLiquidityFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc candidateIsPolicyFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey, belief candidateBelief) {\n\n\tbelief.stamp = r.epoch\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tcandidateMemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tif totalAmt > belief.lowerOK {\n\t\tbelief.lowerOK = totalAmt\n\t}\n\n\t// A truthful success disproves any ceiling at or below this amount.\n\tif belief.upperFail > 0 && totalAmt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 && totalAmt >= belief.suspectAmt {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tcorroborates := false\n\tif belief.suspectAmt > 0 {\n\t\tlow := belief.suspectAmt * 60 / 100\n\t\thigh := belief.suspectAmt * 150 / 100\n\t\tcorroborates = totalAmt >= low && totalAmt <= high\n\t}\n\n\tif corroborates {\n\t\tif belief.suspectVotes < 255 {\n\t\t\tbelief.suspectVotes++\n\t\t}\n\t\tif totalAmt < belief.suspectAmt {\n\t\t\tbelief.suspectAmt = totalAmt\n\t\t}\n\t} else {\n\t\tbelief.suspectAmt = totalAmt\n\t\tbelief.suspectVotes = 1\n\t}\n\n\trequiredVotes := uint8(2)\n\tif belief.lowerOK > 0 && totalAmt <= belief.lowerOK {\n\t\t// Contradicting a truthful success requires extra evidence. This\n\t\t// may still happen after genuine liquidity drift.\n\t\trequiredVotes = 3\n\t}\n\n\tif belief.suspectVotes >= requiredVotes {\n\t\tif totalAmt <= belief.lowerOK {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\n// ReportAttempt uses successes as hard evidence. Liquidity blame is first\n// quarantined as a soft observation and becomes a bound only after another\n// compatible failure. Unreadable failures never modify liquidity beliefs.\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 nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, totalAmt)\n\t\t\tr.reserved[key] += amtOver\n\t\t\tr.edgePenalty[key] *= 0.35\n\t\t}\n\n\t\tr.retryLimit = 0\n\t\treturn nil\n\t}\n\n\t// Every failed route receives only a small, capped diversity penalty.\n\t// This is useful even when the onion error cannot be read, but is much\n\t// weaker than channel-level liquidity evidence.\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t3, r.edgePenalty[key]+0.08,\n\t\t)\n\t}\n\n\tfailIndex := candidateFailureEdge(\n\t\trt, result.FailureSource,\n\t)\n\tattributed := failIndex >= 0 && failIndex < len(rt.Hops)\n\n\tif candidateIsLiquidityFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tamtOver := candidateRouteAmount(rt, failIndex)\n\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\tr.recordFailure(key, totalAmt)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t6, r.edgePenalty[key]+0.9,\n\t\t)\n\n\t\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\t\tlowerRetry := delivered * 72 / 100\n\t\tif lowerRetry < 1_000_000 {\n\t\t\tlowerRetry = 1_000_000\n\t\t}\n\t\tr.retryLimit = lowerRetry\n\n\t\treturn nil\n\t}\n\n\tif candidateIsPolicyFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t6, r.edgePenalty[key]+1.5,\n\t\t)\n\t}\n\n\t// Unknown and policy failures should try another route at the same\n\t// delivered amount rather than being mistaken for liquidity ceilings.\n\tr.retryLimit = 0\n\treturn nil\n}"
}
},
{
"id": 5,
"parent": 0,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"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 directedKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey directedKey\n\tcapacity lnwire.MilliSatoshi\n\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity\n}\n\ntype liquidityBelief struct {\n\tcapacity lnwire.MilliSatoshi\n\n\t// Truthful successes establish a lower bound. Corroborated failures\n\t// establish an upper bound.\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\n\t// Estimate expresses the bimodal interpretation of observations:\n\t// success suggests the high-liquidity mode, while repeated failure\n\t// suggests the depleted mode.\n\testimate lnwire.MilliSatoshi\n\tevidence uint32\n\n\t// A first attributed failure is only a suspect observation. This\n\t// avoids turning one shifted attribution into a hard bound.\n\tsuspectAmt lnwire.MilliSatoshi\n\tfailVotes uint32\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[directedKey]liquidityBelief\n}{\n\tm: make(map[directedKey]liquidityBelief),\n}\n\ntype amountPenalty struct {\n\tamount lnwire.MilliSatoshi\n\tweight float64\n}\n\ntype edgeUse struct {\n\tkey directedKey\n\tamount lnwire.MilliSatoshi\n\tfrom route.Vertex\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[directedKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[directedKey]*liquidityBelief\n\n\t// Reservations include attempts already handed to the runner. Failed\n\t// attempts release them; successful shards retain them so atomic MPP\n\t// siblings cannot assume the same liquidity is still free.\n\treserved map[directedKey]lnwire.MilliSatoshi\n\tpending map[*route.Route][]edgeUse\n\n\t// Per-payment penalties promptly diversify after a failure without\n\t// permanently poisoning knowledge from a single shifted attribution.\n\tpenalties map[directedKey]amountPenalty\n\n\tretryCap lnwire.MilliSatoshi\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tif spec == nil {\n\t\treturn nil, errors.New(\"nil payment specification\")\n\t}\n\n\tbalances := make(map[uint64]lnwire.MilliSatoshi, len(localBalances))\n\tfor chanID, balance := range localBalances {\n\t\tbalances[chanID] = balance\n\t}\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[directedKey]*candidateEdge),\n\t\tlocalBalances: balances,\n\t\tbeliefs: make(map[directedKey]*liquidityBelief),\n\t\treserved: make(map[directedKey]lnwire.MilliSatoshi),\n\t\tpending: make(map[*route.Route][]edgeUse),\n\t\tpenalties: make(map[directedKey]amountPenalty),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := directedKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, exists := r.edges[key]; exists {\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: key,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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.edges[key] = edge\n\t\t\t\tr.incomingEdges[key.to] = append(\n\t\t\t\t\tr.incomingEdges[key.to], edge,\n\t\t\t\t)\n\n\t\t\t\tsharedBeliefs.Lock()\n\t\t\t\tb, ok := sharedBeliefs.m[key]\n\t\t\t\tsharedBeliefs.Unlock()\n\n\t\t\t\tif !ok {\n\t\t\t\t\tb.capacity = edge.capacity\n\t\t\t\t} else if b.capacity != edge.capacity {\n\t\t\t\t\tb.capacity = edge.capacity\n\t\t\t\t\tif b.lowerOK > edge.capacity {\n\t\t\t\t\t\tb.lowerOK = edge.capacity\n\t\t\t\t\t}\n\t\t\t\t\tif b.upperFail > edge.capacity {\n\t\t\t\t\t\tb.upperFail = edge.capacity\n\t\t\t\t\t}\n\t\t\t\t\tif b.estimate > edge.capacity {\n\t\t\t\t\t\tb.estimate = edge.capacity\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcopy := b\n\t\t\t\tr.beliefs[key] = &copy\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.002:\n\t\treturn 0.002\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// bimodalPrior models a channel as a mixture of a narrow depleted mode and\n// a broad high-liquidity mode. Tiny HTLCs can fit in either mode, ordinary\n// HTLCs mostly require the favorable side, and amounts near capacity face\n// a steep cliff.\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.002\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.44 * math.Exp(-x/0.025)\n\thighMode := 0.54 / (1 + math.Exp((x-0.90)/0.055))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treserved := r.reserved[edge.key]\n\tdemand := amt + reserved\n\tif demand < amt {\n\t\treturn 0.002\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif demand > r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.002\n\t\t}\n\t\treturn 0.995\n\t}\n\n\tif demand > edge.capacity {\n\t\treturn 0.002\n\t}\n\n\tb := r.beliefs[edge.key]\n\tif b == nil {\n\t\treturn bimodalPrior(demand, edge.capacity)\n\t}\n\n\tif b.lowerOK > 0 && demand <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\tif b.upperFail > 0 && demand >= b.upperFail {\n\t\treturn 0.002\n\t}\n\n\tprior := bimodalPrior(demand, edge.capacity)\n\tp := prior\n\n\tif b.lowerOK > 0 && b.upperFail > b.lowerOK {\n\t\tspan := float64(b.upperFail - b.lowerOK)\n\t\tpos := float64(demand-b.lowerOK) / span\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\tbounded := 0.995*(1-pos) + 0.002*pos\n\t\tp = 0.25*prior + 0.75*bounded\n\t} else if b.evidence > 0 && b.estimate > 0 {\n\t\twidth := math.Max(float64(edge.capacity)*0.10, 1)\n\t\testimated := 1 / (1 + math.Exp(\n\t\t\t(float64(demand)-float64(b.estimate))/width,\n\t\t))\n\t\tp = 0.35*prior + 0.65*estimated\n\t}\n\n\t// One uncorroborated attribution is deliberately soft. It affects\n\t// route ordering but cannot make a channel impossible.\n\tif b.failVotes == 1 && b.suspectAmt > 0 {\n\t\tratio := float64(demand) / float64(b.suspectAmt)\n\t\tswitch {\n\t\tcase ratio >= 0.85:\n\t\t\tp *= 0.55\n\t\tcase ratio > 0:\n\t\t\tp *= 1 - 0.45*ratio*ratio\n\t\t}\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) penaltyFor(key directedKey,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp, ok := r.penalties[key]\n\tif !ok || p.amount <= 0 {\n\t\treturn 0\n\t}\n\n\tratio := float64(amt) / float64(p.amount)\n\tif ratio >= 0.80 {\n\t\treturn p.weight\n\t}\n\tif ratio <= 0 {\n\t\treturn 0\n\t}\n\n\t// A failed large HTLC is much weaker evidence against a materially\n\t// smaller retry.\n\treturn p.weight * ratio * ratio\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\tlogRisk float64\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype routeChoice struct {\n\troute *route.Route\n\tprobability float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*routeChoice, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"non-positive route amount\")\n\t}\n\n\tdist := make(map[route.Vertex]float64)\n\tarrival := make(map[route.Vertex]lnwire.MilliSatoshi)\n\trisk := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = 0\n\tarrival[r.spec.Target] = amt\n\trisk[r.spec.Target] = 0\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t})\n\n\triskScale := math.Max(25_000, float64(amt)*0.012)\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.score > best+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amount\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdemand := amtOver + r.reserved[edge.key]\n\t\t\tif demand < amtOver || demand > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tdemand > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprob := r.edgeProbability(edge, amtOver)\n\t\t\tedgeRisk := -math.Log(prob)\n\t\t\tlocalPenalty := r.penaltyFor(edge.key, demand)\n\n\t\t\tsending := amtOver\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tsending += edge.fee(amtOver)\n\t\t\t}\n\t\t\tif sending < amtOver {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfeeCost := float64(sending - amtOver)\n\t\t\tscore := item.score + feeCost + 1_000 +\n\t\t\t\triskScale*(edgeRisk+localPenalty)\n\t\t\tlogRisk := item.logRisk + edgeRisk\n\n\t\t\told, exists := dist[edge.key.from]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = score\n\t\t\tarrival[edge.key.from] = sending\n\t\t\trisk[edge.key.from] = logRisk\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tamount: sending,\n\t\t\t\tscore: score,\n\t\t\t\tlogRisk: logRisk,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := arrival[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlogRisk := risk[r.source]\n\tif logRisk > 700 {\n\t\tlogRisk = 700\n\t}\n\n\treturn &routeChoice{\n\t\troute: rt,\n\t\tprobability: math.Exp(-logRisk),\n\t}, 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(\"cycle in selected route\")\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\toutgoing := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\toutgoing.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[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\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 routeUses(rt *route.Route) []edgeUse {\n\tif rt == nil {\n\t\treturn nil\n\t}\n\n\tuses := make([]edgeUse, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tuses = append(uses, edgeUse{\n\t\t\tkey: directedKey{\n\t\t\t\tchanID: hop.ChannelID,\n\t\t\t\tfrom: from,\n\t\t\t\tto: hop.PubKeyBytes,\n\t\t\t},\n\t\t\tamount: amt,\n\t\t\tfrom: from,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn uses\n}\n\nfunc (r *candidateRouter) reserve(rt *route.Route) {\n\tuses := routeUses(rt)\n\tfor _, use := range uses {\n\t\tr.reserved[use.key] += use.amount\n\t}\n\tr.pending[rt] = uses\n}\n\nfunc (r *candidateRouter) release(rt *route.Route) {\n\tuses, ok := r.pending[rt]\n\tif !ok {\n\t\treturn\n\t}\n\n\tfor _, use := range uses {\n\t\tcurrent := r.reserved[use.key]\n\t\tif use.amount >= current {\n\t\t\tdelete(r.reserved, use.key)\n\t\t} else {\n\t\t\tr.reserved[use.key] = current - use.amount\n\t\t}\n\t}\n\tdelete(r.pending, rt)\n}\n\nfunc (r *candidateRouter) handOut(choice *routeChoice) (*route.Route, error) {\n\tif choice == nil || choice.route == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\tr.reserve(choice.route)\n\treturn choice.route, nil\n}\n\nfunc ceilDivide(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn amt/divisor + lnwire.MilliSatoshi(\n\t\tamt%divisor != 0,\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\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\t// With one slot left, the route must carry the full remainder.\n\tif partsLeft <= 1 {\n\t\tchoice, err := r.findRoute(amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.retryCap = 0\n\t\treturn r.handOut(choice)\n\t}\n\n\tvar desired lnwire.MilliSatoshi\n\n\tif r.retryCap > 0 {\n\t\tdesired = r.retryCap\n\t\tif desired > amt {\n\t\t\tdesired = amt\n\t\t}\n\t\tr.retryCap = 0\n\t} else if inFlightHtlcs > 0 {\n\t\t// Once an atomic set is being assembled, deliberately fill the\n\t\t// remaining slots instead of probing a long halving ladder.\n\t\tdesired = ceilDivide(amt, partsLeft)\n\t} else {\n\t\tfull, err := r.findRoute(amt)\n\t\tif err == nil && full.probability >= 0.35 {\n\t\t\treturn r.handOut(full)\n\t\t}\n\n\t\t// Pick an up-front shard count from the full-route confidence.\n\t\t// Highly uncertain long paths use more parallel corridors.\n\t\tshards := partsLeft\n\t\tif err == nil {\n\t\t\tswitch {\n\t\t\tcase full.probability >= 0.08:\n\t\t\t\tshards = 2\n\t\t\tcase full.probability >= 0.015:\n\t\t\t\tshards = 4\n\t\t\t}\n\t\t\tif shards > partsLeft {\n\t\t\t\tshards = partsLeft\n\t\t\t}\n\t\t}\n\t\tdesired = ceilDivide(amt, shards)\n\t}\n\n\tif desired <= 0 {\n\t\tdesired = 1\n\t}\n\n\ttried := make(map[lnwire.MilliSatoshi]bool)\n\ttry := func(candidate lnwire.MilliSatoshi) (*routeChoice, error) {\n\t\tif candidate <= 0 || candidate > amt || tried[candidate] {\n\t\t\treturn nil, errors.New(\"amount already tried\")\n\t\t}\n\t\ttried[candidate] = true\n\t\treturn r.findRoute(candidate)\n\t}\n\n\tif choice, err := try(desired); err == nil {\n\t\treturn r.handOut(choice)\n\t}\n\n\t// A small HTLC may violate min-HTLC policies even where a larger one\n\t// is routable. Check larger deliberate allocations before descending.\n\tif desired < amt {\n\t\tmid := desired + (amt-desired)/2\n\t\tif choice, err := try(mid); err == nil {\n\t\t\treturn r.handOut(choice)\n\t\t}\n\t\tif choice, err := try(amt); err == nil {\n\t\t\treturn r.handOut(choice)\n\t\t}\n\t}\n\n\t// Retry failed corridors at materially smaller amounts. Unlike a hard\n\t// blacklist, this can discover the depleted side's remaining low-mode\n\t// liquidity.\n\tcandidate := desired\n\tfor i := 0; i < 14 && candidate > 1; i++ {\n\t\tcandidate = candidate * 62 / 100\n\t\tif candidate < 1 {\n\t\t\tcandidate = 1\n\t\t}\n\t\tif choice, err := try(candidate); err == nil {\n\t\t\treturn r.handOut(choice)\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) persist(key directedKey) {\n\tb := r.beliefs[key]\n\tif b == nil {\n\t\treturn\n\t}\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = *b\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(use edgeUse) {\n\tif use.from == r.source {\n\t\treturn\n\t}\n\n\tb := r.beliefs[use.key]\n\tedge := r.edges[use.key]\n\tif b == nil || edge == nil {\n\t\treturn\n\t}\n\n\tproven := use.amount\n\tif total := r.reserved[use.key]; total > proven {\n\t\tproven = total\n\t}\n\tif proven > edge.capacity {\n\t\tproven = edge.capacity\n\t}\n\n\tif proven > b.lowerOK {\n\t\tb.lowerOK = proven\n\t}\n\n\thighEstimate := proven +\n\t\tlnwire.MilliSatoshi(\n\t\t\t0.65*float64(edge.capacity-proven),\n\t\t)\n\tif highEstimate > b.estimate {\n\t\tb.estimate = highEstimate\n\t}\n\tif b.estimate > edge.capacity {\n\t\tb.estimate = edge.capacity\n\t}\n\tb.evidence++\n\n\t// A truthful success at or above a suspected/failed amount refutes\n\t// that observation completely.\n\tif b.upperFail > 0 && proven >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\tif b.suspectAmt > 0 && proven >= b.suspectAmt {\n\t\tb.suspectAmt = 0\n\t\tb.failVotes = 0\n\t}\n\n\tif p, ok := r.penalties[use.key]; ok && proven >= p.amount {\n\t\tdelete(r.penalties, use.key)\n\t}\n\n\tr.persist(use.key)\n}\n\nfunc (r *candidateRouter) addFailurePenalty(key directedKey,\n\tamount lnwire.MilliSatoshi, weight float64) {\n\n\told := r.penalties[key]\n\tif old.amount == 0 || amount < old.amount {\n\t\told.amount = amount\n\t}\n\told.weight += weight\n\tif old.weight > 12 {\n\t\told.weight = 12\n\t}\n\tr.penalties[key] = old\n}\n\nfunc (r *candidateRouter) recordAttributedFailure(use edgeUse,\n\teffectiveAmount lnwire.MilliSatoshi) {\n\n\tif use.from == r.source {\n\t\treturn\n\t}\n\n\tb := r.beliefs[use.key]\n\tedge := r.edges[use.key]\n\tif b == nil || edge == nil {\n\t\treturn\n\t}\n\n\tr.addFailurePenalty(use.key, effectiveAmount, 2.8)\n\n\tif b.failVotes == 0 {\n\t\tb.failVotes = 1\n\t\tb.suspectAmt = effectiveAmount\n\t\tr.persist(use.key)\n\t\treturn\n\t}\n\n\tb.failVotes++\n\tif b.suspectAmt == 0 || effectiveAmount < b.suspectAmt {\n\t\tb.suspectAmt = effectiveAmount\n\t}\n\n\t// Only corroborated attribution becomes a hard upper bound.\n\tbound := b.suspectAmt\n\tif b.upperFail == 0 || bound < b.upperFail {\n\t\tb.upperFail = bound\n\t}\n\tif b.lowerOK >= b.upperFail {\n\t\t// Success is truthful, so contradictory failure attribution must\n\t\t// not erase the proven lower bound.\n\t\tb.upperFail = 0\n\t\tb.failVotes = 1\n\t\tb.suspectAmt = effectiveAmount\n\t\tr.persist(use.key)\n\t\treturn\n\t}\n\n\tdepletedEstimate := effectiveAmount / 4\n\tif b.estimate == 0 || depletedEstimate < b.estimate {\n\t\tb.estimate = depletedEstimate\n\t}\n\tb.evidence++\n\tr.persist(use.key)\n}\n\nfunc (r *candidateRouter) failureUse(rt *route.Route,\n\tsource route.Vertex) (edgeUse, bool) {\n\n\tuses := routeUses(rt)\n\tif len(uses) == 0 {\n\t\treturn edgeUse{}, false\n\t}\n\n\tfailIdx := -1\n\tif source == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\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 || failIdx >= len(uses) {\n\t\treturn edgeUse{}, false\n\t}\n\treturn uses[failIdx], true\n}\n\n// An unreadable failure only proves that something on the route failed.\n// Apply a small, payment-local penalty to the route's least convincing\n// non-local edge. Nothing is written to the persistent belief store.\nfunc (r *candidateRouter) recordUnattributedFailure(rt *route.Route) {\n\tuses := routeUses(rt)\n\n\tvar weakest edgeUse\n\tweakestProb := 2.0\n\tfound := false\n\n\tfor _, use := range uses {\n\t\tif use.from == r.source {\n\t\t\tcontinue\n\t\t}\n\t\tedge := r.edges[use.key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tp := r.edgeProbability(edge, use.amount)\n\t\tif !found || p < weakestProb {\n\t\t\tweakest = use\n\t\t\tweakestProb = p\n\t\t\tfound = true\n\t\t}\n\t}\n\n\tif found {\n\t\teffective := weakest.amount + r.reserved[weakest.key]\n\t\tr.addFailurePenalty(weakest.key, effective, 0.9)\n\t}\n}\n\nfunc routeDeliveryAmount(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 (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 _, use := range routeUses(rt) {\n\t\t\tr.recordSuccess(use)\n\t\t}\n\t\t// The reservation remains: in atomic MPP it is held, and in\n\t\t// ordinary MPP the successful transfer consumed that direction's\n\t\t// liquidity.\n\t\tdelete(r.pending, rt)\n\t\treturn nil\n\t}\n\n\tuse, attributed := r.failureUse(rt, result.FailureSource)\n\tif attributed {\n\t\teffective := use.amount\n\t\tif total := r.reserved[use.key]; total > effective {\n\t\t\teffective = total\n\t\t}\n\t\tr.recordAttributedFailure(use, effective)\n\t} else {\n\t\tr.recordUnattributedFailure(rt)\n\t}\n\n\tr.release(rt)\n\n\tdelivered := routeDeliveryAmount(rt)\n\tif delivered > 1 {\n\t\tnext := delivered * 62 / 100\n\t\tif next < 1 {\n\t\t\tnext = 1\n\t\t}\n\t\tif r.retryCap == 0 || next < r.retryCap {\n\t\t\tr.retryCap = next\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 6,
"parent": 2,
"score": 0.5458,
"accepted": true,
"frontier": true,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\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\tcapacity lnwire.MilliSatoshi\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tvotes uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\tpolicyFails map[candidateEdgeKey]uint8\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\trequestNo uint64\n\tsearchSalt uint64\n\tretryHint lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyFails: make(map[candidateEdgeKey]uint8),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\treturn 1\n\t}\n\n\tvar parts uint32\n\tswitch {\n\tcase amt <= 5_000_000:\n\t\tparts = 1\n\tcase amt <= 20_000_000:\n\t\tparts = 3\n\tcase amt <= 50_000_000:\n\t\tparts = 6\n\tdefault:\n\t\tparts = maxParts\n\t}\n\n\tif parts > maxParts {\n\t\tparts = maxParts\n\t}\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\treturn parts\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.055 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tswitch {\n\tcase z > 30:\n\t\treturn 0.005\n\tcase z < -30:\n\t\treturn 0.995\n\tdefault:\n\t\treturn 1 / (1 + math.Exp(z))\n\t}\n}\n\nfunc (r *candidateRouter) beliefSnapshot(\n\te *candidateEdge) (candidateBelief, bool) {\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tstored := candidateBeliefStore.beliefs[e.key]\n\tif stored == nil || (stored.capacity != 0 &&\n\t\tstored.capacity != e.capacity) {\n\n\t\treturn candidateBelief{}, false\n\t}\n\n\treturn *stored, true\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\tbelief, ok := r.beliefSnapshot(e)\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.12 * float64(belief.conf)\n\t\t\tif weight > 0.68 {\n\t\t\t\tweight = 0.68\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.997)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.018)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK &&\n\t\t\t\trequired > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tbounded := 0.997*(1-pos) + 0.018*pos\n\t\t\t\tp = 0.35*p + 0.65*bounded\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, exists := r.localFails[e.key]; exists &&\n\t\trequired >= local.upper {\n\n\t\tswitch {\n\t\tcase local.votes >= 3:\n\t\t\tp *= 0.04\n\t\tcase local.votes == 2:\n\t\t\tp *= 0.12\n\t\tdefault:\n\t\t\tp *= 0.62\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc candidateEdgeHash(key candidateEdgeKey, salt uint64) float64 {\n\th := key.chanID ^ salt ^ 0x9e3779b97f4a7c15\n\tfor _, b := range key.from {\n\t\th ^= uint64(b)\n\t\th *= 0x100000001b3\n\t}\n\tfor _, b := range key.to {\n\t\th ^= uint64(b) + 0x9d\n\t\th *= 0x100000001b3\n\t}\n\n\th ^= h >> 30\n\th *= 0xbf58476d1ce4e5b9\n\th ^= h >> 27\n\th *= 0x94d049bb133111eb\n\th ^= h >> 31\n\n\treturn float64(h>>11) / float64(uint64(1)<<53)\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.012 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tcost += 0.70 +\n\t\t\t1.35*float64(reserved)/float64(e.capacity)\n\t}\n\n\tif r.failures > 0 {\n\t\texploration := 0.12 + 0.035*float64(r.failures)\n\t\tif exploration > 0.85 {\n\t\t\texploration = 0.85\n\t\t}\n\t\tcost += exploration * candidateEdgeHash(\n\t\t\te.key, r.searchSalt,\n\t\t)\n\t}\n\n\treturn cost\n}\n\nfunc (r *candidateRouter) addPenalty(\n\tkey candidateEdgeKey, amount float64) {\n\n\tnext := r.edgePenalty[key] + amount\n\tif next > 12 {\n\t\tnext = 12\n\t}\n\tr.edgePenalty[key] = next\n}\n\ntype candidateLabel struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n\tnextEdge *candidateEdge\n\tnext *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 candidateLabelContains(label *candidateLabel,\n\tnode route.Vertex) bool {\n\n\tfor current := label; current != nil; current = current.next {\n\t\tif current.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAddLabel(labels map[route.Vertex][]*candidateLabel,\n\tlabel *candidateLabel) bool {\n\n\texisting := labels[label.node]\n\tactiveCount := 0\n\n\tfor _, old := range existing {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tactiveCount++\n\n\t\tif old.score <= label.score && old.amt <= label.amt {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor _, old := range existing {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif label.score <= old.score && label.amt <= old.amt {\n\t\t\told.active = false\n\t\t\tactiveCount--\n\t\t}\n\t}\n\n\tif activeCount >= 16 {\n\t\tvar worst *candidateLabel\n\t\tfor _, old := range existing {\n\t\t\tif !old.active {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst == nil || old.score > worst.score {\n\t\t\t\tworst = old\n\t\t\t}\n\t\t}\n\t\tif worst != nil && worst.score <= label.score {\n\t\t\treturn false\n\t\t}\n\t\tif worst != nil {\n\t\t\tworst.active = false\n\t\t}\n\t}\n\n\tlabel.active = true\n\tlabels[label.node] = append(existing, label)\n\treturn true\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\ttarget := &candidateLabel{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamt: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidateLabel{\n\t\tr.spec.Target: []*candidateLabel{target},\n\t}\n\tpq := &candidateLabelQueue{}\n\theap.Push(pq, target)\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\tif candidateLabelContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 14 * float64(fee) / denom\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 +\n\t\t\t\t\tr.edgeCost(edge, amtOver) + feeCost,\n\t\t\t\tamt: sending,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tnext: item,\n\t\t\t}\n\t\t\tif candidateAddLabel(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\tpath := make([]*candidateEdge, 0, 8)\n\tfor label := sourceLabel; label.nextEdge != nil; label = label.next {\n\t\tpath = append(path, label.nextEdge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tprobability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tprobability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, probability, 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\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\treturn 1\n\t}\n\n\ttarget := r.plannedParts + r.failures/2\n\tif r.unknownFails >= 2 {\n\t\ttarget++\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\treturn target\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamt, maximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amt < 1 {\n\t\tamt = 1\n\t}\n\tif amt > maximum {\n\t\tamt = maximum\n\t}\n\n\tfor _, existing := range amounts {\n\t\tif existing == amt {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amt)\n}\n\nfunc (r *candidateRouter) reserveTemporarily(rt *route.Route,\n\tadd bool) {\n\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tif add {\n\t\t\tr.reserved[traversal.key] += traversal.amt\n\t\t\tcontinue\n\t\t}\n\n\t\tcurrent := r.reserved[traversal.key]\n\t\tif current <= traversal.amt {\n\t\t\tdelete(r.reserved, traversal.key)\n\t\t} else {\n\t\t\tr.reserved[traversal.key] =\n\t\t\t\tcurrent - traversal.amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) futureProbability(rt *route.Route,\n\tremaining lnwire.MilliSatoshi,\n\tfutureSlots uint32) (float64, bool) {\n\n\tif remaining <= 0 {\n\t\treturn 1, true\n\t}\n\tif futureSlots == 0 {\n\t\treturn 0, false\n\t}\n\n\tr.reserveTemporarily(rt, true)\n\tnextAmount := candidateCeilDiv(remaining, futureSlots)\n\t_, probability, err := r.findRoute(nextAmount)\n\tr.reserveTemporarily(rt, false)\n\n\tif err != nil {\n\t\treturn 0, false\n\t}\n\treturn probability, true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tr.requestNo++\n\tr.searchSalt = r.requestNo*0x9e3779b97f4a7c15 +\n\t\tuint64(r.failures)*0xbf58476d1ce4e5b9\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\twholeRoute, wholeProbability, wholeErr := r.findRoute(amt)\n\n\tif wholeErr == nil {\n\t\tif partsLeft == 1 ||\n\t\t\t(inFlightHtlcs == 0 && r.plannedParts == 1) ||\n\t\t\twholeProbability >= 0.92 {\n\n\t\t\treturn wholeRoute, nil\n\t\t}\n\t}\n\n\ttargetParts := r.targetPartCount()\n\tdesiredSlots := uint32(1)\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\n\tbase := candidateCeilDiv(amt, desiredSlots)\n\tif partsLeft == 1 {\n\t\tbase = amt\n\t}\n\n\tretryHint := r.retryHint\n\tr.retryHint = 0\n\n\tamounts := make([]lnwire.MilliSatoshi, 0, 10)\n\tamounts = candidateAppendAmount(amounts, base, amt)\n\n\tif partsLeft > 1 {\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base*3/4, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base/2, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base/3, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base*5/4, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base*3/2, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, base*2, amt,\n\t\t)\n\t}\n\tif retryHint > 0 {\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, retryHint, amt,\n\t\t)\n\t}\n\tamounts = candidateAppendAmount(amounts, amt, amt)\n\n\tvar bestRoute *route.Route\n\tbestScore := math.Inf(-1)\n\tvar fallbackRoute *route.Route\n\tfallbackScore := math.Inf(-1)\n\tvar lastErr error\n\n\tfor _, shard := range amounts {\n\t\trt, probability, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tif probability < 1e-12 {\n\t\t\tprobability = 1e-12\n\t\t}\n\n\t\tprogress := float64(shard) / float64(base)\n\t\tif progress < 1e-12 {\n\t\t\tprogress = 1e-12\n\t\t}\n\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tfeeRatio := 0.0\n\t\tif delivered > 0 && rt.TotalAmount > delivered {\n\t\t\tfeeRatio = float64(rt.TotalAmount-delivered) /\n\t\t\t\tfloat64(delivered)\n\t\t}\n\n\t\tscore := math.Log(probability) +\n\t\t\t0.34*math.Log(progress) - 5*feeRatio\n\n\t\tif score > fallbackScore {\n\t\t\tfallbackScore = score\n\t\t\tfallbackRoute = rt\n\t\t}\n\n\t\tremaining := amt - shard\n\t\tif remaining > 0 {\n\t\t\tfuture, feasible := r.futureProbability(\n\t\t\t\trt, remaining, partsLeft-1,\n\t\t\t)\n\t\t\tif !feasible {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif future < 1e-12 {\n\t\t\t\tfuture = 1e-12\n\t\t\t}\n\t\t\tscore += 0.16 * math.Log(future)\n\t\t}\n\n\t\tif score > bestScore {\n\t\t\tbestScore = score\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute != nil {\n\t\treturn bestRoute, nil\n\t}\n\tif fallbackRoute != nil {\n\t\treturn fallbackRoute, nil\n\t}\n\tif wholeRoute != nil {\n\t\treturn wholeRoute, nil\n\t}\n\tif lastErr != nil {\n\t\treturn nil, lastErr\n\t}\n\tif wholeErr != nil {\n\t\treturn nil, wholeErr\n\t}\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateStoredBelief(\n\ttraversal candidateTraversal) *candidateBelief {\n\n\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\tif belief == nil || (belief.capacity != 0 &&\n\t\tbelief.capacity != traversal.edge.capacity) {\n\n\t\tbelief = &candidateBelief{\n\t\t\tcapacity: traversal.edge.capacity,\n\t\t}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t}\n\tif belief.capacity == 0 {\n\t\tbelief.capacity = traversal.edge.capacity\n\t}\n\treturn belief\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tbelief := candidateStoredBelief(traversal)\n\n\t\tif required > belief.lowerOK {\n\t\t\tbelief.lowerOK = required\n\t\t}\n\n\t\testimate := traversal.edge.capacity * 92 / 100\n\t\tif required > estimate {\n\t\t\testimate = required\n\t\t}\n\t\tif estimate > belief.estimate {\n\t\t\tbelief.estimate = estimate\n\t\t}\n\t\tif belief.conf < 8 {\n\t\t\tbelief.conf++\n\t\t}\n\n\t\tif belief.upperFail > 0 &&\n\t\t\trequired >= belief.upperFail {\n\n\t\t\tbelief.upperFail = 0\n\t\t}\n\t\tif belief.suspectAmt > 0 &&\n\t\t\trequired >= belief.suspectAmt {\n\n\t\t\tbelief.suspectAmt = 0\n\t\t\tbelief.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.edgePenalty[traversal.key] *= 0.35\n\t\tr.policyFails[traversal.key] = 0\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief := candidateBeliefStore.beliefs[key]; belief != nil {\n\t\t\tif belief.lowerOK > used {\n\t\t\t\tbelief.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tbelief.lowerOK = 0\n\t\t\t}\n\t\t\tif belief.estimate > used {\n\t\t\t\tbelief.estimate -= used\n\t\t\t} else {\n\t\t\t\tbelief.estimate = 0\n\t\t\t}\n\t\t\tif belief.upperFail > used {\n\t\t\t\tbelief.upperFail -= used\n\t\t\t} else {\n\t\t\t\tbelief.upperFail = 0\n\t\t\t}\n\t\t\tif belief.suspectAmt > used {\n\t\t\t\tbelief.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tbelief.suspectAmt = 0\n\t\t\t\tbelief.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\tcapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\tcapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\tcapacity = edge.capacity\n\t\t}\n\n\t\tif reverseBelief.capacity != 0 &&\n\t\t\treverseBelief.capacity != capacity {\n\n\t\t\t*reverseBelief = candidateBelief{\n\t\t\t\tcapacity: capacity,\n\t\t\t}\n\t\t} else {\n\t\t\treverseBelief.capacity = capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif capacity > 0 {\n\t\t\tif reverseBelief.lowerOK > capacity {\n\t\t\t\treverseBelief.lowerOK = capacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > capacity {\n\t\t\t\treverseBelief.estimate = capacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif capacity > 0 &&\n\t\t\t\treverseBelief.upperFail > capacity {\n\n\t\t\t\treverseBelief.upperFail = capacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureEdge(rt *route.Route,\n\tsource route.Vertex) (candidateTraversal, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\tindex := -1\n\tif source == rt.SourcePubKey {\n\t\tindex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tindex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif index < 0 || index >= len(traversals) {\n\t\treturn candidateTraversal{}, false\n\t}\n\treturn traversals[index], true\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversal candidateTraversal) {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tif traversal.key.from == r.source {\n\t\tr.addPenalty(traversal.key, 0.12)\n\t\treturn\n\t}\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tif local.votes < 255 {\n\t\tlocal.votes++\n\t}\n\tr.localFails[traversal.key] = local\n\n\tif local.votes >= 2 {\n\t\tr.addPenalty(traversal.key, 0.90)\n\t} else {\n\t\tr.addPenalty(traversal.key, 0.38)\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tbelief := candidateStoredBelief(traversal)\n\n\tif belief.suspectVotes == 0 {\n\t\tbelief.suspectAmt = required\n\t\tbelief.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif belief.suspectAmt == 0 || required < belief.suspectAmt {\n\t\tbelief.suspectAmt = required\n\t}\n\tif belief.suspectVotes < 255 {\n\t\tbelief.suspectVotes++\n\t}\n\n\tvotesNeeded := uint8(2)\n\tif belief.lowerOK > 0 &&\n\t\tbelief.suspectAmt <= belief.lowerOK {\n\n\t\tvotesNeeded = 3\n\t}\n\n\tif belief.suspectVotes < votesNeeded {\n\t\treturn\n\t}\n\n\tif belief.lowerOK > 0 &&\n\t\tbelief.suspectAmt <= belief.lowerOK {\n\n\t\tif belief.suspectAmt > 1 {\n\t\t\tbelief.lowerOK = belief.suspectAmt - 1\n\t\t} else {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\t}\n\n\tif belief.upperFail == 0 ||\n\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\tbelief.upperFail = belief.suspectAmt\n\t}\n\n\tfailedEstimate := belief.suspectAmt / 4\n\tif belief.estimate == 0 || failedEstimate < belief.estimate {\n\t\tbelief.estimate = failedEstimate\n\t}\n\tif belief.conf < 8 {\n\t\tbelief.conf++\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryHint = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\ttraversals := r.routeTraversals(rt)\n\trouteShare := 0.04\n\tif len(traversals) > 0 {\n\t\trouteShare += 0.18 / float64(len(traversals))\n\t}\n\tfor _, traversal := range traversals {\n\t\tr.addPenalty(traversal.key, routeShare)\n\t}\n\n\tcode := result.Failure.Code()\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tr.recordAttributedLiquidityFailure(traversal)\n\n\t\t\tdelivered := candidateDeliveredAmount(rt)\n\t\t\tif delivered > 1 {\n\t\t\t\tfactor := lnwire.MilliSatoshi(68)\n\t\t\t\tif local := r.localFails[traversal.key];\n\t\t\t\t\tlocal.votes >= 2 {\n\n\t\t\t\t\tfactor = 54\n\t\t\t\t}\n\t\t\t\tr.retryHint = delivered * factor / 100\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tcount := r.policyFails[traversal.key]\n\t\t\tif count < 255 {\n\t\t\t\tcount++\n\t\t\t}\n\t\t\tr.policyFails[traversal.key] = count\n\n\t\t\tif count >= 2 {\n\t\t\t\tr.addPenalty(traversal.key, 7.5)\n\t\t\t} else {\n\t\t\t\tr.addPenalty(traversal.key, 0.9)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tr.unknownFails++\n\tunknownShare := 0.02\n\tif len(traversals) > 0 {\n\t\tunknownShare += 0.40 / float64(len(traversals))\n\t}\n\tfor _, traversal := range traversals {\n\t\tr.addPenalty(traversal.key, unknownShare)\n\t}\n\n\tif r.unknownFails%2 == 0 {\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tif delivered > 1 {\n\t\t\tr.retryHint = delivered * 70 / 100\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 7,
"parent": 0,
"score": 0.5632,
"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 edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype globalEdgeKey struct {\n\tsource route.Vertex\n\tedge edgeKey\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usable(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 persistentBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\tsuccesses float64\n\tfailures float64\n\tlastEpoch uint64\n}\n\nvar beliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[globalEdgeKey]persistentBelief\n\tepochs map[route.Vertex]uint64\n}{\n\tbeliefs: make(map[globalEdgeKey]persistentBelief),\n\tepochs: make(map[route.Vertex]uint64),\n}\n\ntype localBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tsoftFail lnwire.MilliSatoshi\n\thardFail lnwire.MilliSatoshi\n\tfailVotes int\n\tpolicyVotes int\n\tpenalty float64\n}\n\ntype routeResult struct {\n\troute *route.Route\n\tpath []*candidateEdge\n\tscore float64\n}\n\ntype plannedShard struct {\n\tamount lnwire.MilliSatoshi\n\tresult *routeResult\n}\n\ntype pathCandidate struct {\n\tpath []*candidateEdge\n\tcapacity lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tlocalEvidence map[edgeKey]*localBelief\n\tedgePenalty map[edgeKey]float64\n\trouteFailures map[string]int\n\trouteFailAmt map[string]lnwire.MilliSatoshi\n\n\theldReservations map[edgeKey]lnwire.MilliSatoshi\n\tplanReservations map[edgeKey]lnwire.MilliSatoshi\n\tplan []plannedShard\n\tplanDirty bool\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\tbeliefStore.Lock()\n\tbeliefStore.epochs[source]++\n\tepoch := beliefStore.epochs[source]\n\tbeliefStore.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tlocalEvidence: make(map[edgeKey]*localBelief),\n\t\tedgePenalty: make(map[edgeKey]float64),\n\t\trouteFailures: make(map[string]int),\n\t\trouteFailAmt: make(map[string]lnwire.MilliSatoshi),\n\t\theldReservations: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tplanReservations: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tplanDirty: true,\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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: edgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.46 * math.Exp(-x/0.05)\n\thighMode := 0.54 / (1 + math.Exp((x-0.90)/0.055))\n\n\treturn clampProbability(lowMode + highMode)\n}\n\nfunc (r *candidateRouter) persistentProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tkey := globalEdgeKey{source: r.source, edge: edge.key}\n\n\tbeliefStore.Lock()\n\tbelief, ok := beliefStore.beliefs[key]\n\tbeliefStore.Unlock()\n\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tage := uint64(0)\n\tif r.epoch > belief.lastEpoch {\n\t\tage = r.epoch - belief.lastEpoch\n\t}\n\tfreshness := math.Exp(-float64(age) / 8.0)\n\n\tp := prior\n\n\tif belief.lowerOK > 0 && belief.successes > 0 {\n\t\tsuccessP := prior\n\t\tif amt <= belief.lowerOK {\n\t\t\tsuccessP = 0.995\n\t\t} else {\n\t\t\tratio := float64(belief.lowerOK) / float64(amt)\n\t\t\tsuccessP = prior + (0.97-prior)*ratio*ratio\n\t\t}\n\n\t\tconfidence := (1 - math.Exp(-1.4*belief.successes)) *\n\t\t\tfreshness\n\t\tp = p*(1-confidence) + successP*confidence\n\t}\n\n\tif belief.upperFail > belief.lowerOK && belief.failures > 0 {\n\t\tratio := float64(amt) / float64(belief.upperFail)\n\t\tfailP := 0.02\n\t\tif ratio < 1 {\n\t\t\tfailP = 0.02 + 0.80*(1-ratio)\n\t\t}\n\n\t\tconfidence := (1 - math.Exp(-belief.failures)) *\n\t\t\tfreshness\n\t\tp = p*(1-confidence) + failP*confidence\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\ttotalDemand lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tif r.localBalances[edge.chanID] < totalDemand {\n\t\t\treturn 0.005\n\t\t}\n\t\treturn 0.995\n\t}\n\n\tp := r.persistentProbability(edge, totalDemand)\n\tlocal := r.localEvidence[edge.key]\n\tif local == nil {\n\t\treturn p\n\t}\n\n\tif local.lowerOK > 0 && totalDemand <= local.lowerOK {\n\t\tp = 0.997\n\t}\n\n\tif local.softFail > 0 && totalDemand >= local.softFail {\n\t\tfactor := 0.30\n\t\tif local.failVotes >= 2 {\n\t\t\tfactor = 0.04\n\t\t}\n\t\tp *= factor\n\t} else if local.softFail > 0 {\n\t\tratio := float64(totalDemand) / float64(local.softFail)\n\t\tp *= 1 - 0.65*ratio\n\t}\n\n\tp *= math.Exp(-0.20 * local.penalty)\n\treturn clampProbability(p)\n}\n\nfunc combinedReservations(a, b map[edgeKey]lnwire.MilliSatoshi,\n\tkey edgeKey) lnwire.MilliSatoshi {\n\n\treturn a[key] + b[key]\n}\n\nfunc (r *candidateRouter) edgeAllowed(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) bool {\n\n\tif !edge.usable(amt) {\n\t\treturn false\n\t}\n\n\treserved := combinedReservations(\n\t\tr.heldReservations, extra, edge.key,\n\t)\n\ttotalDemand := amt + reserved\n\tif totalDemand > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.from == r.source &&\n\t\ttotalDemand > r.localBalances[edge.chanID] {\n\n\t\treturn false\n\t}\n\n\tlocal := r.localEvidence[edge.key]\n\tif local != nil {\n\t\tif local.hardFail > 0 && totalDemand >= local.hardFail {\n\t\t\treturn false\n\t\t}\n\t\tif local.policyVotes >= 2 {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) 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) rawRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tbanned map[edgeKey]bool) (*routeResult, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tbestScore := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\tbestAmount := map[route.Vertex]lnwire.MilliSatoshi{\n\t\tr.spec.Target: amt,\n\t}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tif item.score > bestScore[item.node]+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif banned != nil && banned[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.amount\n\t\t\tif !r.edgeAllowed(edge, amtOver, reservations) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := combinedReservations(\n\t\t\t\tr.heldReservations, reservations, edge.key,\n\t\t\t)\n\t\t\tprobability := r.edgeProbability(\n\t\t\t\tedge, amtOver+reserved,\n\t\t\t)\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tsending := amtOver\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tfeeRatio := float64(fee) /\n\t\t\t\tfloat64(maxMSat(amt, 1))\n\t\t\tedgeScore := -math.Log(probability) +\n\t\t\t\t20*feeRatio + 0.006 +\n\t\t\t\tr.edgePenalty[edge.key]\n\n\t\t\tnewScore := item.score + edgeScore\n\t\t\toldScore, ok := bestScore[edge.from]\n\t\t\tif ok && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.from] = newScore\n\t\t\tbestAmount[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tamount: sending,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := bestScore[r.source]\n\tif !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\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.to\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_ = bestAmount\n\treturn &routeResult{\n\t\troute: rt,\n\t\tpath: path,\n\t\tscore: score,\n\t}, nil\n}\n\nfunc routeSignature(path []*candidateEdge) string {\n\tsig := \"\"\n\tfor _, edge := range path {\n\t\tsig += fmt.Sprintf(\"%d:%x/\", edge.chanID, edge.from[:4])\n\t}\n\treturn sig\n}\n\nfunc (r *candidateRouter) adjustedRouteScore(result *routeResult,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tsig := routeSignature(result.path)\n\tcount := r.routeFailures[sig]\n\tif count == 0 {\n\t\treturn result.score\n\t}\n\n\tfailedAmt := r.routeFailAmt[sig]\n\tfactor := 1.5\n\tif failedAmt > 0 && amt*10 < failedAmt*7 {\n\t\tfactor = 0.20\n\t}\n\n\treturn result.score + float64(count)*factor\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) (*routeResult, error) {\n\n\tbase, err := r.rawRoute(amt, reservations, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbest := base\n\tbestScore := r.adjustedRouteScore(base, amt)\n\tif r.routeFailures[routeSignature(base.path)] == 0 {\n\t\treturn best, nil\n\t}\n\n\tfor _, edge := range base.path {\n\t\tbanned := map[edgeKey]bool{edge.key: true}\n\t\talternative, altErr := r.rawRoute(\n\t\t\tamt, reservations, banned,\n\t\t)\n\t\tif altErr != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tscore := r.adjustedRouteScore(alternative, amt)\n\t\tif score < bestScore {\n\t\t\tbest = alternative\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\treturn best, 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\tn := len(path)\n\tamtOver := make([]lnwire.MilliSatoshi, n)\n\texpiryOver := make([]uint32, n)\n\n\tamtOver[n-1] = amt\n\texpiryOver[n-1] = finalCltvDelta\n\n\tfor i := n - 2; i >= 0; i-- {\n\t\tforwardEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, n)\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < n-1 {\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.to,\n\t\t\tChannelID: edge.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 maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc minMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc routeChannelAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc (r *candidateRouter) pathFeasible(path []*candidateEdge,\n\tdelivery lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi,\n\tminProbability float64) bool {\n\n\trt, err := r.buildRoute(delivery, path)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tfor i, edge := range path {\n\t\tamt := routeChannelAmount(rt, i)\n\t\tif !r.edgeAllowed(edge, amt, reservations) {\n\t\t\treturn false\n\t\t}\n\n\t\treserved := combinedReservations(\n\t\t\tr.heldReservations, reservations, edge.key,\n\t\t)\n\t\tif r.edgeProbability(edge, amt+reserved) < minProbability {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) pathCapacity(path []*candidateEdge,\n\tlow, high lnwire.MilliSatoshi,\n\treservations map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif low <= 0 {\n\t\tlow = 1\n\t}\n\tif high < low ||\n\t\t!r.pathFeasible(path, low, reservations, 0.12) {\n\n\t\treturn 0\n\t}\n\n\tbest := low\n\tleft, right := low, high\n\tfor left <= right {\n\t\tmid := left + (right-left)/2\n\t\tif r.pathFeasible(path, mid, reservations, 0.12) {\n\t\t\tbest = mid\n\t\t\tleft = mid + 1\n\t\t} else {\n\t\t\tright = mid - 1\n\t\t}\n\t}\n\n\treturn best\n}\n\nfunc addPathReservation(reservations map[edgeKey]lnwire.MilliSatoshi,\n\tresult *routeResult, sign int) {\n\n\tfor i, edge := range result.path {\n\t\tamt := routeChannelAmount(result.route, i)\n\t\tif sign > 0 {\n\t\t\treservations[edge.key] += amt\n\t\t\tcontinue\n\t\t}\n\n\t\tif reservations[edge.key] <= amt {\n\t\t\tdelete(reservations, edge.key)\n\t\t} else {\n\t\t\treservations[edge.key] -= amt\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) clearPlan() {\n\tr.plan = nil\n\tr.planReservations = make(map[edgeKey]lnwire.MilliSatoshi)\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts uint32) {\n\n\tr.clearPlan()\n\tr.planDirty = false\n\n\tif total <= 0 || parts == 0 {\n\t\treturn\n\t}\n\n\tif parts == 1 || total <= 20_000_000 {\n\t\tresult, err := r.findRoute(\n\t\t\ttotal, r.planReservations,\n\t\t)\n\t\tif err == nil {\n\t\t\tr.plan = append(r.plan, plannedShard{\n\t\t\t\tamount: total,\n\t\t\t\tresult: result,\n\t\t\t})\n\t\t\taddPathReservation(\n\t\t\t\tr.planReservations, result, 1,\n\t\t\t)\n\t\t}\n\t\treturn\n\t}\n\n\tmaxParts := int(parts)\n\tbase := total / lnwire.MilliSatoshi(maxParts*3)\n\tif base < 1_000 {\n\t\tbase = 1_000\n\t}\n\tif base > total {\n\t\tbase = total\n\t}\n\n\tdiscoveryReservations := make(map[edgeKey]lnwire.MilliSatoshi)\n\tvar candidates []pathCandidate\n\tvar aggregateCapacity lnwire.MilliSatoshi\n\n\tfor i := 0; i < maxParts && aggregateCapacity < total; i++ {\n\t\tresult, err := r.findRoute(base, discoveryReservations)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tcapacity := r.pathCapacity(\n\t\t\tresult.path, base, total, discoveryReservations,\n\t\t)\n\t\tif capacity < base {\n\t\t\tbreak\n\t\t}\n\n\t\tcandidates = append(candidates, pathCandidate{\n\t\t\tpath: result.path,\n\t\t\tcapacity: capacity,\n\t\t\tscore: result.score,\n\t\t})\n\t\taggregateCapacity += capacity\n\n\t\tcapRoute, err := r.buildRoute(capacity, result.path)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\taddPathReservation(discoveryReservations, &routeResult{\n\t\t\troute: capRoute,\n\t\t\tpath: result.path,\n\t\t}, 1)\n\t}\n\n\tif len(candidates) == 0 {\n\t\treturn\n\t}\n\n\tsort.SliceStable(candidates, func(i, j int) bool {\n\t\tif candidates[i].capacity == candidates[j].capacity {\n\t\t\treturn candidates[i].score < candidates[j].score\n\t\t}\n\t\treturn candidates[i].capacity > candidates[j].capacity\n\t})\n\n\tremaining := total\n\tfor _, candidate := range candidates {\n\t\tif remaining <= 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tshard := minMSat(candidate.capacity, remaining)\n\t\tresultRoute, err := r.buildRoute(shard, candidate.path)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tresult := &routeResult{\n\t\t\troute: resultRoute,\n\t\t\tpath: candidate.path,\n\t\t\tscore: candidate.score,\n\t\t}\n\t\tif !r.pathFeasible(\n\t\t\tcandidate.path, shard,\n\t\t\tr.planReservations, 0.08,\n\t\t) {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.plan = append(r.plan, plannedShard{\n\t\t\tamount: shard,\n\t\t\tresult: result,\n\t\t})\n\t\taddPathReservation(r.planReservations, result, 1)\n\t\tremaining -= shard\n\t}\n\n\tif len(r.plan) == 0 {\n\t\treturn\n\t}\n\n\tif remaining > 0 && len(r.plan) < maxParts {\n\t\tshard := remaining\n\t\tfor shard > 0 {\n\t\t\tresult, err := r.findRoute(\n\t\t\t\tshard, r.planReservations,\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\tr.plan = append(r.plan, plannedShard{\n\t\t\t\t\tamount: shard,\n\t\t\t\t\tresult: result,\n\t\t\t\t})\n\t\t\t\taddPathReservation(\n\t\t\t\t\tr.planReservations, result, 1,\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif shard <= 1_000 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tshard /= 2\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 inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\n\tif r.planDirty || len(r.plan) == 0 {\n\t\tr.makePlan(amt, partsLeft)\n\t}\n\n\tif len(r.plan) > 0 {\n\t\tshard := r.plan[0]\n\t\tif shard.amount > amt {\n\t\t\tr.makePlan(amt, partsLeft)\n\t\t\tif len(r.plan) == 0 {\n\t\t\t\treturn nil, errors.New(\"no route found\")\n\t\t\t}\n\t\t\tshard = r.plan[0]\n\t\t}\n\n\t\tr.plan = r.plan[1:]\n\t\taddPathReservation(\n\t\t\tr.planReservations, shard.result, -1,\n\t\t)\n\t\treturn shard.result.route, nil\n\t}\n\n\tshard := amt\n\tfor {\n\t\tresult, err := r.findRoute(\n\t\t\tshard, make(map[edgeKey]lnwire.MilliSatoshi),\n\t\t)\n\t\tif err == nil {\n\t\t\treturn result.route, nil\n\t\t}\n\n\t\tif partsLeft <= 1 || shard <= 1_000 {\n\t\t\treturn nil, err\n\t\t}\n\t\tshard /= 2\n\t}\n}\n\nfunc routeEdges(rt *route.Route) []edgeKey {\n\tedges := make([]edgeKey, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tedges[i] = edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\treturn edges\n}\n\nfunc (r *candidateRouter) routePathSignature(rt *route.Route) string {\n\tedges := routeEdges(rt)\n\tsig := \"\"\n\tfor _, edge := range edges {\n\t\tsig += fmt.Sprintf(\"%d:%x/\", edge.chanID, edge.from[:4])\n\t}\n\treturn sig\n}\n\nfunc (r *candidateRouter) updatePersistentSuccess(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tglobalKey := globalEdgeKey{source: r.source, edge: key}\n\n\tbeliefStore.Lock()\n\tbelief := beliefStore.beliefs[globalKey]\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\tbelief.failures = 0\n\t}\n\tbelief.successes = math.Min(4, belief.successes+1)\n\tbelief.lastEpoch = r.epoch\n\tbeliefStore.beliefs[globalKey] = belief\n\tbeliefStore.Unlock()\n}\n\nfunc (r *candidateRouter) updatePersistentFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi, weight float64) {\n\n\tglobalKey := globalEdgeKey{source: r.source, edge: key}\n\n\tbeliefStore.Lock()\n\tbelief := beliefStore.beliefs[globalKey]\n\tif amt > belief.lowerOK &&\n\t\t(belief.upperFail == 0 || amt < belief.upperFail) {\n\n\t\tbelief.upperFail = amt\n\t}\n\tbelief.failures = math.Min(2, belief.failures+weight)\n\tbelief.lastEpoch = r.epoch\n\tbeliefStore.beliefs[globalKey] = belief\n\tbeliefStore.Unlock()\n}\n\nfunc (r *candidateRouter) locateFailure(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tedges := routeEdges(rt)\n\tsig := r.routePathSignature(rt)\n\n\tif result.Failure == nil {\n\t\tfor i, key := range edges {\n\t\t\tamt := routeChannelAmount(rt, i)\n\t\t\tlocal := r.localEvidence[key]\n\t\t\tif local == nil {\n\t\t\t\tlocal = &localBelief{}\n\t\t\t\tr.localEvidence[key] = local\n\t\t\t}\n\n\t\t\tif amt > local.lowerOK {\n\t\t\t\tlocal.lowerOK = amt\n\t\t\t}\n\t\t\tif local.softFail > 0 && amt >= local.softFail {\n\t\t\t\tlocal.softFail = 0\n\t\t\t\tlocal.hardFail = 0\n\t\t\t\tlocal.failVotes = 0\n\t\t\t}\n\t\t\tlocal.penalty *= 0.25\n\t\t\tr.edgePenalty[key] *= 0.25\n\t\t\tr.updatePersistentSuccess(key, amt)\n\t\t}\n\n\t\tdelete(r.routeFailures, sig)\n\t\tdelete(r.routeFailAmt, sig)\n\t\taddPathReservation(r.heldReservations, &routeResult{\n\t\t\troute: rt,\n\t\t\tpath: func() []*candidateEdge {\n\t\t\t\tpath := make([]*candidateEdge, len(edges))\n\t\t\t\tfor i, key := range edges {\n\t\t\t\t\tpath[i] = &candidateEdge{key: key}\n\t\t\t\t}\n\t\t\t\treturn path\n\t\t\t}(),\n\t\t}, 1)\n\n\t\treturn nil\n\t}\n\n\tr.planDirty = true\n\tr.clearPlan()\n\n\tr.routeFailures[sig]++\n\tdeliveryAmt := rt.Hops[len(rt.Hops)-1].AmtToForward\n\toldFailAmt, ok := r.routeFailAmt[sig]\n\tif !ok || deliveryAmt < oldFailAmt {\n\t\tr.routeFailAmt[sig] = deliveryAmt\n\t}\n\n\tfailIdx := r.locateFailure(rt, result.FailureSource)\n\tif failIdx < 0 || failIdx >= len(edges) {\n\t\tfor _, key := range edges {\n\t\t\tr.edgePenalty[key] = math.Min(\n\t\t\t\t2.0, r.edgePenalty[key]+0.08,\n\t\t\t)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor i, key := range edges {\n\t\tincrement := 0.03\n\t\tif i == failIdx {\n\t\t\tincrement = 0.55\n\t\t}\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t3.0, r.edgePenalty[key]+increment,\n\t\t)\n\t}\n\n\tkey := edges[failIdx]\n\tamt := routeChannelAmount(rt, failIdx)\n\ttotalDemand := amt + r.heldReservations[key]\n\n\tlocal := r.localEvidence[key]\n\tif local == nil {\n\t\tlocal = &localBelief{}\n\t\tr.localEvidence[key] = local\n\t}\n\tlocal.penalty = math.Min(5, local.penalty+1)\n\n\tswitch result.Failure.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\tif local.softFail == 0 || totalDemand < local.softFail {\n\t\t\tlocal.softFail = totalDemand\n\t\t}\n\t\tlocal.failVotes++\n\n\t\tif local.failVotes >= 2 {\n\t\t\tif local.hardFail == 0 ||\n\t\t\t\ttotalDemand < local.hardFail {\n\n\t\t\t\tlocal.hardFail = totalDemand\n\t\t\t}\n\n\t\t\tweight := 0.35\n\t\t\tif local.failVotes == 2 {\n\t\t\t\tweight = 0.70\n\t\t\t}\n\t\t\tr.updatePersistentFailure(key, amt, weight)\n\t\t}\n\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\tlocal.policyVotes++\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 8,
"parent": 3,
"score": 0.1123,
"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\"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\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n\tstamp uint64\n}\n\ntype candidateNetworkMemory struct {\n\tepoch uint64\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[uint64]*candidateNetworkMemory\n}{\n\tnetworks: make(map[uint64]*candidateNetworkMemory),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tmemoryID uint64\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\t// Successful shards consume or hold directional liquidity for the rest\n\t// of the payment. This is required for both ordinary and atomic MPP.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// These penalties are deliberately local to one payment. They provide\n\t// useful route-level learning even when an onion error is unreadable.\n\tedgePenalty map[candidateEdgeKey]float64\n\n\t// planLimit is the preferred size of the next shard. It survives shard\n\t// successes, producing an up-front partition rather than a new probe\n\t// ladder after every result.\n\tplanLimit lnwire.MilliSatoshi\n\n\tfailureStreak uint32\n\tlastFailedAmt lnwire.MilliSatoshi\n}\n\nfunc candidateHashUint(h, value uint64) uint64 {\n\th ^= value\n\th *= 1099511628211\n\th ^= h >> 32\n\treturn h\n}\n\nfunc candidateHashVertex(h uint64, vertex route.Vertex) uint64 {\n\tfor i := 0; i < len(vertex); i++ {\n\t\th ^= uint64(vertex[i])\n\t\th *= 1099511628211\n\t}\n\n\treturn h\n}\n\nfunc candidateEdgeFingerprint(edge *candidateEdge) uint64 {\n\th := uint64(1469598103934665603)\n\th = candidateHashUint(h, edge.chanID)\n\th = candidateHashVertex(h, edge.from)\n\th = candidateHashVertex(h, edge.to)\n\th = candidateHashUint(h, uint64(edge.capacity))\n\th = candidateHashUint(h, uint64(edge.baseFeeMsat))\n\th = candidateHashUint(h, uint64(edge.feeRatePPM))\n\th = candidateHashUint(h, uint64(edge.timeLockDelta))\n\th = candidateHashUint(h, uint64(edge.minHTLC))\n\th = candidateHashUint(h, uint64(edge.maxHTLC))\n\treturn h\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tvar topologySum uint64\n\tvar topologyXor uint64\n\tvar edgeCount 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,\n\t\t\tfunc(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},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\n\t\t\t\ttoken := candidateEdgeFingerprint(edge)\n\t\t\t\ttopologySum += token * 0x9e3779b97f4a7c15\n\t\t\t\ttopologyXor ^= token +\n\t\t\t\t\t0x517cc1b727220a95 +\n\t\t\t\t\t(token << 7) + (token >> 3)\n\t\t\t\tedgeCount++\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmemoryID := uint64(1469598103934665603)\n\tmemoryID = candidateHashVertex(memoryID, source)\n\tmemoryID = candidateHashUint(memoryID, topologySum)\n\tmemoryID = candidateHashUint(memoryID, topologyXor)\n\tmemoryID = candidateHashUint(memoryID, edgeCount)\n\tr.memoryID = memoryID\n\n\tcandidateMemory.Lock()\n\tnetwork := candidateMemory.networks[memoryID]\n\tif network == nil {\n\t\tnetwork = &candidateNetworkMemory{\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[memoryID] = network\n\t}\n\n\tnetwork.epoch++\n\tr.epoch = network.epoch\n\tfor key, belief := range network.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// Liquidity is predominantly bimodal. The low mode supports only a small\n// fraction of capacity, while the high mode remains useful until a steep\n// cliff near the channel's capacity.\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.024)\n\thighMode := 1 / (1 + math.Exp(19*(x-0.82)))\n\n\treturn candidateClampProbability(\n\t\t0.5*lowMode + 0.5*highMode,\n\t)\n}\n\nfunc (r *candidateRouter) evidenceConfidence(\n\tbelief candidateBelief) float64 {\n\n\tif belief.stamp == 0 {\n\t\treturn 0\n\t}\n\n\tif belief.stamp >= r.epoch {\n\t\treturn 1\n\t}\n\n\t// Evidence is strongest within its payment. Across payments it is\n\t// softened immediately because settlement and background traffic can\n\t// both move liquidity.\n\tage := float64(r.epoch - belief.stamp - 1)\n\treturn math.Max(0.15, 0.82*math.Exp(-age/9))\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tavailable := r.localBalances[edge.chanID]\n\t\tif totalAmt <= available {\n\t\t\treturn 0.999\n\t\t}\n\n\t\treturn 0.001\n\t}\n\n\tprior := candidatePrior(totalAmt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.stamp == 0 {\n\t\treturn prior\n\t}\n\n\tlearned := prior\n\n\tswitch {\n\tcase belief.lowerOK > 0 && totalAmt <= belief.lowerOK:\n\t\tlearned = 0.995\n\n\tcase belief.upperFail > 0 && totalAmt >= belief.upperFail:\n\t\t// Corroborated blame remains probabilistic. It is never a route\n\t\t// exclusion because apparently valid attribution may be shifted.\n\t\tlearned = 0.025\n\n\tcase belief.lowerOK > 0 &&\n\t\tbelief.upperFail > belief.lowerOK:\n\n\t\tspan := float64(belief.upperFail - belief.lowerOK)\n\t\tposition := float64(totalAmt-belief.lowerOK) / span\n\t\tposition = math.Max(0, math.Min(1, position))\n\t\tlearned = 0.995*(1-position) + 0.025*position\n\n\tcase belief.lowerOK > 0 && totalAmt > belief.lowerOK:\n\t\tratio := float64(belief.lowerOK) / float64(totalAmt)\n\t\tlearned = prior + (0.995-prior)*ratio*ratio\n\n\tcase belief.upperFail > 0 && totalAmt < belief.upperFail:\n\t\tratio := float64(totalAmt) /\n\t\t\tfloat64(belief.upperFail)\n\t\tlearned = prior * (1 - 0.65*ratio*ratio)\n\t}\n\n\t// A single blame is useful as a hint, but not as a liquidity bound.\n\tif belief.upperFail == 0 && belief.suspectAmt > 0 &&\n\t\ttotalAmt >= belief.suspectAmt {\n\n\t\tlearned *= 0.70\n\t}\n\n\tconfidence := r.evidenceConfidence(belief)\n\treturn candidateClampProbability(\n\t\tconfidence*learned + (1-confidence)*prior,\n\t)\n}\n\ntype candidateItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype candidateQueue []*candidateItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype candidatePath struct {\n\troute *route.Route\n\tedges []*candidateEdge\n\tprobability float64\n\tpressure float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tamounts := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tamounts[r.spec.Target] = amt\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateItem)\n\t\tbestScore, ok := scores[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amounts[item.node]\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usable(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\theld := r.reserved[edge.key]\n\t\t\ttotalLiquidity := arriving + held\n\t\t\tif totalLiquidity < arriving ||\n\t\t\t\ttotalLiquidity > edge.capacity {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source &&\n\t\t\t\ttotalLiquidity >\n\t\t\t\t\tr.localBalances[edge.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.probability(\n\t\t\t\tedge, totalLiquidity,\n\t\t\t)\n\n\t\t\tedgeCost := -math.Log(probability) + 0.065\n\t\t\tif arriving > 0 {\n\t\t\t\tedgeCost += 4 * float64(fee) /\n\t\t\t\t\tfloat64(arriving)\n\t\t\t}\n\t\t\tedgeCost += r.edgePenalty[edge.key]\n\n\t\t\t// Successful atomic-MPP siblings should normally use another\n\t\t\t// corridor. This remains a soft cost so unavoidable shared\n\t\t\t// source or destination bottlenecks are still usable.\n\t\t\tif held > 0 {\n\t\t\t\tdenominator := edge.capacity\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\tdenominator =\n\t\t\t\t\t\tr.localBalances[edge.chanID]\n\t\t\t\t}\n\n\t\t\t\treservationCost := 1.25\n\t\t\t\tif denominator > 0 {\n\t\t\t\t\treservationCost += 2.5 *\n\t\t\t\t\t\tfloat64(held) /\n\t\t\t\t\t\tfloat64(denominator)\n\t\t\t\t}\n\t\t\t\tedgeCost += reservationCost\n\t\t\t}\n\n\t\t\tnewScore := item.score + edgeCost\n\t\t\toldScore, visited := scores[edge.from]\n\t\t\tif visited && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.from] = newScore\n\t\t\tamounts[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\n\t\t\theap.Push(pq, &candidateItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, edges, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpathProbability := 1.0\n\tpressure := 0.0\n\n\tfor i, edge := range edges {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\ttotalLiquidity := amtOver + r.reserved[edge.key]\n\n\t\tpathProbability *= r.probability(\n\t\t\tedge, totalLiquidity,\n\t\t)\n\n\t\tdenominator := edge.capacity\n\t\tif edge.from == r.source {\n\t\t\tdenominator = r.localBalances[edge.chanID]\n\t\t}\n\n\t\tif denominator > 0 {\n\t\t\tratio := float64(totalLiquidity) /\n\t\t\t\tfloat64(denominator)\n\t\t\tif ratio > pressure {\n\t\t\t\tpressure = ratio\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &candidatePath{\n\t\troute: rt,\n\t\tedges: edges,\n\t\tprobability: pathProbability,\n\t\tpressure: pressure,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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 := 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.to,\n\t\t\tChannelID: edge.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}, path, nil\n}\n\nfunc candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tdivisor uint32) lnwire.MilliSatoshi {\n\n\tif divisor <= 1 {\n\t\treturn amt\n\t}\n\n\td := lnwire.MilliSatoshi(divisor)\n\treturn amt/d + func() lnwire.MilliSatoshi {\n\t\tif amt%d != 0 {\n\t\t\treturn 1\n\t\t}\n\t\treturn 0\n\t}()\n}\n\nfunc candidateMaxUint32(a, b uint32) uint32 {\n\tif a > b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc candidateMinUint32(a, b uint32) uint32 {\n\tif a < b {\n\t\treturn a\n\t}\n\n\treturn b\n}\n\nfunc (r *candidateRouter) initialPlan(\n\tamt lnwire.MilliSatoshi,\n\tpartsLeft uint32) (*candidatePath, error) {\n\n\tfull, fullErr := r.findRoute(amt)\n\n\tif fullErr == nil &&\n\t\t(amt <= 5_000_000 ||\n\t\t\tfull.probability >= 0.62 ||\n\t\t\tfull.pressure <= 0.012) {\n\n\t\tr.planLimit = amt\n\t\treturn full, nil\n\t}\n\n\tif partsLeft <= 1 {\n\t\tif fullErr != nil {\n\t\t\treturn nil, fullErr\n\t\t}\n\n\t\tr.planLimit = amt\n\t\treturn full, nil\n\t}\n\n\tparts := uint32(2)\n\n\tif fullErr != nil {\n\t\tparts = partsLeft\n\t} else {\n\t\tcapacityParts := uint32(math.Ceil(\n\t\t\tfull.pressure / 0.045,\n\t\t))\n\t\tif capacityParts < 2 {\n\t\t\tcapacityParts = 2\n\t\t}\n\t\tparts = capacityParts\n\n\t\tvar riskParts uint32\n\t\tswitch {\n\t\tcase full.probability < 0.01:\n\t\t\triskParts = 12\n\t\tcase full.probability < 0.05:\n\t\t\triskParts = 8\n\t\tcase full.probability < 0.15:\n\t\t\triskParts = 5\n\t\tcase full.probability < 0.35:\n\t\t\triskParts = 3\n\t\tdefault:\n\t\t\triskParts = 2\n\t\t}\n\t\tparts = candidateMaxUint32(parts, riskParts)\n\t}\n\n\tparts = candidateMinUint32(parts, partsLeft)\n\tif parts == 0 {\n\t\tparts = 1\n\t}\n\n\tr.planLimit = candidateCeilDiv(amt, parts)\n\treturn nil, fullErr\n}\n\nfunc (r *candidateRouter) findNearbyRoute(desired, minimum,\n\tremaining lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tpath, err := r.findRoute(desired)\n\tif err == nil {\n\t\treturn path, nil\n\t}\n\tlastErr := err\n\n\t// A shard can be too small for advertised min-HTLC policies. Search\n\t// upward before assuming that no route exists.\n\tup := desired\n\tfor i := 0; i < 4 && up < remaining; i++ {\n\t\tnext := up + up/2\n\t\tif next <= up || next > remaining {\n\t\t\tnext = remaining\n\t\t}\n\t\tif next == up {\n\t\t\tbreak\n\t\t}\n\n\t\tup = next\n\t\tpath, routeErr := r.findRoute(up)\n\t\tif routeErr == nil {\n\t\t\tr.planLimit = up\n\t\t\treturn path, nil\n\t\t}\n\t\tlastErr = routeErr\n\t}\n\n\t// Conversely, nominal capacity or local balance may require a smaller\n\t// shard. Never go below the amount needed to finish within MaxParts.\n\tdown := desired\n\tfor i := 0; i < 6 && down > minimum; i++ {\n\t\tnext := down * 3 / 4\n\t\tif next < minimum {\n\t\t\tnext = minimum\n\t\t}\n\t\tif next >= down {\n\t\t\tbreak\n\t\t}\n\n\t\tdown = next\n\t\tpath, routeErr := r.findRoute(down)\n\t\tif routeErr == nil {\n\t\t\tr.planLimit = down\n\t\t\treturn path, nil\n\t\t}\n\t\tlastErr = routeErr\n\t}\n\n\treturn nil, lastErr\n}\n\n// RequestRoute plans around the part budget from the first request. It uses\n// small enough shards for a bimodal network, favors wider learned corridors\n// for unequal shards, and uses held-liquidity costs to obtain diverse MPP\n// paths without permanently excluding any channel.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tif partsLeft <= 1 {\n\t\tpath, err := r.findRoute(amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn path.route, nil\n\t}\n\n\tif r.planLimit == 0 {\n\t\tfull, err := r.initialPlan(amt, partsLeft)\n\t\tif full != nil {\n\t\t\treturn full.route, nil\n\t\t}\n\t\tif err != nil && r.planLimit == 0 {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tminimum := candidateCeilDiv(amt, partsLeft)\n\tdesired := r.planLimit\n\n\tif desired < minimum {\n\t\tdesired = minimum\n\t}\n\tif desired <= 0 || desired > amt {\n\t\tdesired = amt\n\t}\n\n\tpath, err := r.findNearbyRoute(desired, minimum, amt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdelivered := path.route.Hops[\n\t\tlen(path.route.Hops)-1].AmtToForward\n\n\t// A corridor with a substantially stronger belief can deliberately\n\t// carry a larger unequal shard. The remaining corridors then carry\n\t// smaller shards under the persistent plan limit.\n\tif r.failureStreak == 0 &&\n\t\tdelivered < amt &&\n\t\tpath.probability >= 0.58 {\n\n\t\tlarger := delivered + delivered/2\n\t\tif larger > amt {\n\t\t\tlarger = amt\n\t\t}\n\n\t\tif larger > delivered {\n\t\t\tbiggerPath, biggerErr := r.findRoute(larger)\n\t\t\tif biggerErr == nil &&\n\t\t\t\tbiggerPath.probability >= 0.48 &&\n\t\t\t\tbiggerPath.probability >=\n\t\t\t\t\tpath.probability*0.72 &&\n\t\t\t\tbiggerPath.pressure <= 0.10 {\n\n\t\t\t\treturn biggerPath.route, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn path.route, nil\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tedgeIndex int) lnwire.MilliSatoshi {\n\n\tif edgeIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[edgeIndex-1].AmtToForward\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tedgeIndex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif edgeIndex > 0 {\n\t\tfrom = rt.Hops[edgeIndex-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[edgeIndex].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc candidateFailureEdge(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc candidateIsLiquidityFailure(failure any) bool {\n\t_, ok := failure.(*lnwire.FailTemporaryChannelFailure)\n\treturn ok\n}\n\nfunc candidateIsPolicyFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey, belief candidateBelief) {\n\n\tbelief.stamp = r.epoch\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tnetwork := candidateMemory.networks[r.memoryID]\n\tif network == nil {\n\t\tnetwork = &candidateNetworkMemory{\n\t\t\tepoch: r.epoch,\n\t\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.memoryID] = network\n\t}\n\tnetwork.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tif totalAmt > belief.lowerOK {\n\t\tbelief.lowerOK = totalAmt\n\t}\n\n\t// Settlements are truthful and therefore override conflicting blame.\n\tif belief.upperFail > 0 && totalAmt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 &&\n\t\ttotalAmt >= belief.suspectAmt {\n\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tcorroborates := false\n\tif belief.suspectAmt > 0 {\n\t\tlow := belief.suspectAmt * 65 / 100\n\t\thigh := belief.suspectAmt * 145 / 100\n\t\tcorroborates = totalAmt >= low && totalAmt <= high\n\t}\n\n\tif corroborates {\n\t\tif belief.suspectVotes < 255 {\n\t\t\tbelief.suspectVotes++\n\t\t}\n\t\tif totalAmt < belief.suspectAmt {\n\t\t\tbelief.suspectAmt = totalAmt\n\t\t}\n\t} else {\n\t\tbelief.suspectAmt = totalAmt\n\t\tbelief.suspectVotes = 1\n\t}\n\n\trequiredVotes := uint8(3)\n\tif belief.lowerOK > 0 && totalAmt <= belief.lowerOK {\n\t\t// A failure that contradicts a truthful settlement is especially\n\t\t// likely to be shifted, although genuine drift remains possible.\n\t\trequiredVotes = 4\n\t}\n\n\tif belief.suspectVotes >= requiredVotes {\n\t\tif totalAmt <= belief.lowerOK {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) noteFailureAmount(\n\tdelivered lnwire.MilliSatoshi) {\n\n\tsimilar := false\n\tif r.lastFailedAmt > 0 {\n\t\tlow := r.lastFailedAmt * 70 / 100\n\t\thigh := r.lastFailedAmt * 140 / 100\n\t\tsimilar = delivered >= low && delivered <= high\n\t}\n\n\tif similar {\n\t\tr.failureStreak++\n\t} else {\n\t\tr.failureStreak = 1\n\t}\n\n\tr.lastFailedAmt = delivered\n}\n\nfunc (r *candidateRouter) lowerPlanLimit(\n\tdelivered lnwire.MilliSatoshi, percent int64) {\n\n\treduced := delivered *\n\t\tlnwire.MilliSatoshi(percent) / 100\n\tif reduced <= 0 {\n\t\treturn\n\t}\n\n\tif r.planLimit == 0 || reduced < r.planLimit {\n\t\tr.planLimit = reduced\n\t}\n}\n\n// ReportAttempt treats success as hard evidence, but treats failure\n// attribution as noisy. Reported and neighboring edges receive local soft\n// penalties so a one-hop shifted blame still moves routing away from the\n// true area. Only repeated compatible blame enters persistent beliefs.\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, totalAmt)\n\t\t\tr.reserved[key] += amtOver\n\t\t\tr.edgePenalty[key] *= 0.25\n\t\t}\n\n\t\tr.failureStreak = 0\n\t\tr.lastFailedAmt = 0\n\t\treturn nil\n\t}\n\n\tr.noteFailureAmount(delivered)\n\n\t// The route certainly failed even when its error cannot be decrypted.\n\t// Penalizing the route locally extracts that truthful information\n\t// without poisoning persistent channel beliefs.\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t12, r.edgePenalty[key]+0.18,\n\t\t)\n\t}\n\n\tfailIndex := candidateFailureEdge(\n\t\trt, result.FailureSource,\n\t)\n\tattributed := failIndex >= 0 &&\n\t\tfailIndex < len(rt.Hops)\n\n\tif attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\textra := 0.9\n\t\tif candidateIsPolicyFailure(result.Failure) {\n\t\t\textra = 1.25\n\t\t}\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t12, r.edgePenalty[key]+extra,\n\t\t)\n\n\t\t// Shifted attribution is constrained to a neighboring hop. Giving\n\t\t// both neighbors smaller penalties makes the true edge less likely\n\t\t// to be selected again without pretending to know which one it is.\n\t\tfor _, adjacent := range []int{\n\t\t\tfailIndex - 1, failIndex + 1,\n\t\t} {\n\t\t\tif adjacent < 0 || adjacent >= len(rt.Hops) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tadjacentKey := candidateRouteEdgeKey(\n\t\t\t\trt, adjacent,\n\t\t\t)\n\t\t\tr.edgePenalty[adjacentKey] = math.Min(\n\t\t\t\t12,\n\t\t\t\tr.edgePenalty[adjacentKey]+0.38,\n\t\t\t)\n\t\t}\n\t}\n\n\tif candidateIsLiquidityFailure(result.Failure) {\n\t\tif attributed {\n\t\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\t\tamtOver := candidateRouteAmount(rt, failIndex)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\t\t\tr.recordFailure(key, totalAmt)\n\t\t}\n\n\t\t// First try another corridor at the jointly planned size. Only\n\t\t// repeated failure changes the partition, avoiding probe ladders.\n\t\tif r.failureStreak >= 2 {\n\t\t\tr.lowerPlanLimit(delivered, 78)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif candidateIsPolicyFailure(result.Failure) {\n\t\t// The encoded route was formed from gossip, so policy failures are\n\t\t// normally stale or shifted information. Change paths, not size.\n\t\treturn nil\n\t}\n\n\t// An unreadable failure conveys no channel identity. Several failures\n\t// at the same size can still justify a modest shard-size adjustment.\n\tif r.failureStreak >= 4 {\n\t\tr.lowerPlanLimit(delivered, 85)\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 9,
"parent": 1,
"score": 0.4415,
"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\"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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalObservation struct {\n\tupper lnwire.MilliSatoshi\n\tweight float64\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalObs map[candidateEdgeKey]candidateLocalObservation\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalObs: make(map[candidateEdgeKey]candidateLocalObservation),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tswitch {\n\tcase amt <= 20_000_000:\n\t\treturn 1\n\n\tcase amt <= 60_000_000:\n\t\tif maxParts < 2 {\n\t\t\treturn maxParts\n\t\t}\n\t\treturn 2\n\n\tdefault:\n\t\t// Larger payments benefit from committing a broad route set\n\t\t// immediately. Failed small shards can later be compensated by\n\t\t// larger shards on corridors that reveal more headroom.\n\t\treturn maxParts\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.985:\n\t\treturn 0.985\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.065 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tswitch {\n\tcase z > 30:\n\t\treturn 0.005\n\tcase z < -30:\n\t\treturn 0.995\n\tdefault:\n\t\treturn 1 / (1 + math.Exp(z))\n\t}\n}\n\nfunc (r *candidateRouter) copiedBelief(\n\tkey candidateEdgeKey) (candidateBelief, bool) {\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tb := candidateBeliefStore.beliefs[key]\n\tif b == nil {\n\t\treturn candidateBelief{}, false\n\t}\n\n\treturn *b, true\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\tbelief, ok := r.copiedBelief(e.key)\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\testimatedP := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.12 * float64(belief.conf)\n\t\t\tif weight > 0.72 {\n\t\t\t\tweight = 0.72\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*estimatedP\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\t// Even corroborated failure reports are not absolute:\n\t\t\t\t// liquidity can move and attribution can be shifted.\n\t\t\t\tp = math.Min(p, 0.025)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK &&\n\t\t\t\trequired > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tbounded := 0.995*(1-pos) + 0.025*pos\n\t\t\t\tp = 0.30*p + 0.70*bounded\n\t\t\t}\n\t\t}\n\t}\n\n\tif observation, exists := r.localObs[e.key]; exists &&\n\t\tobservation.upper > 0 {\n\n\t\tratio := float64(required) / float64(observation.upper)\n\t\tif ratio >= 1 {\n\t\t\tp *= math.Exp(-1.25 * observation.weight)\n\t\t} else {\n\t\t\t// A failure at a larger amount should encourage an actual\n\t\t\t// lower-amount retry, not blacklist the direction.\n\t\t\tp *= math.Exp(\n\t\t\t\t-0.16 * observation.weight *\n\t\t\t\t\tmath.Pow(ratio, 5),\n\t\t\t)\n\t\t}\n\t}\n\n\tswitch {\n\tcase p < 0.003:\n\t\treturn 0.003\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.015 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\t// Successful siblings prove some liquidity, but in atomic MPP\n\t\t// they also hold it. Prefer a fresh corridor while still allowing\n\t\t// reuse when the graph has no alternative.\n\t\tcost += 0.55 +\n\t\t\t1.80*float64(reserved)/float64(e.capacity)\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\n\t\tbest, exists := scores[item.node]\n\t\tif !exists || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif arriving[item.node] != item.amt {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOverEdge := item.amt\n\t\t\tif !edge.policyAllows(amtOverEdge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOverEdge + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOverEdge\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOverEdge)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := math.Max(float64(amt), 1)\n\t\t\t\tfeeCost = 25 * float64(fee) / denom\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(edge, amtOverEdge) + feeCost\n\n\t\t\told, seen := scores[edge.key.from]\n\t\t\tif seen && newScore >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\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: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := scores[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\tpathProbability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tpathProbability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, pathProbability, 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\tfor node := r.source; node != r.spec.Target; {\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\tforwardAmt := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tforwardAmt = 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: forwardAmt,\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts + r.failures\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\n\treturn target\n}\n\nfunc candidateRouteQuality(rt *route.Route, probability float64) float64 {\n\tif rt == nil || len(rt.Hops) == 0 || probability <= 0 {\n\t\treturn 0\n\t}\n\n\treturn math.Pow(probability, 1/float64(len(rt.Hops)))\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\twholeRoute, wholeProbability, wholeErr := r.findRoute(amt)\n\tif wholeErr == nil && r.failures == 0 {\n\t\tif maxParts == 1 || r.plannedParts == 1 ||\n\t\t\twholeProbability >= 0.86 {\n\n\t\t\treturn wholeRoute, nil\n\t\t}\n\t}\n\n\ttargetParts := r.targetPartCount()\n\tdesiredSlots := uint32(1)\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\n\tshard := candidateCeilDiv(amt, desiredSlots)\n\n\t// Failed attempts do not consume a part slot. A lower-than-average\n\t// shard can therefore be retried now and balanced by a larger shard\n\t// later, which is essential for unequal corridors.\n\tif r.retryCap > 0 && r.retryCap < shard && partsLeft > 1 {\n\t\tshard = r.retryCap\n\t}\n\n\thardFloor := amt\n\tif partsLeft > 1 {\n\t\thardFloor = candidateCeilDiv(amt, partsLeft*2)\n\t}\n\tif shard < hardFloor {\n\t\tshard = hardFloor\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tbaseRoute, baseProbability, err := r.findRoute(shard)\n\tfor err != nil && shard > hardFloor {\n\t\tnextShard := shard * 2 / 3\n\t\tif nextShard < hardFloor {\n\t\t\tnextShard = hardFloor\n\t\t}\n\t\tif nextShard == shard {\n\t\t\tnextShard--\n\t\t}\n\n\t\tshard = nextShard\n\t\tbaseRoute, baseProbability, err = r.findRoute(shard)\n\t}\n\n\tif err != nil {\n\t\tif wholeErr == nil {\n\t\t\treturn wholeRoute, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\t// With clean evidence, greedily give a reliable, high-headroom\n\t// corridor more than an equal share. Reservations then make the\n\t// next call seek another corridor, yielding an unequal route set.\n\tif r.retryCap == 0 && desiredSlots > 1 {\n\t\tbestRoute := baseRoute\n\t\tbestProbability := baseProbability\n\t\tbestAmt := shard\n\t\tbaseQuality := candidateRouteQuality(\n\t\t\tbaseRoute, baseProbability,\n\t\t)\n\n\t\tfactors := []int64{5, 3, 7}\n\t\tdivisors := []int64{4, 2, 4}\n\n\t\tfor i := range factors {\n\t\t\tcandidateAmt := lnwire.MilliSatoshi(\n\t\t\t\tint64(shard) * factors[i] / divisors[i],\n\t\t\t)\n\t\t\tif candidateAmt <= bestAmt || candidateAmt >= amt {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trt, probability, routeErr := r.findRoute(candidateAmt)\n\t\t\tif routeErr != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tquality := candidateRouteQuality(rt, probability)\n\t\t\tqualityFloor := math.Max(0.62, baseQuality*0.94)\n\t\t\tif quality < qualityFloor {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestRoute = rt\n\t\t\tbestProbability = probability\n\t\t\tbestAmt = candidateAmt\n\t\t}\n\n\t\t_ = bestProbability\n\t\treturn bestRoute, nil\n\t}\n\n\treturn baseRoute, nil\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) observeLocal(\n\ttraversal candidateTraversal, weight float64) {\n\n\tif weight <= 0 {\n\t\treturn\n\t}\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\tobservation := r.localObs[traversal.key]\n\n\tif observation.upper == 0 || required < observation.upper {\n\t\tobservation.upper = required\n\t}\n\tobservation.weight += weight\n\tif observation.weight > 4 {\n\t\tobservation.weight = 4\n\t}\n\n\tr.localObs[traversal.key] = observation\n}\n\nfunc (r *candidateRouter) recordPersistentFailure(\n\ttraversal candidateTraversal) {\n\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tb := candidateBeliefStore.beliefs[traversal.key]\n\tif b == nil {\n\t\tb = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t}\n\n\tif b.suspectVotes == 0 {\n\t\tb.suspectAmt = required\n\t\tb.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif required < b.suspectAmt || b.suspectAmt == 0 {\n\t\tb.suspectAmt = required\n\t}\n\tif b.suspectVotes < 255 {\n\t\tb.suspectVotes++\n\t}\n\n\t// Requiring three reports keeps a single shifted attribution, and\n\t// even an unlucky pair, out of the persistent hard-bound store.\n\tif b.suspectVotes < 3 {\n\t\treturn\n\t}\n\n\tif b.upperFail == 0 || b.suspectAmt < b.upperFail {\n\t\tb.upperFail = b.suspectAmt\n\t}\n\n\tfailedEstimate := b.suspectAmt * 70 / 100\n\tif b.estimate == 0 || failedEstimate < b.estimate {\n\t\tb.estimate = failedEstimate\n\t}\n\tif b.conf < 8 {\n\t\tb.conf++\n\t}\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tb := candidateBeliefStore.beliefs[traversal.key]\n\t\tif b == nil {\n\t\t\tb = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t\t}\n\n\t\tif required > b.lowerOK {\n\t\t\tb.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 90 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > b.estimate {\n\t\t\tb.estimate = highEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\n\t\tif b.upperFail > 0 && required >= b.upperFail {\n\t\t\tb.upperFail = 0\n\t\t}\n\t\tif b.suspectAmt > 0 && required >= b.suspectAmt {\n\t\t\tb.suspectAmt = 0\n\t\t\tb.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tif observation, ok := r.localObs[traversal.key]; ok &&\n\t\t\trequired >= observation.upper {\n\n\t\t\tdelete(r.localObs, traversal.key)\n\t\t}\n\n\t\tr.edgePenalty[traversal.key] *= 0.20\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif b := candidateBeliefStore.beliefs[key]; b != nil {\n\t\t\tif b.lowerOK > used {\n\t\t\t\tb.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tb.lowerOK = 0\n\t\t\t}\n\n\t\t\tif b.estimate > used {\n\t\t\t\tb.estimate -= used\n\t\t\t} else {\n\t\t\t\tb.estimate = 0\n\t\t\t}\n\n\t\t\tif b.upperFail > used {\n\t\t\t\tb.upperFail -= used\n\t\t\t} else {\n\t\t\t\tb.upperFail = 0\n\t\t\t}\n\n\t\t\tif b.suspectAmt > used {\n\t\t\t\tb.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tb.suspectAmt = 0\n\t\t\t\tb.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex, traversals []candidateTraversal) (int, bool) {\n\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0, len(traversals) > 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\tindex := i + 1\n\t\tif index >= len(traversals) {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn index, true\n\t}\n\n\treturn 0, false\n}\n\nfunc (r *candidateRouter) attributionCredibility(\n\ttraversal candidateTraversal) float64 {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t// A temporary failure attributed to an outgoing channel whose exact\n\t// local balance covers the HTLC is almost certainly shifted blame.\n\tif traversal.key.from == r.source &&\n\t\tr.localBalances[traversal.key.chanID] >= required {\n\n\t\treturn 0.05\n\t}\n\n\tbelief, ok := r.copiedBelief(traversal.key)\n\tif ok && belief.lowerOK >= required {\n\t\treturn 0.40\n\t}\n\n\treturn 1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\ttraversals := r.routeTraversals(rt)\n\n\t// The route failure is real even when its attribution is not. These\n\t// weak, payment-local observations diversify retries without writing\n\t// false persistent bounds.\n\tfor _, traversal := range traversals {\n\t\tr.observeLocal(traversal, 0.045)\n\t\tr.edgePenalty[traversal.key] += 0.018\n\t}\n\n\tcode := result.Failure.Code()\n\tindex, attributed := r.failureIndex(\n\t\trt, result.FailureSource, traversals,\n\t)\n\n\tif code == lnwire.CodeTemporaryChannelFailure && attributed {\n\t\tcredibility := r.attributionCredibility(traversals[index])\n\t\tr.observeLocal(traversals[index], 1.15*credibility)\n\t\tr.edgePenalty[traversals[index].key] +=\n\t\t\t0.50 * credibility\n\n\t\tneighborWeight := 0.42\n\t\tif credibility < 0.5 {\n\t\t\tneighborWeight = 0.90\n\t\t}\n\n\t\tif index > 0 {\n\t\t\tr.observeLocal(\n\t\t\t\ttraversals[index-1], neighborWeight,\n\t\t\t)\n\t\t\tr.edgePenalty[traversals[index-1].key] +=\n\t\t\t\t0.16 * neighborWeight\n\t\t}\n\t\tif index+1 < len(traversals) {\n\t\t\tr.observeLocal(\n\t\t\t\ttraversals[index+1], neighborWeight,\n\t\t\t)\n\t\t\tr.edgePenalty[traversals[index+1].key] +=\n\t\t\t\t0.16 * neighborWeight\n\t\t}\n\n\t\tif credibility >= 0.8 {\n\t\t\tr.recordPersistentFailure(traversals[index])\n\t\t}\n\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tif delivered > 1 {\n\t\t\tr.retryCap = delivered * 62 / 100\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif !attributed {\n\t\tr.unknownFails++\n\t\tfor _, traversal := range traversals {\n\t\t\tr.observeLocal(traversal, 0.055)\n\t\t\tr.edgePenalty[traversal.key] += 0.045\n\t\t}\n\n\t\tif r.unknownFails%2 == 0 {\n\t\t\tdelivered := candidateDeliveredAmount(rt)\n\t\t\tif delivered > 1 {\n\t\t\t\tr.retryCap = delivered * 72 / 100\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tr.edgePenalty[traversals[index].key] += 2.5\n\t\tif index > 0 {\n\t\t\tr.edgePenalty[traversals[index-1].key] += 0.55\n\t\t}\n\t\tif index+1 < len(traversals) {\n\t\t\tr.edgePenalty[traversals[index+1].key] += 0.55\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 10,
"parent": 1,
"score": 0.1461,
"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\"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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tconsecutiveFailures uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.Amount > 250_000_000 {\n\t\tr.plannedParts = maxParts\n\t}\n\tif r.plannedParts > maxParts {\n\t\tr.plannedParts = maxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tswitch {\n\tcase amt <= 25_000_000:\n\t\treturn 1\n\tcase amt <= 100_000_000:\n\t\treturn 2\n\tcase amt <= 250_000_000:\n\t\treturn 4\n\tdefault:\n\t\treturn 8\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.07 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tif z > 30 {\n\t\treturn 0.005\n\t}\n\tif z < -30 {\n\t\treturn 0.995\n\t}\n\n\treturn 1 / (1 + math.Exp(z))\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.14 * float64(belief.conf)\n\t\t\tif weight > 0.70 {\n\t\t\t\tweight = 0.70\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.008)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tif pos > 0 {\n\t\t\t\t\tbounded := 0.995*(1-pos) + 0.008*pos\n\t\t\t\t\tp = 0.35*p + 0.65*bounded\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, exists := r.localFails[e.key]; exists &&\n\t\trequired >= local.upper {\n\n\t\tif local.count >= 2 {\n\t\t\tp = math.Min(p, 0.004)\n\t\t} else {\n\t\t\tp *= 0.08\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) addEdgePenalty(key candidateEdgeKey,\n\tdelta float64) {\n\n\tnext := r.edgePenalty[key] + delta\n\tif next > 5 {\n\t\tnext = 5\n\t}\n\tr.edgePenalty[key] = next\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.025 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\trequired := amt + reserved\n\n\t\tcandidateBeliefStore.Lock()\n\t\tb := candidateBeliefStore.beliefs[e.key]\n\t\tproven := b != nil && b.lowerOK >= required\n\t\tcandidateBeliefStore.Unlock()\n\n\t\tif proven {\n\t\t\tcost += 0.10 +\n\t\t\t\t0.25*float64(reserved)/float64(e.capacity)\n\t\t} else {\n\t\t\tcost += 0.65 +\n\t\t\t\t0.90*float64(reserved)/float64(e.capacity)\n\t\t}\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tscore := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscore[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\n\t\tbestScore, ok := score[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.amt != arriving[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 30 * float64(fee) / denom\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(edge, amtOver) + feeCost\n\n\t\t\toldScore, exists := score[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := score[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\tpathProbability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tpathProbability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, pathProbability, 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\tfor node := r.source; node != r.spec.Target; {\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\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc candidateMin(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts\n\n\tif r.failures > 0 {\n\t\ttarget += 1 + r.failures/3\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\n\treturn target\n}\n\nfunc (r *candidateRouter) routeLiquidityLimit(\n\trt *route.Route) lnwire.MilliSatoshi {\n\n\tdelivered := candidateDeliveredAmount(rt)\n\tif delivered <= 0 {\n\t\treturn 0\n\t}\n\n\tvar targetLimit lnwire.MilliSatoshi\n\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tedge := traversal.edge\n\t\treserved := r.reserved[traversal.key]\n\n\t\tavailable := edge.capacity - reserved\n\t\tif available <= 0 {\n\t\t\treturn 0\n\t\t}\n\n\t\tif traversal.key.from == r.source {\n\t\t\tlocal := r.localBalances[traversal.key.chanID] -\n\t\t\t\treserved\n\t\t\tif local <= 0 {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tavailable = candidateMin(available, local)\n\t\t} else {\n\t\t\tcandidateBeliefStore.Lock()\n\t\t\tstored := candidateBeliefStore.beliefs[traversal.key]\n\t\t\tvar belief candidateBelief\n\t\t\tif stored != nil {\n\t\t\t\tbelief = *stored\n\t\t\t}\n\t\t\tcandidateBeliefStore.Unlock()\n\n\t\t\tliquidityEstimate := edge.capacity * 82 / 100\n\t\t\tif belief.estimate > liquidityEstimate {\n\t\t\t\tliquidityEstimate = belief.estimate\n\t\t\t}\n\t\t\tif belief.lowerOK > liquidityEstimate {\n\t\t\t\tliquidityEstimate = belief.lowerOK\n\t\t\t}\n\n\t\t\tif belief.upperFail > reserved {\n\t\t\t\tbelowFailure := (belief.upperFail - reserved) * 58 / 100\n\t\t\t\tif belowFailure < liquidityEstimate-reserved {\n\t\t\t\t\tliquidityEstimate = reserved + belowFailure\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif liquidityEstimate <= reserved {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tavailable = candidateMin(\n\t\t\t\tavailable, liquidityEstimate-reserved,\n\t\t\t)\n\t\t}\n\n\t\tif edge.maxHTLC > 0 {\n\t\t\tavailable = candidateMin(available, edge.maxHTLC)\n\t\t}\n\n\t\tif available <= 0 || traversal.amt <= 0 {\n\t\t\treturn 0\n\t\t}\n\n\t\tlimit := lnwire.MilliSatoshi(\n\t\t\tfloat64(delivered) * float64(available) /\n\t\t\t\tfloat64(traversal.amt),\n\t\t)\n\t\tif targetLimit == 0 || limit < targetLimit {\n\t\t\ttargetLimit = limit\n\t\t}\n\t}\n\n\treturn targetLimit\n}\n\nfunc (r *candidateRouter) plannedAverage(\n\tamt lnwire.MilliSatoshi, inFlightHtlcs,\n\tpartsLeft uint32) lnwire.MilliSatoshi {\n\n\ttarget := r.targetPartCount()\n\tvar slots uint32\n\n\tif target > inFlightHtlcs {\n\t\tslots = target - inFlightHtlcs\n\t} else {\n\t\tslots = 1\n\t}\n\tif slots > partsLeft {\n\t\tslots = partsLeft\n\t}\n\tif slots == 0 {\n\t\tslots = 1\n\t}\n\n\treturn candidateCeilDiv(amt, slots)\n}\n\nfunc (r *candidateRouter) chooseFreshShard(\n\tamt, average lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tbaseRoute, baseProbability, err := r.findRoute(average)\n\tif err != nil {\n\t\tfloor := average / 2\n\t\tif floor < 1 {\n\t\t\tfloor = 1\n\t\t}\n\n\t\tfor shard := average * 4 / 5; shard >= floor; {\n\t\t\tif rt, _, findErr := r.findRoute(shard); findErr == nil {\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t\tif shard == floor {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tnext := shard * 4 / 5\n\t\t\tif next < floor {\n\t\t\t\tnext = floor\n\t\t\t}\n\t\t\tif next == shard {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tshard = next\n\t\t}\n\n\t\tif average != amt {\n\t\t\tif rt, _, wholeErr := r.findRoute(amt); wholeErr == nil {\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tlimit := r.routeLiquidityLimit(baseRoute)\n\tif limit <= average {\n\t\treturn baseRoute, nil\n\t}\n\n\tdesired := limit * 3 / 4\n\tmaxShard := average * 9 / 4\n\tif desired > maxShard {\n\t\tdesired = maxShard\n\t}\n\tif desired > amt {\n\t\tdesired = amt\n\t}\n\tif desired <= average {\n\t\treturn baseRoute, nil\n\t}\n\n\tif rt, probability, findErr := r.findRoute(desired); findErr == nil {\n\t\tif probability >= baseProbability*0.22 {\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\tmiddle := average * 3 / 2\n\tif middle > amt {\n\t\tmiddle = amt\n\t}\n\tif middle > average {\n\t\tif rt, probability, findErr := r.findRoute(middle); findErr == nil {\n\t\t\tif probability >= baseProbability*0.38 {\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn baseRoute, nil\n}\n\nfunc (r *candidateRouter) chooseRetryShard(\n\tamt, average, completionAverage lnwire.MilliSatoshi) (\n\t*route.Route, error) {\n\n\tfloor := completionAverage * 2 / 5\n\tif floor < 1 {\n\t\tfloor = 1\n\t}\n\n\tshard := r.retryCap\n\tif shard <= 0 || r.consecutiveFailures%5 == 0 {\n\t\tshard = average\n\t}\n\tif shard < floor {\n\t\tshard = floor\n\t}\n\tif shard > average {\n\t\tshard = average\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tif rt, _, err := r.findRoute(shard); err == nil {\n\t\treturn rt, nil\n\t}\n\n\tif shard != average {\n\t\tif rt, _, err := r.findRoute(average); err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\tfor candidate := shard * 4 / 5; candidate >= floor; {\n\t\tif rt, _, err := r.findRoute(candidate); err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\t\tif candidate == floor {\n\t\t\tbreak\n\t\t}\n\n\t\tnext := candidate * 4 / 5\n\t\tif next < floor {\n\t\t\tnext = floor\n\t\t}\n\t\tif next == candidate {\n\t\t\tbreak\n\t\t}\n\t\tcandidate = next\n\t}\n\n\tif rt, _, err := r.findRoute(amt); err == nil {\n\t\treturn rt, nil\n\t}\n\n\treturn nil, errors.New(\"no route found\")\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tcompletionAverage := candidateCeilDiv(amt, partsLeft)\n\n\tif inFlightHtlcs == 0 && r.failures == 0 {\n\t\tif rt, probability, err := r.findRoute(amt); err == nil {\n\t\t\tif r.plannedParts == 1 || probability >= 0.75 {\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t}\n\t}\n\n\taverage := r.plannedAverage(\n\t\tamt, inFlightHtlcs, partsLeft,\n\t)\n\tif average < completionAverage {\n\t\taverage = completionAverage\n\t}\n\tif average > amt {\n\t\taverage = amt\n\t}\n\n\tif r.consecutiveFailures > 0 {\n\t\treturn r.chooseRetryShard(\n\t\t\tamt, average, completionAverage,\n\t\t)\n\t}\n\n\treturn r.chooseFreshShard(amt, average)\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tb := candidateBeliefStore.beliefs[traversal.key]\n\t\tif b == nil {\n\t\t\tb = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t\t}\n\n\t\tif required > b.lowerOK {\n\t\t\tb.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > b.estimate {\n\t\t\tb.estimate = highEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\n\t\tif b.upperFail > 0 && required >= b.upperFail {\n\t\t\tb.upperFail = 0\n\t\t}\n\t\tif b.suspectAmt > 0 && required >= b.suspectAmt {\n\t\t\tb.suspectAmt = 0\n\t\t\tb.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif b := candidateBeliefStore.beliefs[key]; b != nil {\n\t\t\tif b.lowerOK > used {\n\t\t\t\tb.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tb.lowerOK = 0\n\t\t\t}\n\n\t\t\tif b.estimate > used {\n\t\t\t\tb.estimate -= used\n\t\t\t} else {\n\t\t\t\tb.estimate = 0\n\t\t\t}\n\n\t\t\tif b.upperFail > used {\n\t\t\t\tb.upperFail -= used\n\t\t\t} else {\n\t\t\t\tb.upperFail = 0\n\t\t\t}\n\n\t\t\tif b.suspectAmt > used {\n\t\t\t\tb.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tb.suspectAmt = 0\n\t\t\t\tb.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureEdge(rt *route.Route,\n\tsource route.Vertex) (candidateTraversal, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\tindex := -1\n\tif source == rt.SourcePubKey {\n\t\tindex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tindex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif index < 0 || index >= len(traversals) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\treturn traversals[index], true\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversal candidateTraversal) bool {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t// The sender's balance is exact. A report contradicting it is shifted\n\t// attribution and must not poison that channel.\n\tif traversal.key.from == r.source &&\n\t\tr.localBalances[traversal.key.chanID] >= required {\n\n\t\treturn false\n\t}\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tif local.count < 255 {\n\t\tlocal.count++\n\t}\n\tr.localFails[traversal.key] = local\n\n\tif traversal.key.from == r.source {\n\t\treturn true\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tb := candidateBeliefStore.beliefs[traversal.key]\n\tif b == nil {\n\t\tb = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t}\n\n\tif b.suspectVotes == 0 {\n\t\tb.suspectAmt = required\n\t\tb.suspectVotes = 1\n\t\treturn true\n\t}\n\n\tif required < b.suspectAmt || b.suspectAmt == 0 {\n\t\tb.suspectAmt = required\n\t}\n\tif b.suspectVotes < 255 {\n\t\tb.suspectVotes++\n\t}\n\n\t// Persistent bounds require corroboration inside this payment. This\n\t// keeps isolated shifted reports from accumulating across payments.\n\tif local.count >= 2 && b.suspectVotes >= 2 {\n\t\tif b.upperFail == 0 || b.suspectAmt < b.upperFail {\n\t\t\tb.upperFail = b.suspectAmt\n\t\t}\n\n\t\tfailedEstimate := required * 70 / 100\n\t\tif b.estimate == 0 || failedEstimate < b.estimate {\n\t\t\tb.estimate = failedEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) recordUnknownRouteFailure(\n\ttraversals []candidateTraversal) {\n\n\tif len(traversals) == 0 {\n\t\treturn\n\t}\n\n\tweights := make([]float64, len(traversals))\n\ttotal := 0.0\n\n\tfor i, traversal := range traversals {\n\t\tp := r.edgeProbability(traversal.edge, traversal.amt)\n\t\tweight := 1 - p\n\t\tif weight < 0.01 {\n\t\t\tweight = 0.01\n\t\t}\n\t\tweights[i] = weight\n\t\ttotal += weight\n\t}\n\n\tif total <= 0 {\n\t\treturn\n\t}\n\n\tfor i, traversal := range traversals {\n\t\t// One unreadable failure contributes one bounded unit of route\n\t\t// diversification instead of penalizing every edge equally.\n\t\tr.addEdgePenalty(\n\t\t\ttraversal.key, 0.90*weights[i]/total,\n\t\t)\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.consecutiveFailures = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\tr.consecutiveFailures++\n\n\ttraversals := r.routeTraversals(rt)\n\tcode := result.Failure.Code()\n\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tif r.recordAttributedLiquidityFailure(traversal) {\n\t\t\t\tdelivered := candidateDeliveredAmount(rt)\n\t\t\t\tif delivered > 1 {\n\t\t\t\t\tr.retryCap = delivered * 58 / 100\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\n\tr.unknownFails++\n\tr.recordUnknownRouteFailure(traversals)\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tr.addEdgePenalty(traversal.key, 0.75)\n\t\t}\n\t}\n\n\tdelivered := candidateDeliveredAmount(rt)\n\tif delivered > 1 {\n\t\tswitch {\n\t\tcase r.consecutiveFailures <= 2:\n\t\t\tr.retryCap = delivered * 68 / 100\n\t\tcase r.consecutiveFailures%5 == 0:\n\t\t\tr.retryCap = 0\n\t\tdefault:\n\t\t\tr.retryCap = delivered * 55 / 100\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 11,
"parent": 3,
"score": 0.5773,
"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\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usable(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\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n\tstamp uint64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\t// Successful shards consume or hold this much liquidity. Including these\n\t// reservations in every later path calculation is essential for atomic\n\t// MPP, and is also correct after an immediately settled shard.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// Penalties are payment-local. They diversify siblings and failed\n\t// attempts without converting unreadable failures into persistent facts.\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tretryLimit lnwire.MilliSatoshi\n\tconsecutiveFailure uint8\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\tcandidateMemory.Lock()\n\tcandidateMemory.epoch++\n\tepoch := candidateMemory.epoch\n\n\tsnapshot := make(map[candidateEdgeKey]candidateBelief,\n\t\tlen(candidateMemory.beliefs))\n\tfor key, belief := range candidateMemory.beliefs {\n\t\tsnapshot[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: snapshot,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// The prior reflects a channel that is usually near one endpoint rather\n// than uniformly distributed. Reducing a medium-sized shard helps mainly\n// by crossing the low-mode exponential or the high-mode capacity cliff.\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\thighMode := 1 / (1 + math.Exp(18*(x-0.82)))\n\n\treturn candidateClampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) evidenceConfidence(b candidateBelief) float64 {\n\tif b.stamp == 0 || r.epoch <= b.stamp+3 {\n\t\treturn 1\n\t}\n\n\tage := float64(r.epoch - b.stamp - 3)\n\treturn math.Max(0.12, math.Exp(-age/12))\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tif totalAmt <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := candidatePrior(totalAmt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.stamp == 0 {\n\t\treturn prior\n\t}\n\n\tlearned := prior\n\n\tswitch {\n\tcase belief.lowerOK > 0 && totalAmt <= belief.lowerOK:\n\t\tlearned = 0.995\n\n\tcase belief.upperFail > 0 && totalAmt >= belief.upperFail:\n\t\tlearned = 0.008\n\n\tcase belief.lowerOK > 0 && belief.upperFail > belief.lowerOK:\n\t\tspan := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(totalAmt-belief.lowerOK) / span\n\t\tpos = math.Max(0, math.Min(1, pos))\n\t\tlearned = 0.995*(1-pos) + 0.008*pos\n\n\tcase belief.lowerOK > 0 && totalAmt > belief.lowerOK:\n\t\tratio := float64(belief.lowerOK) / float64(totalAmt)\n\t\tlearned = prior + (0.995-prior)*ratio*ratio\n\n\tcase belief.upperFail > 0 && totalAmt < belief.upperFail:\n\t\tratio := float64(totalAmt) / float64(belief.upperFail)\n\t\tlearned = prior * (1 - 0.72*ratio*ratio)\n\t}\n\n\tif belief.upperFail == 0 && belief.suspectAmt > 0 &&\n\t\ttotalAmt >= belief.suspectAmt {\n\n\t\tlearned *= 0.58\n\t}\n\n\tconfidence := r.evidenceConfidence(belief)\n\treturn candidateClampProbability(\n\t\tconfidence*learned + (1-confidence)*prior,\n\t)\n}\n\ntype candidateItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype candidateQueue []*candidateItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype candidatePath struct {\n\troute *route.Route\n\tedges []*candidateEdge\n\tprobability float64\n\tpressure float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\treturn r.findRouteWithPenalty(amt, nil)\n}\n\nfunc (r *candidateRouter) findRouteWithPenalty(\n\tamt lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]float64) (*candidatePath, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tamounts := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tamounts[r.spec.Target] = amt\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateItem)\n\t\tbestScore, ok := scores[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amounts[item.node]\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usable(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := arriving + r.reserved[edge.key]\n\t\t\tif totalLiquidity < arriving ||\n\t\t\t\ttotalLiquidity > edge.capacity {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source &&\n\t\t\t\ttotalLiquidity > r.localBalances[edge.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tp := r.probability(edge, totalLiquidity)\n\t\t\tedgeCost := -math.Log(p) + 0.02\n\t\t\tif arriving > 0 {\n\t\t\t\tedgeCost += 4.0 * float64(fee) /\n\t\t\t\t\tfloat64(arriving)\n\t\t\t}\n\n\t\t\tedgeCost += r.edgePenalty[edge.key]\n\t\t\tif r.reserved[edge.key] > 0 &&\n\t\t\t\tedge.from != r.source {\n\n\t\t\t\tedgeCost += 0.45\n\t\t\t}\n\t\t\tif extra != nil {\n\t\t\t\tedgeCost += extra[edge.key]\n\t\t\t}\n\n\t\t\tnewScore := item.score + edgeCost\n\t\t\toldScore, visited := scores[edge.from]\n\t\t\tif visited && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.from] = newScore\n\t\t\tamounts[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &candidateItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r.assessPath(rt, path), nil\n}\n\nfunc (r *candidateRouter) assessPath(rt *route.Route,\n\tpath []*candidateEdge) *candidatePath {\n\n\tprobability := 1.0\n\tpressure := 0.0\n\n\tfor i, edge := range path {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\ttotalLiquidity := amtOver + r.reserved[edge.key]\n\n\t\tprobability *= r.probability(edge, totalLiquidity)\n\n\t\tdenominator := edge.capacity\n\t\tif edge.from == r.source {\n\t\t\tdenominator = r.localBalances[edge.chanID]\n\t\t}\n\n\t\tif denominator > 0 {\n\t\t\tratio := float64(totalLiquidity) /\n\t\t\t\tfloat64(denominator)\n\t\t\tif ratio > pressure {\n\t\t\t\tpressure = ratio\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &candidatePath{\n\t\troute: rt,\n\t\tedges: path,\n\t\tprobability: probability,\n\t\tpressure: pressure,\n\t}\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\trt, err := r.buildFixedRoute(amt, path)\n\treturn rt, path, err\n}\n\nfunc (r *candidateRouter) buildFixedRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tfor i := 1; i < len(path); i++ {\n\t\tif path[i-1].to != path[i].from {\n\t\t\treturn nil, errors.New(\"disconnected route\")\n\t\t}\n\t}\n\tif path[0].from != r.source ||\n\t\tpath[len(path)-1].to != r.spec.Target {\n\n\t\treturn nil, errors.New(\"route endpoints do not match\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tfee := forwardingEdge.fee(amtOver[i+1])\n\t\tamtOver[i] = amtOver[i+1] + fee\n\t\tif amtOver[i] < amtOver[i+1] {\n\t\t\treturn nil, errors.New(\"amount overflow\")\n\t\t}\n\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t\tif expiryOver[i] < expiryOver[i+1] {\n\t\t\treturn nil, errors.New(\"cltv overflow\")\n\t\t}\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := 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.to,\n\t\t\tChannelID: edge.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\n// candidateLiquidityLimit provides two planning envelopes. The safe envelope\n// avoids the prior's capacity cliff. The hard envelope permits deliberate\n// unequal allocations when the payment cannot fit otherwise. Neither is used\n// to blacklist an edge during ordinary pathfinding.\nfunc (r *candidateRouter) candidateLiquidityLimit(\n\tedge *candidateEdge, safe bool) lnwire.MilliSatoshi {\n\n\tif edge.from == r.source {\n\t\treturn r.localBalances[edge.chanID]\n\t}\n\n\tlimit := edge.capacity\n\tif safe {\n\t\tlimit = edge.capacity * 82 / 100\n\t}\n\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.stamp == 0 {\n\t\treturn limit\n\t}\n\n\tconfidence := r.evidenceConfidence(belief)\n\tif belief.lowerOK > limit && confidence >= 0.45 {\n\t\tlimit = belief.lowerOK\n\t}\n\n\tif belief.upperFail > 0 && confidence >= 0.72 {\n\t\tfailureLimit := belief.upperFail * 98 / 100\n\t\tif safe {\n\t\t\tfailureLimit = belief.upperFail * 72 / 100\n\t\t}\n\t\tif belief.lowerOK > failureLimit {\n\t\t\tfailureLimit = belief.lowerOK\n\t\t}\n\t\tif failureLimit < limit {\n\t\t\tlimit = failureLimit\n\t\t}\n\t}\n\n\tif limit > edge.capacity {\n\t\tlimit = edge.capacity\n\t}\n\treturn limit\n}\n\nfunc (r *candidateRouter) fixedPathAt(amt lnwire.MilliSatoshi,\n\tedges []*candidateEdge, safe bool) (*candidatePath, error) {\n\n\trt, err := r.buildFixedRoute(amt, edges)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i, edge := range edges {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\tif !edge.usable(amtOver) {\n\t\t\treturn nil, errors.New(\"path violates htlc limits\")\n\t\t}\n\n\t\ttotal := amtOver + r.reserved[edge.key]\n\t\tif total < amtOver ||\n\t\t\ttotal > r.candidateLiquidityLimit(edge, safe) {\n\n\t\t\treturn nil, errors.New(\"path exceeds liquidity envelope\")\n\t\t}\n\t}\n\n\treturn r.assessPath(rt, edges), nil\n}\n\nfunc (r *candidateRouter) minimumFixedAmount(\n\tedges []*candidateEdge,\n\tknown lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif known <= 0 {\n\t\treturn 0\n\t}\n\tif _, err := r.fixedPathAt(known, edges, false); err != nil {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(1)\n\thigh := known\n\tfor low < high {\n\t\tmid := low + (high-low)/2\n\t\tif _, err := r.fixedPathAt(mid, edges, false); err == nil {\n\t\t\thigh = mid\n\t\t} else {\n\t\t\tlow = mid + 1\n\t\t}\n\t}\n\n\treturn low\n}\n\nfunc (r *candidateRouter) maximumFixedAmount(\n\tedges []*candidateEdge, minimum, maximum lnwire.MilliSatoshi,\n\tsafe bool) lnwire.MilliSatoshi {\n\n\tif minimum <= 0 || maximum < minimum {\n\t\treturn 0\n\t}\n\tif _, err := r.fixedPathAt(minimum, edges, safe); err != nil {\n\t\treturn 0\n\t}\n\tif _, err := r.fixedPathAt(maximum, edges, safe); err == nil {\n\t\treturn maximum\n\t}\n\n\tlow := minimum\n\thigh := maximum\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif _, err := r.fixedPathAt(mid, edges, safe); err == nil {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\treturn low\n}\n\ntype candidatePlanPath struct {\n\tedges []*candidateEdge\n\tminimum lnwire.MilliSatoshi\n\tsafe lnwire.MilliSatoshi\n\thard lnwire.MilliSatoshi\n}\n\nfunc candidateSamePath(a, b []*candidateEdge) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif a[i].key != b[i].key {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Source channels may legitimately feed several independent corridors and\n// have exact known balances. Other shared channels make two planned shards\n// compete for the same hidden directional liquidity.\nfunc candidatePathsConflict(a, b []*candidateEdge) bool {\n\tfor _, left := range a {\n\t\tif left.from == left.to {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, right := range b {\n\t\t\tif left.key == right.key && left.from != right.from {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif left.key == right.key && left.from != (route.Vertex{}) {\n\t\t\t\t// Sharing the sender's known local channel is allowed.\n\t\t\t\tif left.from == a[0].from &&\n\t\t\t\t\tright.from == b[0].from {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r *candidateRouter) enumeratePlanPaths(\n\tamt lnwire.MilliSatoshi, slots uint32) []candidatePlanPath {\n\n\tminimumShard := lnwire.MilliSatoshi(1_000_000)\n\tequal := candidateCeilDiv(amt, slots)\n\n\tseeds := []lnwire.MilliSatoshi{\n\t\tequal,\n\t\tminimumShard,\n\t\t50_000_000,\n\t\tamt,\n\t}\n\n\traw := make([]candidatePlanPath, 0, 12)\n\n\tfor _, seed := range seeds {\n\t\tif seed <= 0 || seed > amt {\n\t\t\tcontinue\n\t\t}\n\n\t\textra := make(map[candidateEdgeKey]float64)\n\t\ttries := int(slots) + 2\n\t\tif tries > 8 {\n\t\t\ttries = 8\n\t\t}\n\n\t\tfor attempt := 0; attempt < tries; attempt++ {\n\t\t\tpath, err := r.findRouteWithPenalty(seed, extra)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tfound := false\n\t\t\tfor i := range raw {\n\t\t\t\tif candidateSamePath(raw[i].edges, path.edges) {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !found && len(raw) < 12 {\n\t\t\t\tedges := append(\n\t\t\t\t\t[]*candidateEdge(nil), path.edges...,\n\t\t\t\t)\n\t\t\t\traw = append(raw, candidatePlanPath{\n\t\t\t\t\tedges: edges,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfor _, edge := range path.edges {\n\t\t\t\tpenalty := 2.2\n\t\t\t\tif edge.from == r.source {\n\t\t\t\t\tpenalty = 0.35\n\t\t\t\t}\n\t\t\t\textra[edge.key] += penalty\n\t\t\t}\n\t\t}\n\t}\n\n\tplans := make([]candidatePlanPath, 0, len(raw))\n\tfor _, plan := range raw {\n\t\tknown := amt\n\t\tif path, err := r.findMatchingSeed(plan.edges, seeds); err == nil {\n\t\t\tknown = path\n\t\t}\n\n\t\tminimum := r.minimumFixedAmount(plan.edges, known)\n\t\tif minimum == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tplan.minimum = minimum\n\t\tplan.hard = r.maximumFixedAmount(\n\t\t\tplan.edges, minimum, amt, false,\n\t\t)\n\t\tplan.safe = r.maximumFixedAmount(\n\t\t\tplan.edges, minimum, amt, true,\n\t\t)\n\t\tif plan.hard > 0 {\n\t\t\tplans = append(plans, plan)\n\t\t}\n\t}\n\n\treturn plans\n}\n\nfunc (r *candidateRouter) findMatchingSeed(edges []*candidateEdge,\n\tseeds []lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) {\n\n\tbest := lnwire.MilliSatoshi(0)\n\tfor _, seed := range seeds {\n\t\tif seed <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif _, err := r.fixedPathAt(seed, edges, false); err == nil {\n\t\t\tif best == 0 || seed < best {\n\t\t\t\tbest = seed\n\t\t\t}\n\t\t}\n\t}\n\tif best == 0 {\n\t\treturn 0, errors.New(\"no feasible seed\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) alternativeCapacity(\n\tplans []candidatePlanPath, skip int, slots int) lnwire.MilliSatoshi {\n\n\tif slots <= 0 {\n\t\treturn 0\n\t}\n\n\torder := make([]int, 0, len(plans))\n\tfor i := range plans {\n\t\tif i != skip {\n\t\t\torder = append(order, i)\n\t\t}\n\t}\n\tsort.Slice(order, func(i, j int) bool {\n\t\treturn plans[order[i]].hard > plans[order[j]].hard\n\t})\n\n\tchosen := make([]int, 0, slots)\n\ttotal := lnwire.MilliSatoshi(0)\n\n\tfor _, index := range order {\n\t\tif candidatePathsConflict(\n\t\t\tplans[skip].edges, plans[index].edges,\n\t\t) {\n\t\t\tcontinue\n\t\t}\n\n\t\tconflict := false\n\t\tfor _, prior := range chosen {\n\t\t\tif candidatePathsConflict(\n\t\t\t\tplans[prior].edges,\n\t\t\t\tplans[index].edges,\n\t\t\t) {\n\t\t\t\tconflict = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif conflict {\n\t\t\tcontinue\n\t\t}\n\n\t\tchosen = append(chosen, index)\n\t\ttotal += plans[index].hard\n\t\tif len(chosen) == slots {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn total\n}\n\n// planShard jointly considers several distinct route corridors and their\n// feasible amount envelopes. It chooses the current shard large enough that\n// the remaining corridors can cover the rest, which naturally produces\n// unequal splits for unequal channel capacities.\nfunc (r *candidateRouter) planShard(\n\tamt lnwire.MilliSatoshi, partsLeft uint32) (*candidatePath, error) {\n\n\tslots := partsLeft\n\tif slots > 6 {\n\t\tslots = 6\n\t}\n\tif slots < 2 {\n\t\treturn nil, errors.New(\"not enough parts for planning\")\n\t}\n\n\tplans := r.enumeratePlanPaths(amt, slots)\n\tif len(plans) == 0 {\n\t\treturn nil, errors.New(\"no splittable route set\")\n\t}\n\n\tequal := candidateCeilDiv(amt, slots)\n\tbestScore := math.Inf(1)\n\tvar best *candidatePath\n\n\tfor i := range plans {\n\t\tother := r.alternativeCapacity(\n\t\t\tplans, i, int(slots)-1,\n\t\t)\n\n\t\trequired := equal\n\t\tif other < amt {\n\t\t\tneeded := amt - other\n\t\t\tif needed > required {\n\t\t\t\trequired = needed\n\t\t\t}\n\t\t}\n\t\tif required < plans[i].minimum {\n\t\t\trequired = plans[i].minimum\n\t\t}\n\t\tif required > plans[i].hard || required > amt {\n\t\t\tcontinue\n\t\t}\n\n\t\tpath, err := r.fixedPathAt(\n\t\t\trequired, plans[i].edges, false,\n\t\t)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfee := path.route.TotalAmount - required\n\t\tscore := -math.Log(path.probability)\n\t\tif required > 0 {\n\t\t\tscore += 3 * float64(fee) / float64(required)\n\t\t}\n\n\t\tif plans[i].safe > 0 && required > plans[i].safe {\n\t\t\tspan := plans[i].hard - plans[i].safe\n\t\t\tif span > 0 {\n\t\t\t\tscore += 0.7 * float64(\n\t\t\t\t\trequired-plans[i].safe,\n\t\t\t\t) / float64(span)\n\t\t\t}\n\t\t}\n\n\t\t// Prefer a deliberate non-trivial commitment over tiny shards\n\t\t// when route-set coverage and reliability are otherwise similar.\n\t\tscore += 0.04 * float64(equal) / float64(required)\n\n\t\tif score < bestScore {\n\t\t\tbestScore = score\n\t\t\tbest = path\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"route set cannot cover remainder\")\n\t}\n\treturn best, nil\n}\n\nfunc candidateCeilDiv(a lnwire.MilliSatoshi,\n\tb uint32) lnwire.MilliSatoshi {\n\n\tif b <= 1 {\n\t\treturn a\n\t}\n\tdivisor := lnwire.MilliSatoshi(b)\n\treturn a/divisor + func() lnwire.MilliSatoshi {\n\t\tif a%divisor != 0 {\n\t\t\treturn 1\n\t\t}\n\t\treturn 0\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(\"invalid remaining amount\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tif partsLeft <= 1 {\n\t\tpath, err := r.findRoute(amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn path.route, nil\n\t}\n\n\tfull, fullErr := r.findRoute(amt)\n\tif fullErr == nil &&\n\t\t(amt <= 10_000_000 ||\n\t\t\tfull.probability >= 0.80 ||\n\t\t\t(full.probability >= 0.62 &&\n\t\t\t\tfull.pressure < 0.18)) {\n\n\t\treturn full.route, nil\n\t}\n\n\tif planned, err := r.planShard(amt, partsLeft); err == nil {\n\t\treturn planned.route, nil\n\t}\n\n\t// A lower retry can be impossible solely because of min-HTLC policy.\n\t// Search several legal allocations and finally retry the whole amount\n\t// instead of terminally giving up while a route still exists.\n\tamounts := make([]lnwire.MilliSatoshi, 0, 10)\n\tif r.retryLimit > 0 && r.retryLimit <= amt {\n\t\tamounts = append(amounts, r.retryLimit)\n\t}\n\n\tslots := partsLeft\n\tif slots > 6 {\n\t\tslots = 6\n\t}\n\tfor n := slots; n >= 2; n-- {\n\t\tamounts = append(amounts, candidateCeilDiv(amt, n))\n\t}\n\tamounts = append(amounts, amt)\n\n\tvar lastErr error\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tfor _, candidateAmt := range amounts {\n\t\tif candidateAmt <= 0 || candidateAmt > amt ||\n\t\t\tseen[candidateAmt] {\n\n\t\t\tcontinue\n\t\t}\n\t\tseen[candidateAmt] = true\n\n\t\tpath, err := r.findRoute(candidateAmt)\n\t\tif err == nil {\n\t\t\treturn path.route, nil\n\t\t}\n\t\tlastErr = err\n\t}\n\n\tif fullErr == nil {\n\t\treturn full.route, nil\n\t}\n\tif lastErr != nil {\n\t\treturn nil, lastErr\n\t}\n\treturn nil, fullErr\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tedgeIndex int) lnwire.MilliSatoshi {\n\n\tif edgeIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[edgeIndex-1].AmtToForward\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tedgeIndex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif edgeIndex > 0 {\n\t\tfrom = rt.Hops[edgeIndex-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[edgeIndex].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc candidateFailureEdge(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc candidateIsLiquidityFailure(failure any) bool {\n\t_, ok := failure.(*lnwire.FailTemporaryChannelFailure)\n\treturn ok\n}\n\nfunc candidateIsPolicyFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey, belief candidateBelief) {\n\n\tbelief.stamp = r.epoch\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tcandidateMemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tif totalAmt > belief.lowerOK {\n\t\tbelief.lowerOK = totalAmt\n\t}\n\n\t// Success attribution is always truthful and invalidates incompatible\n\t// failure observations immediately.\n\tif belief.upperFail > 0 && totalAmt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 && totalAmt >= belief.suspectAmt {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\t// Do not allow an isolated old accusation to corroborate a new one\n\t// after background traffic has had several payments to move liquidity.\n\tif belief.stamp > 0 && r.epoch > belief.stamp+4 {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tcorroborates := false\n\tif belief.suspectAmt > 0 {\n\t\tlow := belief.suspectAmt * 60 / 100\n\t\thigh := belief.suspectAmt * 150 / 100\n\t\tcorroborates = totalAmt >= low && totalAmt <= high\n\t}\n\n\tif corroborates {\n\t\tif belief.suspectVotes < 255 {\n\t\t\tbelief.suspectVotes++\n\t\t}\n\t\tif totalAmt < belief.suspectAmt {\n\t\t\tbelief.suspectAmt = totalAmt\n\t\t}\n\t} else {\n\t\tbelief.suspectAmt = totalAmt\n\t\tbelief.suspectVotes = 1\n\t}\n\n\trequiredVotes := uint8(2)\n\tif belief.lowerOK > 0 && totalAmt <= belief.lowerOK {\n\t\trequiredVotes = 3\n\t}\n\n\tif belief.suspectVotes >= requiredVotes {\n\t\tif totalAmt <= belief.lowerOK {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\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 nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, totalAmt)\n\t\t\tr.reserved[key] += amtOver\n\n\t\t\t// A held shard is reliable evidence, but a modest diversity\n\t\t\t// cost keeps the next sibling from consuming the same hidden\n\t\t\t// corridor merely because it just succeeded.\n\t\t\tif i > 0 {\n\t\t\t\tr.edgePenalty[key] = math.Max(\n\t\t\t\t\t0, r.edgePenalty[key]*0.30+0.25,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tr.edgePenalty[key] *= 0.30\n\t\t\t}\n\t\t}\n\n\t\tr.retryLimit = 0\n\t\tr.consecutiveFailure = 0\n\t\treturn nil\n\t}\n\n\tif r.consecutiveFailure < 255 {\n\t\tr.consecutiveFailure++\n\t}\n\n\t// Unreadable failure conveys route-level information only. Its small\n\t// diversity penalty is deliberately too weak to become a liquidity\n\t// ceiling or poison persistent memory.\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t3, r.edgePenalty[key]+0.10,\n\t\t)\n\t}\n\n\tfailIndex := candidateFailureEdge(\n\t\trt, result.FailureSource,\n\t)\n\tattributed := failIndex >= 0 && failIndex < len(rt.Hops)\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif candidateIsLiquidityFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tamtOver := candidateRouteAmount(rt, failIndex)\n\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\tr.recordFailure(key, totalAmt)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t6, r.edgePenalty[key]+0.85,\n\t\t)\n\n\t\t// Shifted blame is usually adjacent. Give neighboring edges only a\n\t\t// transient hint; repeated direct blame is still required before\n\t\t// any persistent bound is written.\n\t\tif failIndex > 0 {\n\t\t\tneighbor := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex-1,\n\t\t\t)\n\t\t\tr.edgePenalty[neighbor] = math.Min(\n\t\t\t\t3, r.edgePenalty[neighbor]+0.12,\n\t\t\t)\n\t\t}\n\t\tif failIndex+1 < len(rt.Hops) {\n\t\t\tneighbor := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex+1,\n\t\t\t)\n\t\t\tr.edgePenalty[neighbor] = math.Min(\n\t\t\t\t3, r.edgePenalty[neighbor]+0.12,\n\t\t\t)\n\t\t}\n\n\t\t// First try another corridor at the same deliberate shard size.\n\t\t// Only repeated failure starts lowering the shard.\n\t\tif r.consecutiveFailure == 1 {\n\t\t\tr.retryLimit = delivered\n\t\t} else {\n\t\t\tlower := delivered * 70 / 100\n\t\t\tif lower < 1_000_000 {\n\t\t\t\tlower = 1_000_000\n\t\t\t}\n\t\t\tr.retryLimit = lower\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif candidateIsPolicyFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t6, r.edgePenalty[key]+1.6,\n\t\t)\n\t}\n\n\t// Unknown attribution and policy failures should preserve the shard\n\t// size and seek a different route.\n\tr.retryLimit = delivered\n\treturn nil\n}"
}
},
{
"id": 12,
"parent": 3,
"score": 0.2315,
"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\"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\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\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) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n\n\tstamp uint64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\t// Reserved tracks liquidity already consumed or held by successful\n\t// shards in this payment.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// Penalties are payment-local. They diversify retries and MPP shards\n\t// without turning unreadable failures into persistent channel facts.\n\tedgePenalty map[candidateEdgeKey]float64\n\n\t// A corroborated liquidity failure requests a smaller first shard when\n\t// the next route set is planned.\n\tretryLimit lnwire.MilliSatoshi\n\n\t// A route set is built jointly before the first shard is returned. Later\n\t// calls consume this plan while the remaining amount matches.\n\tplan []*route.Route\n\tplanExpected lnwire.MilliSatoshi\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\tcandidateMemory.Lock()\n\tcandidateMemory.epoch++\n\tepoch := candidateMemory.epoch\n\n\tsnapshot := make(map[candidateEdgeKey]candidateBelief,\n\t\tlen(candidateMemory.beliefs))\n\tfor key, belief := range candidateMemory.beliefs {\n\t\tsnapshot[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: snapshot,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// candidatePrior models the dominant bimodal balance distribution. Tiny\n// amounts fit in either balance mode; larger amounts generally require the\n// channel's full mode, followed by a steep capacity cliff.\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.024)\n\thighMode := 1 / (1 + math.Exp(19*(x-0.81)))\n\n\treturn candidateClampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) evidenceConfidence(b candidateBelief) float64 {\n\tif b.stamp == 0 || r.epoch <= b.stamp+4 {\n\t\treturn 1\n\t}\n\n\tage := float64(r.epoch - b.stamp - 4)\n\treturn math.Max(0.15, math.Exp(-age/14))\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tif totalAmt <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\n\t\treturn 0.001\n\t}\n\n\tprior := candidatePrior(totalAmt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.stamp == 0 {\n\t\treturn prior\n\t}\n\n\tlearned := prior\n\n\tswitch {\n\tcase belief.lowerOK > 0 && totalAmt <= belief.lowerOK:\n\t\tlearned = 0.995\n\n\tcase belief.upperFail > 0 && totalAmt >= belief.upperFail:\n\t\t// Corroborated failures remain a strong warning, but never make an\n\t\t// edge impossible. This avoids terminal route loss after shifted\n\t\t// attribution or genuine liquidity movement.\n\t\tlearned = 0.018\n\n\tcase belief.lowerOK > 0 && belief.upperFail > belief.lowerOK:\n\t\tspan := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(totalAmt-belief.lowerOK) / span\n\t\tpos = math.Max(0, math.Min(1, pos))\n\t\tlearned = 0.995*(1-pos) + 0.018*pos\n\n\tcase belief.lowerOK > 0 && totalAmt > belief.lowerOK:\n\t\tratio := float64(belief.lowerOK) / float64(totalAmt)\n\t\tlearned = prior + (0.995-prior)*ratio*ratio\n\n\tcase belief.upperFail > 0 && totalAmt < belief.upperFail:\n\t\tratio := float64(totalAmt) / float64(belief.upperFail)\n\t\tlearned = prior * (1 - 0.72*ratio*ratio)\n\t}\n\n\t// A single attribution is quarantined as a soft hint. It is useful for\n\t// immediate diversification but cannot create a hard exclusion.\n\tif belief.upperFail == 0 && belief.suspectAmt > 0 &&\n\t\ttotalAmt >= belief.suspectAmt {\n\n\t\tlearned *= 0.62\n\t}\n\n\tconfidence := r.evidenceConfidence(belief)\n\treturn candidateClampProbability(\n\t\tconfidence*learned + (1-confidence)*prior,\n\t)\n}\n\ntype candidateItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype candidateQueue []*candidateItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype candidatePath struct {\n\troute *route.Route\n\tedges []*candidateEdge\n\tprobability float64\n\tpressure float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tamounts := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tamounts[r.spec.Target] = amt\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateItem)\n\t\tbestScore, ok := scores[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amounts[item.node]\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usable(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := arriving + r.reserved[edge.key]\n\t\t\tif totalLiquidity < arriving ||\n\t\t\t\ttotalLiquidity > edge.capacity {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source &&\n\t\t\t\ttotalLiquidity > r.localBalances[edge.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tp := r.probability(edge, totalLiquidity)\n\n\t\t\t// Reliability dominates. A meaningful per-hop charge prevents\n\t\t\t// tiny apparent probability gains from producing needlessly\n\t\t\t// long routes, while fees remain a secondary tie breaker.\n\t\t\tedgeCost := -math.Log(p) + 0.075\n\t\t\tif arriving > 0 {\n\t\t\t\tedgeCost += 11 * float64(fee) /\n\t\t\t\t\tfloat64(arriving)\n\t\t\t}\n\n\t\t\tload := float64(totalLiquidity) /\n\t\t\t\tfloat64(edge.capacity)\n\t\t\tedgeCost += 0.10 * load * load * load * load\n\t\t\tedgeCost += r.edgePenalty[edge.key]\n\n\t\t\tnewScore := item.score + edgeCost\n\t\t\toldScore, visited := scores[edge.from]\n\t\t\tif visited && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.from] = newScore\n\t\t\tamounts[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\n\t\t\theap.Push(pq, &candidateItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tpressure := 0.0\n\n\tfor i, edge := range path {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\ttotalLiquidity := amtOver + r.reserved[edge.key]\n\n\t\tprobability *= r.probability(edge, totalLiquidity)\n\n\t\tdenominator := edge.capacity\n\t\tif edge.from == r.source {\n\t\t\tdenominator = r.localBalances[edge.chanID]\n\t\t} else if belief, ok := r.beliefs[edge.key]; ok &&\n\t\t\tbelief.upperFail > 0 &&\n\t\t\tr.evidenceConfidence(belief) >= 0.72 &&\n\t\t\tbelief.upperFail < denominator {\n\n\t\t\tdenominator = belief.upperFail\n\t\t}\n\n\t\tif denominator > 0 {\n\t\t\tload := float64(totalLiquidity) /\n\t\t\t\tfloat64(denominator)\n\t\t\tif load > pressure {\n\t\t\t\tpressure = load\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &candidatePath{\n\t\troute: rt,\n\t\tedges: path,\n\t\tprobability: math.Max(probability, 1e-300),\n\t\tpressure: pressure,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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\tif amtOver[i] < amtOver[i+1] {\n\t\t\treturn nil, nil, errors.New(\"amount overflow\")\n\t\t}\n\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 := 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.to,\n\t\t\tChannelID: edge.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}, path, nil\n}\n\nfunc candidateCeilDiv(a lnwire.MilliSatoshi,\n\tb uint32) lnwire.MilliSatoshi {\n\n\tif b <= 1 {\n\t\treturn a\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(b)\n\treturn a/divisor + boolToMSat(a%divisor != 0)\n}\n\nfunc boolToMSat(value bool) lnwire.MilliSatoshi {\n\tif value {\n\t\treturn 1\n\t}\n\n\treturn 0\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tedgeIndex int) lnwire.MilliSatoshi {\n\n\tif edgeIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[edgeIndex-1].AmtToForward\n}\n\nfunc candidateRouteDelivered(rt *route.Route) lnwire.MilliSatoshi {\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tedgeIndex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif edgeIndex > 0 {\n\t\tfrom = rt.Hops[edgeIndex-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[edgeIndex].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[edgeIndex].PubKeyBytes,\n\t}\n}\n\nfunc candidateCopyAmounts(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, value := range source {\n\t\tresult[key] = value\n\t}\n\n\treturn result\n}\n\nfunc candidateCopyPenalties(\n\tsource map[candidateEdgeKey]float64,\n) map[candidateEdgeKey]float64 {\n\n\tresult := make(map[candidateEdgeKey]float64, len(source))\n\tfor key, value := range source {\n\t\tresult[key] = value\n\t}\n\n\treturn result\n}\n\ntype candidatePlan struct {\n\troutes []*route.Route\n\tscore float64\n}\n\nfunc (r *candidateRouter) addPlannedReservation(path *candidatePath) {\n\tfor i, edge := range path.edges {\n\t\tamtOver := candidateRouteAmount(path.route, i)\n\t\tr.reserved[edge.key] += amtOver\n\n\t\t// Planned siblings should prefer independent corridors. Shared\n\t\t// bottlenecks remain possible when the topology requires them.\n\t\tr.edgePenalty[edge.key] += 0.62\n\t}\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tnumParts uint32, firstLimit lnwire.MilliSatoshi) *candidatePlan {\n\n\tif numParts == 0 || total <= 0 {\n\t\treturn nil\n\t}\n\n\tconst minimumShard = lnwire.MilliSatoshi(1_000_000)\n\n\tsavedReserved := candidateCopyAmounts(r.reserved)\n\tsavedPenalty := candidateCopyPenalties(r.edgePenalty)\n\tdefer func() {\n\t\tr.reserved = savedReserved\n\t\tr.edgePenalty = savedPenalty\n\t}()\n\n\tremaining := total\n\troutes := make([]*route.Route, 0, numParts)\n\tscore := 0.0\n\n\tfor part := uint32(0); part < numParts; part++ {\n\t\tslots := numParts - part\n\t\tif remaining <= 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tfloor := candidateCeilDiv(remaining, slots)\n\t\tdesired := floor\n\n\t\tmaxDesired := remaining\n\t\tif slots > 1 {\n\t\t\tminimumTail := minimumShard *\n\t\t\t\tlnwire.MilliSatoshi(slots-1)\n\t\t\tif remaining <= minimumTail {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tmaxDesired = remaining - minimumTail\n\t\t}\n\n\t\tif part == 0 && firstLimit > 0 &&\n\t\t\tfirstLimit < desired {\n\n\t\t\tdesired = firstLimit\n\t\t} else if slots > 1 {\n\t\t\thigh := floor * 5 / 4\n\t\t\tif high > maxDesired {\n\t\t\t\thigh = maxDesired\n\t\t\t}\n\n\t\t\tif high > floor {\n\t\t\t\tfloorPath, floorErr := r.findRoute(floor)\n\t\t\t\thighPath, highErr := r.findRoute(high)\n\n\t\t\t\tif floorErr != nil && highErr != nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tif highErr == nil &&\n\t\t\t\t\t(floorErr != nil ||\n\t\t\t\t\t\t(highPath.probability >=\n\t\t\t\t\t\t\tfloorPath.probability*0.80 &&\n\t\t\t\t\t\t\thighPath.pressure <= 0.72)) {\n\n\t\t\t\t\tdesired = high\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif desired > maxDesired {\n\t\t\tdesired = maxDesired\n\t\t}\n\t\tif desired <= 0 || desired > remaining {\n\t\t\treturn nil\n\t\t}\n\n\t\tpath, err := r.findRoute(desired)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tdelivered := candidateRouteDelivered(path.route)\n\t\tif delivered <= 0 || delivered > remaining {\n\t\t\treturn nil\n\t\t}\n\n\t\tfee := path.route.TotalAmount - delivered\n\t\tfeeCost := 0.0\n\t\tif delivered > 0 && fee > 0 {\n\t\t\tfeeCost = 8 * float64(fee) / float64(delivered)\n\t\t}\n\n\t\tscore += math.Log(path.probability) - feeCost - 0.035\n\t\troutes = append(routes, path.route)\n\t\tr.addPlannedReservation(path)\n\t\tremaining -= delivered\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil\n\t}\n\n\treturn &candidatePlan{\n\t\troutes: routes,\n\t\tscore: score,\n\t}\n}\n\nfunc candidatePlanWidths(partsLeft uint32) []uint32 {\n\tif partsLeft <= 1 {\n\t\treturn []uint32{1}\n\t}\n\n\tseen := make(map[uint32]bool)\n\twidths := make([]uint32, 0, 12)\n\n\tadd := func(width uint32) {\n\t\tif width == 0 || width > partsLeft || seen[width] {\n\t\t\treturn\n\t\t}\n\t\tseen[width] = true\n\t\twidths = append(widths, width)\n\t}\n\n\tfor width := uint32(1); width <= partsLeft && width <= 8; width++ {\n\t\tadd(width)\n\t}\n\n\tadd(10)\n\tadd(12)\n\tadd(16)\n\tadd(24)\n\tadd(32)\n\tadd(partsLeft)\n\n\treturn widths\n}\n\nfunc (r *candidateRouter) buildBestPlan(\n\tamt lnwire.MilliSatoshi, partsLeft uint32) bool {\n\n\tvar best *candidatePlan\n\n\tfor _, width := range candidatePlanWidths(partsLeft) {\n\t\tplan := r.makePlan(amt, width, r.retryLimit)\n\t\tif plan == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif best == nil || plan.score > best.score {\n\t\t\tbest = plan\n\t\t}\n\t}\n\n\t// A lower retry limit can make every proposed route set infeasible.\n\t// Retry planning without it before terminally giving up.\n\tif best == nil && r.retryLimit > 0 {\n\t\tfor _, width := range candidatePlanWidths(partsLeft) {\n\t\t\tplan := r.makePlan(amt, width, 0)\n\t\t\tif plan == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif best == nil || plan.score > best.score {\n\t\t\t\tbest = plan\n\t\t\t}\n\t\t}\n\t}\n\n\tif best == nil || len(best.routes) == 0 {\n\t\treturn false\n\t}\n\n\tr.plan = best.routes\n\tr.planExpected = amt\n\treturn true\n}\n\nfunc (r *candidateRouter) clearPlan() {\n\tr.plan = nil\n\tr.planExpected = 0\n}\n\n// RequestRoute selects an entire route set in memory before committing its\n// first shard. Reservations and diversity penalties used during planning\n// make later shards account for earlier atomic-MPP holds.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tif len(r.plan) > 0 && r.planExpected != amt {\n\t\tr.clearPlan()\n\t}\n\n\tif len(r.plan) == 0 {\n\t\tif !r.buildBestPlan(amt, partsLeft) {\n\t\t\t// Retain a reactive fallback for unusual policy/min-HTLC\n\t\t\t// combinations that cannot form a complete planned set.\n\t\t\tdesired := candidateCeilDiv(amt, partsLeft)\n\t\t\tif r.retryLimit > 0 && r.retryLimit < desired {\n\t\t\t\tdesired = r.retryLimit\n\t\t\t}\n\n\t\t\tconst minimumShard =\n\t\t\t\tlnwire.MilliSatoshi(1_000_000)\n\n\t\t\tvar lastErr error\n\t\t\tfor desired > 0 {\n\t\t\t\tpath, err := r.findRoute(desired)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn path.route, nil\n\t\t\t\t}\n\t\t\t\tlastErr = err\n\n\t\t\t\tif desired <= minimumShard {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tnext := desired * 72 / 100\n\t\t\t\tif next < minimumShard {\n\t\t\t\t\tnext = minimumShard\n\t\t\t\t}\n\t\t\t\tif next >= desired {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tdesired = next\n\t\t\t}\n\n\t\t\tif lastErr == nil {\n\t\t\t\tlastErr = errors.New(\"no route found\")\n\t\t\t}\n\t\t\treturn nil, lastErr\n\t\t}\n\t}\n\n\trt := r.plan[0]\n\tr.plan = r.plan[1:]\n\n\tdelivered := candidateRouteDelivered(rt)\n\tif delivered <= 0 || delivered > r.planExpected {\n\t\tr.clearPlan()\n\t\treturn nil, errors.New(\"invalid planned route\")\n\t}\n\n\tr.planExpected -= delivered\n\treturn rt, nil\n}\n\nfunc candidateFailureEdge(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc candidateIsLiquidityFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc candidateIsPolicyFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey, belief candidateBelief) {\n\n\tbelief.stamp = r.epoch\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tcandidateMemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tif totalAmt > belief.lowerOK {\n\t\tbelief.lowerOK = totalAmt\n\t}\n\n\t// Success is truthful and therefore disproves conflicting failure\n\t// evidence immediately.\n\tif belief.upperFail > 0 && totalAmt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 && totalAmt >= belief.suspectAmt {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tcorroborates := false\n\tif belief.suspectAmt > 0 {\n\t\tlow := belief.suspectAmt * 60 / 100\n\t\thigh := belief.suspectAmt * 150 / 100\n\t\tcorroborates = totalAmt >= low && totalAmt <= high\n\t}\n\n\tif corroborates {\n\t\tif belief.suspectVotes < 255 {\n\t\t\tbelief.suspectVotes++\n\t\t}\n\t\tif totalAmt < belief.suspectAmt {\n\t\t\tbelief.suspectAmt = totalAmt\n\t\t}\n\t} else {\n\t\tbelief.suspectAmt = totalAmt\n\t\tbelief.suspectVotes = 1\n\t}\n\n\trequiredVotes := uint8(2)\n\tif belief.lowerOK > 0 && totalAmt <= belief.lowerOK {\n\t\t// A shifted attribution should not overturn truthful success.\n\t\trequiredVotes = 3\n\t}\n\n\tif belief.suspectVotes >= requiredVotes {\n\t\tif totalAmt <= belief.lowerOK {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) penalizeNeighborEdges(\n\trt *route.Route, failIndex int) {\n\n\tfor _, index := range []int{failIndex - 1, failIndex + 1} {\n\t\tif index < 0 || index >= len(rt.Hops) {\n\t\t\tcontinue\n\t\t}\n\n\t\tkey := candidateRouteEdgeKey(rt, index)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t4, r.edgePenalty[key]+0.24,\n\t\t)\n\t}\n}\n\n// ReportAttempt treats success as hard evidence, quarantines attributed\n// liquidity failures until corroborated, and never writes persistent\n// channel knowledge from an unreadable failure.\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 nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, totalAmt)\n\t\t\tr.reserved[key] += amtOver\n\n\t\t\t// If the precomputed plan is later exhausted, continue to\n\t\t\t// prefer another corridor for any additional shard.\n\t\t\tr.edgePenalty[key] = math.Min(\n\t\t\t\t4, r.edgePenalty[key]+0.42,\n\t\t\t)\n\t\t}\n\n\t\tr.retryLimit = 0\n\t\treturn nil\n\t}\n\n\t// A failed planned shard invalidates the rest of the joint plan.\n\tr.clearPlan()\n\n\t// Route-local diversity is safe even when the error is unreadable:\n\t// unlike a belief, it does not survive this payment.\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t4, r.edgePenalty[key]+0.22,\n\t\t)\n\t}\n\n\tfailIndex := candidateFailureEdge(\n\t\trt, result.FailureSource,\n\t)\n\tattributed := failIndex >= 0 && failIndex < len(rt.Hops)\n\n\tif candidateIsLiquidityFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tamtOver := candidateRouteAmount(rt, failIndex)\n\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\tr.recordFailure(key, totalAmt)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t7, r.edgePenalty[key]+1.05,\n\t\t)\n\n\t\t// Shifted blame is normally adjacent to the real failure. Apply\n\t\t// only temporary, weak penalties to those neighbors.\n\t\tr.penalizeNeighborEdges(rt, failIndex)\n\n\t\tdelivered := candidateRouteDelivered(rt)\n\t\tlowerRetry := delivered * 68 / 100\n\t\tif lowerRetry < 1_000_000 {\n\t\t\tlowerRetry = 1_000_000\n\t\t}\n\t\tr.retryLimit = lowerRetry\n\n\t\treturn nil\n\t}\n\n\tif candidateIsPolicyFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t7, r.edgePenalty[key]+1.7,\n\t\t)\n\t\tr.penalizeNeighborEdges(rt, failIndex)\n\t}\n\n\t// Unknown and policy failures request a fresh route set at the same\n\t// scale. They never create liquidity bounds.\n\tr.retryLimit = 0\n\treturn nil\n}"
}
},
{
"id": 13,
"parent": 0,
"score": 0.6289,
"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\"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\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) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\tsoftFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tfailVotes uint8\n\tconf float64\n\tepoch uint64\n}\n\nvar candidateKnowledge = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateMetaEdge struct {\n\tkey candidateEdgeKey\n\tcapacity lnwire.MilliSatoshi\n\tamt lnwire.MilliSatoshi\n\treserved lnwire.MilliSatoshi\n}\n\ntype candidateAttemptMeta struct {\n\tedges []candidateMetaEdge\n\tdelivered lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\troutePenalty map[candidateEdgeKey]float64\n\tpolicyVotes map[candidateEdgeKey]uint8\n\tpolicyBad map[candidateEdgeKey]bool\n\tpending map[*route.Route]*candidateAttemptMeta\n\tmaxShard lnwire.MilliSatoshi\n\tconsecutive int\n\tsuccessfulAmt lnwire.MilliSatoshi\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\tedgeByKey: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyVotes: make(map[candidateEdgeKey]uint8),\n\t\tpolicyBad: make(map[candidateEdgeKey]bool),\n\t\tpending: make(map[*route.Route]*candidateAttemptMeta),\n\t\tmaxShard: spec.Amount,\n\t\tsuccessfulAmt: 0,\n\t\tconsecutive: 0,\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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.edgeByKey[key] = 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\tcandidateKnowledge.Lock()\n\tcandidateKnowledge.epoch++\n\tr.epoch = candidateKnowledge.epoch\n\n\tfor key := range r.edgeByKey {\n\t\tb, ok := candidateKnowledge.beliefs[key]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tage := r.epoch - b.epoch\n\t\tb.conf *= math.Pow(0.96, float64(age))\n\t\tif b.conf < 0.08 {\n\t\t\tcontinue\n\t\t}\n\t\tr.beliefs[key] = b\n\t}\n\tcandidateKnowledge.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tif x < 0 {\n\t\tx = 0\n\t}\n\tif x > 1.2 {\n\t\tx = 1.2\n\t}\n\n\tlowMode := math.Exp(-x / 0.03)\n\thighMode := 1 / (1 + math.Exp((x-0.86)/0.065))\n\tp := 0.005 + 0.98*(0.48*lowMode+0.52*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) edgeProbability(e *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif e.key.from == r.source {\n\t\treturn 0.999\n\t}\n\n\tprior := candidatePrior(total, e.capacity)\n\tb, ok := r.beliefs[e.key]\n\tif !ok || b.conf <= 0 {\n\t\treturn prior\n\t}\n\n\tobserved := prior\n\tswitch {\n\tcase b.lowerOK > 0 && total <= b.lowerOK:\n\t\tobserved = 0.995\n\n\tcase b.upperFail > 0 && total >= b.upperFail:\n\t\tobserved = 0.003\n\n\tcase b.estimate > 0:\n\t\twidth := float64(e.capacity) * 0.12\n\t\tif width < 1 {\n\t\t\twidth = 1\n\t\t}\n\t\tz := (float64(total) - float64(b.estimate)) / width\n\t\tobserved = 0.01 + 0.98/(1+math.Exp(z))\n\t}\n\n\tconf := b.conf\n\tif conf > 0.995 {\n\t\tconf = 0.995\n\t}\n\tp := prior*(1-conf) + observed*conf\n\n\tif b.softFail > 0 && total >= b.softFail*3/4 {\n\t\tp *= 1 - 0.55*conf\n\t}\n\n\tp *= math.Exp(-0.35 * r.routePenalty[e.key])\n\tif p < 0.001 {\n\t\treturn 0.001\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tbestScore := make(map[route.Vertex]float64)\n\tneeded := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\tneeded[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tbest, ok := bestScore[item.node]\n\t\tif !ok || item.score > best+0.5 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.policyBad[edge.key] || !edge.usable(item.amount) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := item.amount + r.reserved[edge.key]\n\t\t\tif totalLiquidity > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\ttotalLiquidity > r.localBalances[edge.key.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := item.amount\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(item.amount)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, totalLiquidity)\n\t\t\triskBudget := 5_000 + 0.007*float64(item.amount)\n\t\t\tedgeScore := float64(fee) -\n\t\t\t\tmath.Log(p)*riskBudget + 1_000\n\t\t\tscore := item.score + edgeScore\n\n\t\t\told, exists := bestScore[edge.key.from]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.key.from] = score\n\t\t\tneeded[edge.key.from] = sending\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\tamount: sending,\n\t\t\t\tscore: score,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := needed[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tconst finalCltvDelta uint32 = 40\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\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\tlast := len(path) - 1\n\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\toutgoing := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] + outgoing.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(outgoing.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := finalCltvDelta\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = 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: forward,\n\t\t\tOutgoingTimeLock: expiry,\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 routeChannelAmount(rt *route.Route,\n\tindex int) lnwire.MilliSatoshi {\n\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc (r *candidateRouter) rememberRoute(rt *route.Route) {\n\tmeta := &candidateAttemptMeta{\n\t\tedges: make([]candidateMetaEdge, 0, len(rt.Hops)),\n\t}\n\tif len(rt.Hops) != 0 {\n\t\tmeta.delivered = rt.Hops[len(rt.Hops)-1].AmtToForward\n\t}\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tcapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edgeByKey[key]; edge != nil {\n\t\t\tcapacity = edge.capacity\n\t\t}\n\t\tmeta.edges = append(meta.edges, candidateMetaEdge{\n\t\t\tkey: key,\n\t\t\tcapacity: capacity,\n\t\t\tamt: routeChannelAmount(rt, i),\n\t\t\treserved: r.reserved[key],\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\tr.pending[rt] = meta\n}\n\nfunc (r *candidateRouter) largestFeasibleRoute(upper,\n\tfloor lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif floor < 1 {\n\t\tfloor = 1\n\t}\n\tif upper < floor {\n\t\tupper = floor\n\t}\n\n\tconst probes = 12\n\tvar (\n\t\tbest *route.Route\n\t\tbestAmt lnwire.MilliSatoshi\n\t\tfailedAt = upper\n\t)\n\n\tspan := upper - floor\n\tfor i := 0; i <= probes; i++ {\n\t\ttestAmt := upper - span*lnwire.MilliSatoshi(i)/probes\n\t\trt, err := r.findRoute(testAmt)\n\t\tif err != nil {\n\t\t\tfailedAt = testAmt\n\t\t\tcontinue\n\t\t}\n\t\tbest = rt\n\t\tbestAmt = testAmt\n\t\tbreak\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no feasible shard route\")\n\t}\n\n\tlow := bestAmt\n\thigh := failedAt\n\tif high <= low {\n\t\treturn best, nil\n\t}\n\n\tfor i := 0; i < 8 && high-low > 1_000; i++ {\n\t\tmid := low + (high-low)/2\n\t\trt, err := r.findRoute(mid)\n\t\tif err != nil {\n\t\t\thigh = mid\n\t\t\tcontinue\n\t\t}\n\t\tlow = mid\n\t\tbest = rt\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\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 reached\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminShard := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\n\tupper := amt\n\tif r.maxShard > 0 && r.maxShard < upper {\n\t\tupper = r.maxShard\n\t}\n\tif upper < minShard {\n\t\tupper = minShard\n\t}\n\n\trt, err := r.findRoute(upper)\n\tif err != nil {\n\t\tif partsLeft <= 1 {\n\t\t\treturn nil, err\n\t\t}\n\t\trt, err = r.largestFeasibleRoute(upper, minShard)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.rememberRoute(rt)\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) metaForRoute(\n\trt *route.Route) *candidateAttemptMeta {\n\n\tif meta, ok := r.pending[rt]; ok {\n\t\tdelete(r.pending, rt)\n\t\treturn meta\n\t}\n\n\tr.rememberRoute(rt)\n\tmeta := r.pending[rt]\n\tdelete(r.pending, rt)\n\treturn meta\n}\n\nfunc (r *candidateRouter) storeBelief(key candidateEdgeKey,\n\tb candidateBelief) {\n\n\tb.epoch = r.epoch\n\tr.beliefs[key] = b\n\n\tcandidateKnowledge.Lock()\n\told, ok := candidateKnowledge.beliefs[key]\n\tif ok && old.epoch > b.epoch {\n\t\tcandidateKnowledge.Unlock()\n\t\treturn\n\t}\n\tcandidateKnowledge.beliefs[key] = b\n\tcandidateKnowledge.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(edge candidateMetaEdge) {\n\ttotal := edge.reserved + edge.amt\n\tb := r.beliefs[edge.key]\n\n\tif total > b.lowerOK {\n\t\tb.lowerOK = total\n\t}\n\tif b.upperFail > 0 && total >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\tif b.softFail > 0 && total >= b.softFail {\n\t\tb.softFail = 0\n\t\tb.failVotes = 0\n\t}\n\n\thighEstimate := edge.capacity * 85 / 100\n\tif total > highEstimate {\n\t\thighEstimate = total\n\t}\n\tif highEstimate > b.estimate {\n\t\tb.estimate = highEstimate\n\t}\n\n\tb.conf = 0.995\n\tr.storeBelief(edge.key, b)\n}\n\nfunc candidateComparableAmounts(a,\n\tb lnwire.MilliSatoshi) bool {\n\n\tif a <= 0 || b <= 0 {\n\t\treturn false\n\t}\n\taf := float64(a)\n\tbf := float64(b)\n\treturn af <= 4*bf && bf <= 4*af\n}\n\nfunc (r *candidateRouter) recordLiquidityFailure(\n\tedge candidateMetaEdge) {\n\n\ttotal := edge.reserved + edge.amt\n\tb := r.beliefs[edge.key]\n\n\tif b.lowerOK > 0 && total <= b.lowerOK {\n\t\tif b.softFail == 0 || total < b.softFail {\n\t\t\tb.softFail = total\n\t\t}\n\t\tif b.conf < 0.25 {\n\t\t\tb.conf = 0.25\n\t\t}\n\t\tr.storeBelief(edge.key, b)\n\t\treturn\n\t}\n\n\tif b.softFail == 0 || total < b.softFail {\n\t\tif b.softFail != 0 &&\n\t\t\t!candidateComparableAmounts(total, b.softFail) {\n\n\t\t\tb.failVotes = 0\n\t\t}\n\t\tb.softFail = total\n\t}\n\n\tif b.failVotes < 2 {\n\t\tb.failVotes++\n\t}\n\n\tif b.failVotes >= 2 {\n\t\tif b.upperFail == 0 || total < b.upperFail {\n\t\t\tb.upperFail = total\n\t\t}\n\t\testimate := total / 2\n\t\tif b.estimate == 0 || estimate < b.estimate {\n\t\t\tb.estimate = estimate\n\t\t}\n\t\tif b.conf < 0.92 {\n\t\t\tb.conf = 0.92\n\t\t}\n\t} else if b.conf < 0.65 {\n\t\tb.conf = 0.65\n\t}\n\n\tr.storeBelief(edge.key, b)\n}\n\nfunc (r *candidateRouter) failureEdge(meta *candidateAttemptMeta,\n\trt *route.Route, source route.Vertex) (candidateMetaEdge, bool) {\n\n\tindex := -1\n\tif source == rt.SourcePubKey {\n\t\tindex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tindex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif index < 0 || index >= len(meta.edges) {\n\t\treturn candidateMetaEdge{}, false\n\t}\n\treturn meta.edges[index], true\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tmeta := r.metaForRoute(rt)\n\tif meta == nil {\n\t\treturn nil\n\t}\n\n\tif result.Failure == nil {\n\t\tr.consecutive = 0\n\t\tr.successfulAmt += meta.delivered\n\n\t\tfor _, edge := range meta.edges {\n\t\t\tr.recordSuccess(edge)\n\t\t\tr.reserved[edge.key] += edge.amt\n\n\t\t\tr.routePenalty[edge.key] *= 0.5\n\t\t\tr.routePenalty[edge.key] += 0.25\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.consecutive++\n\tfor _, edge := range meta.edges {\n\t\tr.routePenalty[edge.key] += 0.08\n\t}\n\n\tfailedEdge, attributed := r.failureEdge(\n\t\tmeta, rt, result.FailureSource,\n\t)\n\tcode := result.Failure.Code()\n\n\tswitch {\n\tcase attributed &&\n\t\tcode == lnwire.CodeTemporaryChannelFailure:\n\n\t\tr.routePenalty[failedEdge.key] += 1.25\n\t\tr.recordLiquidityFailure(failedEdge)\n\n\tcase attributed &&\n\t\t(code == lnwire.CodeFeeInsufficient ||\n\t\t\tcode == lnwire.CodeIncorrectCltvExpiry):\n\n\t\tr.routePenalty[failedEdge.key] += 2\n\t\tr.policyVotes[failedEdge.key]++\n\t\tif r.policyVotes[failedEdge.key] >= 2 {\n\t\t\tr.policyBad[failedEdge.key] = true\n\t\t}\n\n\tcase !attributed:\n\t\tfor _, edge := range meta.edges {\n\t\t\tr.routePenalty[edge.key] += 0.12\n\t\t}\n\t}\n\n\tif r.consecutive%2 == 0 && r.spec.MaxParts > 1 {\n\t\treduced := meta.delivered * 7 / 10\n\t\tif reduced < 1 {\n\t\t\treduced = 1\n\t\t}\n\t\tif r.maxShard == 0 || reduced < r.maxShard {\n\t\t\tr.maxShard = reduced\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 14,
"parent": 3,
"score": 0.3639,
"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\"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\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateMemoryKey struct {\n\tnetwork uint64\n\tedge candidateEdgeKey\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\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) usable(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\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n\n\tstamp uint64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tepochs map[uint64]uint64\n\tbeliefs map[candidateMemoryKey]candidateBelief\n}{\n\tepochs: make(map[uint64]uint64),\n\tbeliefs: make(map[candidateMemoryKey]candidateBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tnetwork uint64\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tplan []*candidatePlanned\n\tretryLimit lnwire.MilliSatoshi\n\n\tattempts uint32\n\tfailureStreak uint32\n\tamountFailures map[lnwire.MilliSatoshi]uint16\n}\n\nfunc candidateMix64(x uint64) uint64 {\n\tx ^= x >> 30\n\tx *= 0xbf58476d1ce4e5b9\n\tx ^= x >> 27\n\tx *= 0x94d049bb133111eb\n\tx ^= x >> 31\n\treturn x\n}\n\nfunc candidateEdgeHash(e *candidateEdge) uint64 {\n\th := uint64(1469598103934665603)\n\tadd := func(v uint64) {\n\t\tfor i := 0; i < 8; i++ {\n\t\t\th ^= v & 0xff\n\t\t\th *= 1099511628211\n\t\t\tv >>= 8\n\t\t}\n\t}\n\n\tadd(e.chanID)\n\tadd(uint64(e.capacity))\n\tadd(uint64(e.baseFeeMsat))\n\tadd(uint64(e.feeRatePPM))\n\tadd(uint64(e.timeLockDelta))\n\tadd(uint64(e.minHTLC))\n\tadd(uint64(e.maxHTLC))\n\n\tfor _, b := range e.from {\n\t\th ^= uint64(b)\n\t\th *= 1099511628211\n\t}\n\tfor _, b := range e.to {\n\t\th ^= uint64(b)\n\t\th *= 1099511628211\n\t}\n\n\treturn h\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tamountFailures: make(map[lnwire.MilliSatoshi]uint16),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\n\tvar signatureSum uint64\n\tvar signatureXor uint64\n\tvar edgeCount 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,\n\t\t\tfunc(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},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\n\t\t\t\th := candidateMix64(candidateEdgeHash(edge))\n\t\t\t\tsignatureSum += h\n\t\t\t\tsignatureXor ^= candidateMix64(\n\t\t\t\t\th + 0x9e3779b97f4a7c15,\n\t\t\t\t)\n\t\t\t\tedgeCount++\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\trotated := signatureXor<<17 | signatureXor>>47\n\tr.network = candidateMix64(\n\t\tsignatureSum ^ rotated ^ candidateMix64(edgeCount),\n\t)\n\n\tcandidateMemory.Lock()\n\tcandidateMemory.epochs[r.network]++\n\tr.epoch = candidateMemory.epochs[r.network]\n\n\tfor key, belief := range candidateMemory.beliefs {\n\t\tif key.network == r.network {\n\t\t\tr.beliefs[key.edge] = belief\n\t\t}\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\thighMode := 1 / (1 + math.Exp(18*(x-0.82)))\n\n\treturn candidateClampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) evidenceConfidence(b candidateBelief) float64 {\n\tif b.stamp == 0 || r.epoch <= b.stamp+3 {\n\t\treturn 1\n\t}\n\n\tage := float64(r.epoch - b.stamp - 3)\n\treturn math.Max(0.12, math.Exp(-age/12))\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tif totalAmt <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := candidatePrior(totalAmt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.stamp == 0 {\n\t\treturn prior\n\t}\n\n\tlearned := prior\n\n\tswitch {\n\tcase belief.lowerOK > 0 && totalAmt <= belief.lowerOK:\n\t\tlearned = 0.995\n\n\tcase belief.upperFail > 0 && totalAmt >= belief.upperFail:\n\t\tlearned = 0.008\n\n\tcase belief.lowerOK > 0 && belief.upperFail > belief.lowerOK:\n\t\tspan := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(totalAmt-belief.lowerOK) / span\n\t\tpos = math.Max(0, math.Min(1, pos))\n\t\tlearned = 0.995*(1-pos) + 0.008*pos\n\n\tcase belief.lowerOK > 0 && totalAmt > belief.lowerOK:\n\t\tratio := float64(belief.lowerOK) / float64(totalAmt)\n\t\tlearned = prior + (0.995-prior)*ratio*ratio\n\n\tcase belief.upperFail > 0 && totalAmt < belief.upperFail:\n\t\tratio := float64(totalAmt) / float64(belief.upperFail)\n\t\tlearned = prior * (1 - 0.75*ratio*ratio)\n\t}\n\n\tif belief.upperFail == 0 && belief.suspectAmt > 0 &&\n\t\ttotalAmt >= belief.suspectAmt {\n\n\t\tlearned *= 0.62\n\t}\n\n\tconfidence := r.evidenceConfidence(belief)\n\treturn candidateClampProbability(\n\t\tconfidence*learned + (1-confidence)*prior,\n\t)\n}\n\nfunc (r *candidateRouter) hardFailed(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) bool {\n\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.upperFail == 0 {\n\t\treturn false\n\t}\n\n\treturn r.evidenceConfidence(belief) >= 0.72 &&\n\t\ttotalAmt >= belief.upperFail\n}\n\ntype candidateItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype candidateQueue []*candidateItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype candidatePath struct {\n\troute *route.Route\n\tedges []*candidateEdge\n\tprobability float64\n\tpressure float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tamounts := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tamounts[r.spec.Target] = amt\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateItem)\n\t\tbestScore, ok := scores[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amounts[item.node]\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usable(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := arriving + r.reserved[edge.key]\n\t\t\tif totalLiquidity < arriving ||\n\t\t\t\ttotalLiquidity > edge.capacity {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tif totalLiquidity >\n\t\t\t\t\tr.localBalances[edge.chanID] {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else if r.hardFailed(edge, totalLiquidity) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, totalLiquidity)\n\t\t\tedgeCost := -math.Log(probability) + 0.065\n\t\t\tif arriving > 0 {\n\t\t\t\tedgeCost += 7 * float64(fee) /\n\t\t\t\t\tfloat64(arriving)\n\t\t\t}\n\t\t\tedgeCost += r.edgePenalty[edge.key]\n\n\t\t\tnewScore := item.score + edgeCost\n\t\t\toldScore, visited := scores[edge.from]\n\t\t\tif visited && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.from] = newScore\n\t\t\tamounts[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &candidateItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tpressure := 0.0\n\n\tfor i, edge := range path {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\ttotalLiquidity := amtOver + r.reserved[edge.key]\n\n\t\tprobability *= r.probability(edge, totalLiquidity)\n\n\t\tdenominator := edge.capacity\n\t\tif edge.from == r.source {\n\t\t\tdenominator = r.localBalances[edge.chanID]\n\t\t} else if belief, ok := r.beliefs[edge.key]; ok &&\n\t\t\tbelief.upperFail > 0 &&\n\t\t\tr.evidenceConfidence(belief) >= 0.72 &&\n\t\t\tbelief.upperFail < denominator {\n\n\t\t\tdenominator = belief.upperFail\n\t\t}\n\n\t\tif denominator > 0 {\n\t\t\tratio := float64(totalLiquidity) /\n\t\t\t\tfloat64(denominator)\n\t\t\tif ratio > pressure {\n\t\t\t\tpressure = ratio\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &candidatePath{\n\t\troute: rt,\n\t\tedges: path,\n\t\tprobability: probability,\n\t\tpressure: pressure,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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 := 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.to,\n\t\t\tChannelID: edge.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}, path, nil\n}\n\ntype candidatePlanned struct {\n\tpath *candidatePath\n\tdelivered lnwire.MilliSatoshi\n}\n\nfunc candidateCopyAmounts(\n\tsrc map[candidateEdgeKey]lnwire.MilliSatoshi) map[\n\tcandidateEdgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, value := range src {\n\t\tdst[key] = value\n\t}\n\treturn dst\n}\n\nfunc candidateCopyPenalties(\n\tsrc map[candidateEdgeKey]float64) map[candidateEdgeKey]float64 {\n\n\tdst := make(map[candidateEdgeKey]float64, len(src))\n\tfor key, value := range src {\n\t\tdst[key] = value\n\t}\n\treturn dst\n}\n\nfunc candidateWeightSets(parts int) [][]int {\n\tswitch parts {\n\tcase 1:\n\t\treturn [][]int{{1}}\n\n\tcase 2:\n\t\treturn [][]int{\n\t\t\t{1, 1},\n\t\t\t{2, 3},\n\t\t\t{3, 2},\n\t\t\t{1, 2},\n\t\t\t{2, 1},\n\t\t}\n\n\tcase 3:\n\t\treturn [][]int{\n\t\t\t{1, 1, 1},\n\t\t\t{1, 2, 2},\n\t\t\t{2, 2, 1},\n\t\t\t{1, 2, 3},\n\t\t\t{3, 2, 1},\n\t\t}\n\n\tcase 4:\n\t\treturn [][]int{\n\t\t\t{1, 1, 1, 1},\n\t\t\t{1, 1, 2, 2},\n\t\t\t{2, 2, 1, 1},\n\t\t\t{1, 2, 2, 3},\n\t\t\t{3, 2, 2, 1},\n\t\t}\n\t}\n\n\tequal := make([]int, parts)\n\trising := make([]int, parts)\n\tfalling := make([]int, parts)\n\tfor i := 0; i < parts; i++ {\n\t\tequal[i] = 1\n\t\trising[i] = i/2 + 1\n\t\tfalling[parts-1-i] = rising[i]\n\t}\n\n\treturn [][]int{equal, rising, falling}\n}\n\nfunc candidateAmountsFromWeights(total lnwire.MilliSatoshi,\n\tweights []int) ([]lnwire.MilliSatoshi, bool) {\n\n\tconst minimumShard = lnwire.MilliSatoshi(1_000_000)\n\n\tsum := 0\n\tfor _, weight := range weights {\n\t\tif weight <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\t\tsum += weight\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(weights))\n\tremaining := total\n\n\tfor i, weight := range weights {\n\t\tif i == len(weights)-1 {\n\t\t\tamounts[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tamount := total * lnwire.MilliSatoshi(weight) /\n\t\t\tlnwire.MilliSatoshi(sum)\n\t\tif amount < minimumShard {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tamounts[i] = amount\n\t\tremaining -= amount\n\t}\n\n\tfor _, amount := range amounts {\n\t\tif amount < minimumShard {\n\t\t\treturn nil, false\n\t\t}\n\t}\n\n\treturn amounts, true\n}\n\nfunc (r *candidateRouter) makePlan(\n\tamounts []lnwire.MilliSatoshi) ([]*candidatePlanned, error) {\n\n\trealReserved := r.reserved\n\trealPenalty := r.edgePenalty\n\n\tr.reserved = candidateCopyAmounts(realReserved)\n\tr.edgePenalty = candidateCopyPenalties(realPenalty)\n\n\tdefer func() {\n\t\tr.reserved = realReserved\n\t\tr.edgePenalty = realPenalty\n\t}()\n\n\tplans := make([]*candidatePlanned, 0, len(amounts))\n\n\tfor _, amount := range amounts {\n\t\tpath, err := r.findRoute(amount)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tplans = append(plans, &candidatePlanned{\n\t\t\tpath: path,\n\t\t\tdelivered: amount,\n\t\t})\n\n\t\tfor i, edge := range path.edges {\n\t\t\tamtOver := candidateRouteAmount(path.route, i)\n\t\t\tr.reserved[edge.key] += amtOver\n\n\t\t\t// The next planned shard should prefer another corridor.\n\t\t\t// Aggregate reservations still allow reuse when necessary.\n\t\t\tr.edgePenalty[edge.key] += 0.48\n\t\t}\n\t}\n\n\treturn plans, nil\n}\n\nfunc (r *candidateRouter) planCost(plans []*candidatePlanned,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tincrements := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tedges := make(map[candidateEdgeKey]*candidateEdge)\n\n\tvar totalFees lnwire.MilliSatoshi\n\thopCount := 0\n\n\tfor _, planned := range plans {\n\t\trt := planned.path.route\n\t\tif rt.TotalAmount >= planned.delivered {\n\t\t\ttotalFees += rt.TotalAmount - planned.delivered\n\t\t}\n\n\t\thopCount += len(planned.path.edges)\n\t\tfor i, edge := range planned.path.edges {\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\tincrements[edge.key] += amtOver\n\t\t\tedges[edge.key] = edge\n\t\t}\n\t}\n\n\tcost := 0.0\n\n\tfor key, increment := range increments {\n\t\tedge := edges[key]\n\t\taggregate := r.reserved[key] + increment\n\t\tif aggregate < increment || aggregate > edge.capacity {\n\t\t\treturn math.Inf(1)\n\t\t}\n\t\tif edge.from == r.source &&\n\t\t\taggregate > r.localBalances[edge.chanID] {\n\n\t\t\treturn math.Inf(1)\n\t\t}\n\n\t\tcost -= math.Log(r.probability(edge, aggregate))\n\t\tcost += 0.10 * r.edgePenalty[key]\n\t}\n\n\tif total > 0 {\n\t\tcost += 7 * float64(totalFees) / float64(total)\n\t}\n\n\tcost += 0.035 * float64(hopCount)\n\tcost += 0.10 * float64(len(plans)-1)\n\n\tif total < 50_000_000 {\n\t\tcost += 0.35 * float64(len(plans)-1)\n\t}\n\n\treturn cost\n}\n\nfunc (r *candidateRouter) selectPlan(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) ([]*candidatePlanned, error) {\n\n\tmaxParts := int(partsLeft)\n\tif maxParts > 6 {\n\t\tmaxParts = 6\n\t}\n\tif maxParts < 1 {\n\t\tmaxParts = 1\n\t}\n\n\tvar best []*candidatePlanned\n\tbestCost := math.Inf(1)\n\tvar lastErr error\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tfor _, weights := range candidateWeightSets(parts) {\n\t\t\tamounts, ok := candidateAmountsFromWeights(amt, weights)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif r.retryLimit > 0 && amounts[0] > r.retryLimit {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tplans, err := r.makePlan(amounts)\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcost := r.planCost(plans, amt)\n\t\t\tif cost < bestCost {\n\t\t\t\tbestCost = cost\n\t\t\t\tbest = plans\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\tif lastErr == nil {\n\t\t\tlastErr = errors.New(\"no route found\")\n\t\t}\n\t\treturn nil, lastErr\n\t}\n\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\t// Feasible payments normally resolve well before this point. This\n\t// prevents impossible payments from repeatedly probing a moving\n\t// network until the simulator's much larger outer attempt limit.\n\tif r.attempts >= 64 {\n\t\treturn nil, errors.New(\"route search exhausted\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tif len(r.plan) != 0 {\n\t\tvar plannedTotal lnwire.MilliSatoshi\n\t\tfor _, planned := range r.plan {\n\t\t\tplannedTotal += planned.delivered\n\t\t}\n\n\t\tif plannedTotal == amt &&\n\t\t\tuint32(len(r.plan)) <= partsLeft {\n\n\t\t\tplanned := r.plan[0]\n\t\t\tr.plan = r.plan[1:]\n\t\t\tr.attempts++\n\t\t\treturn planned.path.route, nil\n\t\t}\n\n\t\tr.plan = nil\n\t}\n\n\tplan, err := r.selectPlan(amt, partsLeft)\n\tif err == nil && len(plan) != 0 {\n\t\tr.plan = plan[1:]\n\t\tr.attempts++\n\t\treturn plan[0].path.route, nil\n\t}\n\n\tconst minimumShard = lnwire.MilliSatoshi(1_000_000)\n\n\tdesired := r.retryLimit\n\tif desired <= 0 || desired > amt {\n\t\tdesired = amt\n\t}\n\n\tvar lastErr = err\n\tfor desired >= minimumShard {\n\t\tpath, routeErr := r.findRoute(desired)\n\t\tif routeErr == nil {\n\t\t\tr.attempts++\n\t\t\treturn path.route, nil\n\t\t}\n\n\t\tlastErr = routeErr\n\t\tsmaller := desired * 70 / 100\n\t\tif smaller < minimumShard {\n\t\t\tsmaller = minimumShard\n\t\t}\n\t\tif smaller >= desired {\n\t\t\tbreak\n\t\t}\n\t\tdesired = smaller\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tedgeIndex int) lnwire.MilliSatoshi {\n\n\tif edgeIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[edgeIndex-1].AmtToForward\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tedgeIndex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif edgeIndex > 0 {\n\t\tfrom = rt.Hops[edgeIndex-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[edgeIndex].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc candidateFailureEdge(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc candidateIsLiquidityFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc candidateIsPolicyFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey, belief candidateBelief) {\n\n\tbelief.stamp = r.epoch\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tcandidateMemory.beliefs[candidateMemoryKey{\n\t\tnetwork: r.network,\n\t\tedge: key,\n\t}] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tif totalAmt > belief.lowerOK {\n\t\tbelief.lowerOK = totalAmt\n\t}\n\n\tif belief.upperFail > 0 && totalAmt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 && totalAmt >= belief.suspectAmt {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tcorroborates := false\n\tif belief.suspectAmt > 0 {\n\t\tlow := belief.suspectAmt * 60 / 100\n\t\thigh := belief.suspectAmt * 150 / 100\n\t\tcorroborates = totalAmt >= low && totalAmt <= high\n\t}\n\n\tif corroborates {\n\t\tif belief.suspectVotes < 255 {\n\t\t\tbelief.suspectVotes++\n\t\t}\n\t\tif totalAmt < belief.suspectAmt {\n\t\t\tbelief.suspectAmt = totalAmt\n\t\t}\n\t} else {\n\t\tbelief.suspectAmt = totalAmt\n\t\tbelief.suspectVotes = 1\n\t}\n\n\trequiredVotes := uint8(2)\n\tif belief.lowerOK > 0 && totalAmt <= belief.lowerOK {\n\t\trequiredVotes = 3\n\t}\n\n\tif belief.suspectVotes >= requiredVotes {\n\t\tif totalAmt <= belief.lowerOK {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\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 nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, totalAmt)\n\t\t\tr.reserved[key] += amtOver\n\t\t\tr.edgePenalty[key] *= 0.30\n\t\t}\n\n\t\tr.failureStreak = 0\n\t\tr.retryLimit = 0\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.failureStreak++\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\tr.amountFailures[delivered]++\n\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t4, r.edgePenalty[key]+0.18,\n\t\t)\n\t}\n\n\tfailIndex := candidateFailureEdge(\n\t\trt, result.FailureSource,\n\t)\n\tattributed := failIndex >= 0 && failIndex < len(rt.Hops)\n\n\tif candidateIsLiquidityFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tamtOver := candidateRouteAmount(rt, failIndex)\n\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\tr.recordFailure(key, totalAmt)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t7, r.edgePenalty[key]+1.15,\n\t\t)\n\n\t\t// Adjacent edges receive only a local diversity hint. Shifted\n\t\t// attribution therefore cannot create a neighboring hard bound.\n\t\tif failIndex > 0 {\n\t\t\tadjacent := candidateRouteEdgeKey(rt, failIndex-1)\n\t\t\tr.edgePenalty[adjacent] = math.Min(\n\t\t\t\t4, r.edgePenalty[adjacent]+0.12,\n\t\t\t)\n\t\t}\n\t\tif failIndex+1 < len(rt.Hops) {\n\t\t\tadjacent := candidateRouteEdgeKey(rt, failIndex+1)\n\t\t\tr.edgePenalty[adjacent] = math.Min(\n\t\t\t\t4, r.edgePenalty[adjacent]+0.12,\n\t\t\t)\n\t\t}\n\n\t\tlowerRetry := delivered * 70 / 100\n\t\tif lowerRetry < 1_000_000 {\n\t\t\tlowerRetry = 1_000_000\n\t\t}\n\t\tr.retryLimit = lowerRetry\n\t\treturn nil\n\t}\n\n\tif candidateIsPolicyFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t7, r.edgePenalty[key]+2.0,\n\t\t)\n\n\t\t// Keep the amount unchanged: policy errors call for another\n\t\t// route, not a fictitious liquidity ceiling.\n\t\tr.retryLimit = delivered\n\t\treturn nil\n\t}\n\n\t// An unreadable failure only proves that this route failed. Try route\n\t// diversity first and reduce the shard only after repeated failures at\n\t// the same amount.\n\tif r.amountFailures[delivered] >= 3 {\n\t\tlowerRetry := delivered * 78 / 100\n\t\tif lowerRetry < 1_000_000 {\n\t\t\tlowerRetry = 1_000_000\n\t\t}\n\t\tr.retryLimit = lowerRetry\n\t} else {\n\t\tr.retryLimit = delivered\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 15,
"parent": 0,
"score": 0.3926,
"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\"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 candidateEdge struct {\n\tchanID uint64\n\tfrom, to route.Vertex\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) usable(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 beliefKey struct {\n\tnetwork uint64\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tsuspectAmt lnwire.MilliSatoshi\n\tfailVotes uint16\n\tsuccesses uint16\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[beliefKey]liquidityBelief\n}{\n\tm: make(map[beliefKey]liquidityBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tnetwork uint64\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeIndex map[beliefKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[beliefKey]liquidityBelief\n\n\t// Amount already committed by successful shards in this payment.\n\treserved map[beliefKey]lnwire.MilliSatoshi\n\n\t// Per-payment evidence encourages route diversity without turning an\n\t// unreadable or shifted failure into a permanent liquidity bound.\n\troutePenalty map[beliefKey]float64\n\tfailAt map[beliefKey]lnwire.MilliSatoshi\n\tpolicyVotes map[beliefKey]uint16\n\n\tcurrentShard lnwire.MilliSatoshi\n\tfailStreak uint32\n\trequests uint32\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\tedgeIndex: make(map[beliefKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[beliefKey]liquidityBelief),\n\t\treserved: make(map[beliefKey]lnwire.MilliSatoshi),\n\t\troutePenalty: make(map[beliefKey]float64),\n\t\tfailAt: make(map[beliefKey]lnwire.MilliSatoshi),\n\t\tpolicyVotes: make(map[beliefKey]uint16),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\n\tvar graphFingerprint uint64\n\tvar edgeCount 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,\n\t\t\tfunc(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\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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[edge.to] = append(\n\t\t\t\t\tr.incomingEdges[edge.to], edge,\n\t\t\t\t)\n\t\t\t\tgraphFingerprint ^= fingerprintEdge(edge)\n\t\t\t\tedgeCount++\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.network = graphFingerprint ^ mix64(edgeCount)\n\n\tsharedBeliefs.Lock()\n\tfor _, edges := range r.incomingEdges {\n\t\tfor _, edge := range edges {\n\t\t\tkey := r.key(edge)\n\t\t\tr.edgeIndex[key] = edge\n\t\t\tif belief, ok := sharedBeliefs.m[key]; ok {\n\t\t\t\tr.beliefs[key] = belief\n\t\t\t}\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc mix64(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 fingerprintEdge(edge *candidateEdge) uint64 {\n\th := mix64(edge.chanID) ^ mix64(uint64(edge.capacity))\n\tfor i, b := range edge.from {\n\t\th ^= mix64(uint64(b) + uint64(i+1)*257)\n\t}\n\tfor i, b := range edge.to {\n\t\th ^= mix64(uint64(b) + uint64(i+1)*65537)\n\t}\n\treturn mix64(h)\n}\n\nfunc (r *candidateRouter) key(edge *candidateEdge) beliefKey {\n\treturn beliefKey{\n\t\tnetwork: r.network,\n\t\tchanID: edge.chanID,\n\t\tfrom: edge.from,\n\t\tto: edge.to,\n\t}\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\trequired lnwire.MilliSatoshi\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *dijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*dijkstraItem))\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.49 * math.Exp(-x/0.035)\n\thighMode := 0.49 / (1 + math.Exp((x-0.93)/0.035))\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc subtractFloor(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif b >= a {\n\t\treturn 0\n\t}\n\treturn a - b\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tkey := r.key(edge)\n\theld := r.reserved[key]\n\n\tif edge.from == r.source {\n\t\tavailable := subtractFloor(\n\t\t\tr.localBalances[edge.chanID], held,\n\t\t)\n\t\tif amt <= available {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tavailable := subtractFloor(edge.capacity, held)\n\tif amt > available {\n\t\treturn 0.001\n\t}\n\n\tprior := bimodalPrior(amt, available)\n\tbelief, ok := r.beliefs[key]\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tlower := subtractFloor(belief.lowerOK, held)\n\tupper := subtractFloor(belief.upperFail, held)\n\testimate := subtractFloor(belief.estimate, held)\n\tsuspect := subtractFloor(belief.suspectAmt, held)\n\n\tif lower > 0 && amt <= lower {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && upper == 0 {\n\t\treturn 0.005\n\t}\n\tif upper > 0 && amt >= upper {\n\t\treturn 0.007\n\t}\n\n\tp := prior\n\tobservations := float64(belief.successes) +\n\t\tmath.Min(float64(belief.failVotes)/2, 4)\n\n\tif estimate > 0 {\n\t\twidth := math.Max(float64(available)*0.08, 1)\n\t\tz := (float64(amt) - float64(estimate)) / width\n\t\tif z > 30 {\n\t\t\tz = 30\n\t\t} else if z < -30 {\n\t\t\tz = -30\n\t\t}\n\t\tpointP := 1 / (1 + math.Exp(z))\n\t\tweight := math.Min(0.82, 0.22*observations)\n\t\tp = (1-weight)*p + weight*pointP\n\t}\n\n\tif lower > 0 && upper > lower && amt > lower && amt < upper {\n\t\tposition := float64(amt-lower) / float64(upper-lower)\n\t\tboundedP := 0.99*(1-position) + 0.01*position\n\t\tp = 0.25*p + 0.75*boundedP\n\t}\n\n\t// One readable report is only a suspect observation. It influences\n\t// route ordering, but cannot create a hard exclusion.\n\tif belief.failVotes == 1 && suspect > 0 &&\n\t\tamt*4 >= suspect*3 {\n\n\t\tp *= 0.45\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) hardAvailable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) bool {\n\n\tkey := r.key(edge)\n\theld := r.reserved[key]\n\n\tif amt > subtractFloor(edge.capacity, held) {\n\t\treturn false\n\t}\n\tif edge.from == r.source {\n\t\treturn amt <= subtractFloor(\n\t\t\tr.localBalances[edge.chanID], held,\n\t\t)\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) findRouteMode(amt lnwire.MilliSatoshi,\n\trelaxed bool) (*route.Route, error) {\n\n\tbestScore := make(map[route.Vertex]float64)\n\trequired := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbestScore[r.spec.Target] = 0\n\trequired[r.spec.Target] = amt\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\trequired: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode := item.node\n\n\t\tscore, ok := bestScore[node]\n\t\tif !ok || item.score > score+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.required != required[node] {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tamtOver := item.required\n\t\t\tif !edge.usable(amtOver) ||\n\t\t\t\t!r.hardAvailable(edge, amtOver) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tkey := r.key(edge)\n\t\t\tbelief := r.beliefs[key]\n\t\t\tupper := subtractFloor(\n\t\t\t\tbelief.upperFail, r.reserved[key],\n\t\t\t)\n\n\t\t\tif !relaxed && belief.failVotes >= 3 &&\n\t\t\t\tbelief.upperFail > 0 &&\n\t\t\t\t(upper == 0 || amtOver >= upper) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, amtOver)\n\t\t\tstepScore := -math.Log(p) + 0.008\n\n\t\t\tfee := edge.fee(amtOver)\n\t\t\tsending := amtOver + fee\n\t\t\tif edge.from == r.source {\n\t\t\t\tsending = amtOver\n\t\t\t\tfee = 0\n\t\t\t}\n\n\t\t\tdenom := math.Max(float64(amtOver), 1)\n\t\t\tstepScore += 4 * float64(fee) / denom\n\t\t\tstepScore += r.routePenalty[key]\n\n\t\t\tif failedAt := r.failAt[key]; failedAt > 0 {\n\t\t\t\tif amtOver*10 >= failedAt*7 {\n\t\t\t\t\tstepScore += 1.35\n\t\t\t\t} else {\n\t\t\t\t\tstepScore += 0.04\n\t\t\t\t}\n\t\t\t}\n\t\t\tif votes := r.policyVotes[key]; votes > 0 {\n\t\t\t\tstepScore += 0.8 * float64(votes)\n\t\t\t}\n\n\t\t\tnewScore := item.score + stepScore\n\t\t\toldScore, seen := bestScore[edge.from]\n\t\t\tif seen && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.from] = newScore\n\t\t\trequired[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t\trequired: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn r.buildRoute(amt, next)\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\trt, err := r.findRouteMode(amt, false)\n\tif err == nil {\n\t\treturn rt, nil\n\t}\n\n\t// Hard failure bounds are deliberately reversible. If all paths have\n\t// suspect bounds, retry the best one instead of terminally poisoning\n\t// the only corridor.\n\treturn r.findRouteMode(amt, true)\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\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.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn &route.Route{\n\t\t\tTotalTimeLock: finalCltvDelta,\n\t\t\tTotalAmount: amt,\n\t\t\tSourcePubKey: r.source,\n\t\t}, nil\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\tlast := len(path) - 1\n\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforwardAmt := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforwardAmt = 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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forwardAmt,\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 (r *candidateRouter) routeLogProbability(rt *route.Route) float64 {\n\tlogP := 0.0\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := beliefKey{\n\t\t\tnetwork: r.network,\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeIndex[key]\n\t\tif edge == nil {\n\t\t\treturn math.Log(0.005)\n\t\t}\n\n\t\tamtOver := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamtOver = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tlogP += math.Log(r.edgeProbability(edge, amtOver))\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn logP\n}\n\nfunc ceilShard(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\tp := lnwire.MilliSatoshi(parts)\n\treturn (amt + p - 1) / p\n}\n\n// selectInitialShard compares complete route-set sizes without consuming\n// attempts. Equal allocations are only the initial plan: after each accepted\n// shard, reservations and the remaining amount produce unequal allocations\n// across the surviving corridors.\nfunc (r *candidateRouter) selectInitialShard(amt lnwire.MilliSatoshi,\n\tparts uint32) (*route.Route, lnwire.MilliSatoshi, error) {\n\n\tif parts <= 1 {\n\t\trt, err := r.findRoute(amt)\n\t\treturn rt, amt, err\n\t}\n\n\tlimit := parts\n\tif limit > 16 {\n\t\tlimit = 16\n\t}\n\n\tbestScore := math.Inf(-1)\n\tvar bestRoute *route.Route\n\tvar bestAmt lnwire.MilliSatoshi\n\n\tevaluate := func(n uint32) {\n\t\tshard := ceilShard(amt, n)\n\t\trt, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t// Approximate the probability of the whole planned route set.\n\t\t// The small part penalty breaks close ties toward faster commit.\n\t\tscore := float64(n)*r.routeLogProbability(rt) -\n\t\t\t0.018*float64(n)\n\t\tif score > bestScore {\n\t\t\tbestScore = score\n\t\t\tbestRoute = rt\n\t\t\tbestAmt = shard\n\t\t}\n\t}\n\n\tfor n := uint32(1); n <= limit; n++ {\n\t\tevaluate(n)\n\t}\n\tif parts > limit {\n\t\tevaluate(parts)\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\treturn bestRoute, bestAmt, 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(\"invalid payment amount\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts in flight\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminShard := ceilShard(amt, partsLeft)\n\n\tif r.currentShard == 0 {\n\t\trt, shard, err := r.selectInitialShard(amt, partsLeft)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.currentShard = shard\n\t\tr.requests++\n\t\treturn rt, nil\n\t}\n\n\tif r.currentShard > amt {\n\t\tr.currentShard = amt\n\t}\n\tif r.currentShard < minShard {\n\t\tr.currentShard = minShard\n\t}\n\n\tfor {\n\t\trt, err := r.findRoute(r.currentShard)\n\t\tif err == nil {\n\t\t\tr.requests++\n\t\t\treturn rt, nil\n\t\t}\n\t\tif r.currentShard <= minShard {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnext := r.currentShard * 72 / 100\n\t\tif next < minShard || next >= r.currentShard {\n\t\t\tnext = minShard\n\t\t}\n\t\tr.currentShard = next\n\t}\n}\n\nfunc (r *candidateRouter) routeEdges(rt *route.Route,\n\tfn func(beliefKey, *candidateEdge, lnwire.MilliSatoshi)) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := beliefKey{\n\t\t\tnetwork: r.network,\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tamtOver := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamtOver = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfn(key, r.edgeIndex[key], amtOver)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc (r *candidateRouter) storeBelief(key beliefKey,\n\tbelief liquidityBelief) {\n\n\tr.beliefs[key] = belief\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key beliefKey,\n\tedge *candidateEdge, amt lnwire.MilliSatoshi) {\n\n\tif edge == nil || edge.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\n\testimate := amt\n\tif edge.capacity > amt {\n\t\testimate += (edge.capacity - amt) * 7 / 10\n\t}\n\tif estimate > belief.estimate {\n\t\tbelief.estimate = estimate\n\t}\n\n\tif belief.successes < ^uint16(0) {\n\t\tbelief.successes++\n\t}\n\n\t// Truthful success disproves any contradictory failure attribution.\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 && amt >= belief.suspectAmt {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.failVotes = 0\n\t}\n\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordLiquidityFailure(key beliefKey,\n\tedge *candidateEdge, amt lnwire.MilliSatoshi) {\n\n\tif edge == nil || edge.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.suspectAmt == 0 || amt < belief.suspectAmt {\n\t\tbelief.suspectAmt = amt\n\t}\n\tif belief.failVotes < ^uint16(0) {\n\t\tbelief.failVotes++\n\t}\n\n\t// Require corroboration before writing a hard upper bound.\n\tif belief.failVotes >= 2 {\n\t\tif belief.upperFail == 0 || amt < belief.upperFail {\n\t\t\tbelief.upperFail = amt\n\t\t}\n\n\t\testimate := amt * 45 / 100\n\t\tif belief.estimate == 0 || estimate < belief.estimate {\n\t\t\tbelief.estimate = estimate\n\t\t}\n\t}\n\n\t// A later, smaller failure can invalidate an old lower bound. This is\n\t// also how beliefs recover when background traffic moves liquidity.\n\tif belief.lowerOK >= amt && belief.failVotes >= 2 {\n\t\tbelief.lowerOK = 0\n\t}\n\n\tr.storeBelief(key, belief)\n}\n\nfunc (r *candidateRouter) attributedFailure(rt *route.Route,\n\tsource route.Vertex) (beliefKey, *candidateEdge,\n\tlnwire.MilliSatoshi, bool) {\n\n\tfailIdx := -1\n\tif source == rt.SourcePubKey {\n\t\tfailIdx = 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\tfailIdx = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif failIdx < 0 || failIdx >= len(rt.Hops) {\n\t\treturn beliefKey{}, nil, 0, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif failIdx > 0 {\n\t\tfrom = rt.Hops[failIdx-1].PubKeyBytes\n\t}\n\thop := rt.Hops[failIdx]\n\tkey := beliefKey{\n\t\tnetwork: r.network,\n\t\tchanID: hop.ChannelID,\n\t\tfrom: from,\n\t\tto: hop.PubKeyBytes,\n\t}\n\n\tamtOver := rt.TotalAmount\n\tif failIdx > 0 {\n\t\tamtOver = rt.Hops[failIdx-1].AmtToForward\n\t}\n\n\treturn key, r.edgeIndex[key], amtOver, true\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.failStreak = 0\n\t\tr.currentShard = 0\n\n\t\tr.routeEdges(rt, func(key beliefKey, edge *candidateEdge,\n\t\t\tamt lnwire.MilliSatoshi) {\n\n\t\t\tr.reserved[key] += amt\n\t\t\tr.recordSuccess(key, edge, amt)\n\t\t})\n\t\treturn nil\n\t}\n\n\tr.failStreak++\n\n\t// A failed route is weak evidence for diversification, even when its\n\t// onion error cannot be read. It never creates a global bound.\n\tr.routeEdges(rt, func(key beliefKey, _ *candidateEdge,\n\t\t_ lnwire.MilliSatoshi) {\n\n\t\tr.routePenalty[key] += 0.055\n\t})\n\n\tkey, edge, failedAmt, attributed := r.attributedFailure(\n\t\trt, result.FailureSource,\n\t)\n\n\tcode := result.Failure.Code()\n\tliquidityFailure := code == lnwire.CodeTemporaryChannelFailure\n\tpolicyFailure := code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry\n\n\tif attributed {\n\t\tswitch {\n\t\tcase liquidityFailure:\n\t\t\tif old := r.failAt[key]; old == 0 || failedAmt < old {\n\t\t\t\tr.failAt[key] = failedAmt\n\t\t\t}\n\t\t\tr.recordLiquidityFailure(key, edge, failedAmt)\n\n\t\tcase policyFailure:\n\t\t\tif r.policyVotes[key] < ^uint16(0) {\n\t\t\t\tr.policyVotes[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\t// Try route diversity at the same size once, then descend smoothly.\n\t// Unattributed failures can trigger sizing changes but never poison a\n\t// particular directed channel.\n\tif !policyFailure && r.failStreak >= 2 {\n\t\tshard := rt.Hops[len(rt.Hops)-1].AmtToForward\n\t\tfactor := lnwire.MilliSatoshi(70)\n\t\tif attributed && liquidityFailure &&\n\t\t\tr.beliefs[key].failVotes >= 2 {\n\n\t\t\tfactor = 62\n\t\t}\n\t\tnext := shard * factor / 100\n\t\tif next > 0 && (r.currentShard == 0 ||\n\t\t\tnext < r.currentShard) {\n\n\t\t\tr.currentShard = next\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 16,
"parent": 1,
"score": 0.5653,
"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\"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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateObservation struct {\n\tamt lnwire.MilliSatoshi\n\tweight float64\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tevidence map[candidateEdgeKey][]candidateObservation\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tif spec == nil {\n\t\treturn nil, errors.New(\"nil payment specification\")\n\t}\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tevidence: make(map[candidateEdgeKey][]candidateObservation),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tconst desiredShard = lnwire.MilliSatoshi(50_000_000)\n\n\tif amt <= 20_000_000 {\n\t\treturn 1\n\t}\n\n\tparts := uint32((amt + desiredShard - 1) / desiredShard)\n\tif parts < 2 {\n\t\tparts = 2\n\t}\n\treturn parts\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.07 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tswitch {\n\tcase z > 30:\n\t\treturn 0.005\n\tcase z < -30:\n\t\treturn 0.995\n\tdefault:\n\t\treturn 1 / (1 + math.Exp(z))\n\t}\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.12 * float64(belief.conf)\n\t\t\tif weight > 0.72 {\n\t\t\t\tweight = 0.72\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.025)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tif pos > 0 {\n\t\t\t\t\tbounded := 0.995*(1-pos) + 0.025*pos\n\t\t\t\t\tp = 0.4*p + 0.6*bounded\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc candidateObservationScale(required,\n\tfailedAt lnwire.MilliSatoshi) float64 {\n\n\tif failedAt <= 0 || required >= failedAt {\n\t\treturn 1\n\t}\n\n\tratio := float64(required) / float64(failedAt)\n\tif ratio <= 0.40 {\n\t\treturn 0.03\n\t}\n\n\tx := (ratio - 0.40) / 0.60\n\treturn 0.03 + 0.97*x*x\n}\n\nfunc (r *candidateRouter) evidencePenalty(key candidateEdgeKey,\n\trequired lnwire.MilliSatoshi) float64 {\n\n\ttotal := 0.0\n\tfor _, observation := range r.evidence[key] {\n\t\ttotal += observation.weight *\n\t\t\tcandidateObservationScale(required, observation.amt)\n\t}\n\n\tif total > 7 {\n\t\treturn 7\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) addEvidence(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi, weight float64) {\n\n\tif amt <= 0 || weight <= 0 {\n\t\treturn\n\t}\n\n\tobservations := r.evidence[key]\n\tif len(observations) < 24 {\n\t\tr.evidence[key] = append(observations, candidateObservation{\n\t\t\tamt: amt,\n\t\t\tweight: weight,\n\t\t})\n\t\treturn\n\t}\n\n\tclosest := 0\n\tbestDistance := math.MaxFloat64\n\tfor i, observation := range observations {\n\t\tdistance := math.Abs(\n\t\t\tfloat64(observation.amt-amt) /\n\t\t\t\tmath.Max(1, float64(observation.amt)),\n\t\t)\n\t\tif distance < bestDistance {\n\t\t\tbestDistance = distance\n\t\t\tclosest = i\n\t\t}\n\t}\n\n\tobservations[closest].weight += weight\n\tif observations[closest].weight > 6 {\n\t\tobservations[closest].weight = 6\n\t}\n\tif amt < observations[closest].amt {\n\t\tobservations[closest].amt = amt\n\t}\n\tr.evidence[key] = observations\n}\n\nfunc (r *candidateRouter) clearDisprovedEvidence(key candidateEdgeKey,\n\tsuccessAmt lnwire.MilliSatoshi) {\n\n\tobservations := r.evidence[key]\n\tkept := observations[:0]\n\tfor _, observation := range observations {\n\t\tif observation.amt > successAmt {\n\t\t\tkept = append(kept, observation)\n\t\t}\n\t}\n\n\tif len(kept) == 0 {\n\t\tdelete(r.evidence, key)\n\t\treturn\n\t}\n\tr.evidence[key] = kept\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.01 +\n\t\tr.evidencePenalty(e.key, required)\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tcost += 0.25 +\n\t\t\t0.90*float64(reserved)/float64(e.capacity)\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tscore := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscore[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tbestScore, ok := score[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.amt != arriving[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := math.Max(1, float64(amt))\n\t\t\t\tfeeCost = 7 * float64(fee) / denom\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(edge, amtOver) + feeCost\n\n\t\t\toldScore, exists := score[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\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: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := score[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\tpathProbability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tpathProbability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, pathProbability, 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\tfor node := r.source; node != r.spec.Target; {\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts + r.failures/2\n\tif r.unknownFails >= 3 {\n\t\ttarget++\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\treturn target\n}\n\nfunc (r *candidateRouter) routeProven(rt *route.Route) bool {\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn false\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tif traversal.key.from == r.source {\n\t\t\tif r.localBalances[traversal.key.chanID] < required {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\t\tif belief == nil || belief.lowerOK < required {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminimumShard := candidateCeilDiv(amt, partsLeft)\n\n\tvar wholeRoute *route.Route\n\tif rt, probability, err := r.findRoute(amt); err == nil {\n\t\twholeRoute = rt\n\t\tif r.plannedParts == 1 ||\n\t\t\t(r.failures == 0 && probability >= 0.82 &&\n\t\t\t\tr.routeProven(rt)) {\n\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\ttargetParts := r.targetPartCount()\n\tdesiredSlots := uint32(1)\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\n\tbaseShard := candidateCeilDiv(amt, desiredSlots)\n\tif baseShard > amt {\n\t\tbaseShard = amt\n\t}\n\n\tfloor := minimumShard\n\tif partsLeft > 1 {\n\t\tfloor = minimumShard * 55 / 100\n\t\tif floor < 1 {\n\t\t\tfloor = 1\n\t\t}\n\t}\n\n\tif r.retryCap == 0 && desiredSlots > 1 {\n\t\tcandidates := []lnwire.MilliSatoshi{\n\t\t\tbaseShard * 2,\n\t\t\tbaseShard * 3 / 2,\n\t\t\tbaseShard * 5 / 4,\n\t\t\tbaseShard,\n\t\t}\n\n\t\tvar last lnwire.MilliSatoshi\n\t\tfor _, shard := range candidates {\n\t\t\tif shard > amt {\n\t\t\t\tshard = amt\n\t\t\t}\n\t\t\tif shard < floor {\n\t\t\t\tshard = floor\n\t\t\t}\n\t\t\tif shard == last {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlast = shard\n\n\t\t\trt, probability, err := r.findRoute(shard)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif shard == baseShard || probability >= 0.48 ||\n\t\t\t\tr.routeProven(rt) {\n\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t}\n\t}\n\n\tshard := baseShard\n\tif r.retryCap > 0 && r.retryCap < shard {\n\t\tshard = r.retryCap\n\t}\n\tif shard < floor {\n\t\tshard = floor\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tvar lastErr error\n\tfor {\n\t\trt, _, err := r.findRoute(shard)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\n\t\tif shard <= floor {\n\t\t\tbreak\n\t\t}\n\n\t\tnextShard := shard * 3 / 4\n\t\tif nextShard < floor {\n\t\t\tnextShard = floor\n\t\t}\n\t\tif nextShard == shard {\n\t\t\tnextShard--\n\t\t}\n\t\tshard = nextShard\n\t}\n\n\tif wholeRoute != nil && shard != amt {\n\t\treturn wholeRoute, nil\n\t}\n\treturn nil, lastErr\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\t\tif belief == nil {\n\t\t\tbelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t\t}\n\n\t\tif required > belief.lowerOK {\n\t\t\tbelief.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > belief.estimate {\n\t\t\tbelief.estimate = highEstimate\n\t\t}\n\t\tif belief.conf < 8 {\n\t\t\tbelief.conf++\n\t\t}\n\n\t\tif belief.upperFail > 0 && required >= belief.upperFail {\n\t\t\tbelief.upperFail = 0\n\t\t}\n\t\tif belief.suspectAmt > 0 &&\n\t\t\trequired >= belief.suspectAmt {\n\n\t\t\tbelief.suspectAmt = 0\n\t\t\tbelief.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tr.clearDisprovedEvidence(traversal.key, required)\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief := candidateBeliefStore.beliefs[key]; belief != nil {\n\t\t\tif belief.lowerOK > used {\n\t\t\t\tbelief.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tbelief.lowerOK = 0\n\t\t\t}\n\t\t\tif belief.estimate > used {\n\t\t\t\tbelief.estimate -= used\n\t\t\t} else {\n\t\t\t\tbelief.estimate = 0\n\t\t\t}\n\t\t\tif belief.upperFail > used {\n\t\t\t\tbelief.upperFail -= used\n\t\t\t} else {\n\t\t\t\tbelief.upperFail = 0\n\t\t\t}\n\t\t\tif belief.suspectAmt > used {\n\t\t\t\tbelief.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tbelief.suspectAmt = 0\n\t\t\t\tbelief.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) ([]candidateTraversal, int, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn traversals, -1, false\n\t}\n\n\tif source == rt.SourcePubKey {\n\t\treturn traversals, 0, len(traversals) > 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\tindex := i + 1\n\t\tif index >= len(traversals) {\n\t\t\treturn traversals, -1, false\n\t\t}\n\t\treturn traversals, index, true\n\t}\n\n\treturn traversals, -1, false\n}\n\nfunc (r *candidateRouter) recordPersistentFailure(\n\ttraversal candidateTraversal) {\n\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\tif belief == nil {\n\t\tbelief = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t}\n\n\tif belief.suspectVotes == 0 {\n\t\tbelief.suspectAmt = required\n\t\tbelief.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif belief.suspectAmt == 0 || required < belief.suspectAmt {\n\t\tbelief.suspectAmt = required\n\t}\n\tif belief.suspectVotes < 255 {\n\t\tbelief.suspectVotes++\n\t}\n\n\tif belief.suspectVotes < 3 {\n\t\treturn\n\t}\n\n\tif belief.upperFail == 0 ||\n\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\tbelief.upperFail = belief.suspectAmt\n\t}\n\n\tfailedEstimate := belief.suspectAmt * 60 / 100\n\tif belief.estimate == 0 || failedEstimate < belief.estimate {\n\t\tbelief.estimate = failedEstimate\n\t}\n\tif belief.conf < 8 {\n\t\tbelief.conf++\n\t}\n}\n\nfunc (r *candidateRouter) recordRouteEvidence(\n\ttraversals []candidateTraversal, weight float64) {\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tr.addEvidence(traversal.key, required, weight)\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\ttraversals, index, attributed := r.failureIndex(\n\t\trt, result.FailureSource,\n\t)\n\tcode := result.Failure.Code()\n\tdelivered := candidateDeliveredAmount(rt)\n\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t// Every failed route contains the true bottleneck, even when the\n\t\t// onion error cannot be read. Weak route-wide evidence permits\n\t\t// intersection across retries without persistent poisoning.\n\t\tr.recordRouteEvidence(traversals, 0.09)\n\n\t\tif attributed {\n\t\t\tfailed := traversals[index]\n\t\t\trequired := failed.amt + r.reserved[failed.key]\n\t\t\tr.addEvidence(failed.key, required, 0.52)\n\n\t\t\t// Shifted attribution is normally one adjacent hop away.\n\t\t\t// Giving both neighbors smaller votes lets repeated routes\n\t\t\t// identify the edge that consistently remains implicated.\n\t\t\tif index > 0 {\n\t\t\t\tneighbor := traversals[index-1]\n\t\t\t\tneighborAmt := neighbor.amt +\n\t\t\t\t\tr.reserved[neighbor.key]\n\t\t\t\tr.addEvidence(\n\t\t\t\t\tneighbor.key, neighborAmt, 0.20,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif index+1 < len(traversals) {\n\t\t\t\tneighbor := traversals[index+1]\n\t\t\t\tneighborAmt := neighbor.amt +\n\t\t\t\t\tr.reserved[neighbor.key]\n\t\t\t\tr.addEvidence(\n\t\t\t\t\tneighbor.key, neighborAmt, 0.20,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tr.recordPersistentFailure(failed)\n\t\t} else {\n\t\t\tr.unknownFails++\n\t\t}\n\n\t\tif delivered > 1 {\n\t\t\tr.retryCap = delivered * 60 / 100\n\t\t\tif r.retryCap < 1 {\n\t\t\t\tr.retryCap = 1\n\t\t\t}\n\t\t}\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.recordRouteEvidence(traversals, 0.04)\n\t\tif attributed {\n\t\t\tfailed := traversals[index]\n\t\t\trequired := failed.amt + r.reserved[failed.key]\n\t\t\tr.addEvidence(failed.key, required, 1.1)\n\n\t\t\tif index > 0 {\n\t\t\t\tneighbor := traversals[index-1]\n\t\t\t\tneighborAmt := neighbor.amt +\n\t\t\t\t\tr.reserved[neighbor.key]\n\t\t\t\tr.addEvidence(\n\t\t\t\t\tneighbor.key, neighborAmt, 0.18,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif index+1 < len(traversals) {\n\t\t\t\tneighbor := traversals[index+1]\n\t\t\t\tneighborAmt := neighbor.amt +\n\t\t\t\t\tr.reserved[neighbor.key]\n\t\t\t\tr.addEvidence(\n\t\t\t\t\tneighbor.key, neighborAmt, 0.18,\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tr.unknownFails++\n\t\t}\n\n\tdefault:\n\t\tr.unknownFails++\n\t\tr.recordRouteEvidence(traversals, 0.07)\n\n\t\tif r.unknownFails%2 == 0 && delivered > 1 {\n\t\t\tr.retryCap = delivered * 70 / 100\n\t\t\tif r.retryCap < 1 {\n\t\t\t\tr.retryCap = 1\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 17,
"parent": 2,
"score": 0.1295,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidatePlanPart struct {\n\tamt lnwire.MilliSatoshi\n\tpath []*candidateEdge\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\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tdiversion map[candidateEdgeKey]float64\n\tpolicyFails map[candidateEdgeKey]uint8\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\trequestNo uint64\n\tsearchSalt uint64\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\n\n\tplan []candidatePlanPart\n\tplanIndex int\n\tpendingPlan bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tdiversion: make(map[candidateEdgeKey]float64),\n\t\tpolicyFails: make(map[candidateEdgeKey]uint8),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tswitch {\n\tcase amt <= 20_000_000:\n\t\treturn 1\n\tcase amt <= 100_000_000:\n\t\treturn 2\n\tcase amt <= 400_000_000:\n\t\treturn 4\n\tcase amt <= 1_000_000_000:\n\t\treturn 6\n\tcase amt <= 3_000_000_000:\n\t\treturn 10\n\tdefault:\n\t\tif r.spec.MaxParts != 0 {\n\t\t\treturn r.spec.MaxParts\n\t\t}\n\t\treturn 12\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.002\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.06 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tswitch {\n\tcase z > 30:\n\t\treturn 0.005\n\tcase z < -30:\n\t\treturn 0.995\n\tdefault:\n\t\treturn 1 / (1 + math.Exp(z))\n\t}\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.13 * float64(belief.conf)\n\t\t\tif weight > 0.68 {\n\t\t\t\tweight = 0.68\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.022)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK &&\n\t\t\t\trequired > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tbounded := 0.995*(1-pos) + 0.022*pos\n\t\t\t\tp = 0.35*p + 0.65*bounded\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, exists := r.localFails[e.key]; exists &&\n\t\trequired >= local.upper {\n\n\t\tswitch {\n\t\tcase local.count >= 3:\n\t\t\tp *= 0.035\n\t\tcase local.count == 2:\n\t\t\tp *= 0.12\n\t\tdefault:\n\t\t\tp *= 0.45\n\t\t}\n\t}\n\n\tif p < 0.002 {\n\t\treturn 0.002\n\t}\n\tif p > 0.997 {\n\t\treturn 0.997\n\t}\n\treturn p\n}\n\nfunc candidateEdgeHash(key candidateEdgeKey, salt uint64) float64 {\n\th := key.chanID ^ salt ^ 0x9e3779b97f4a7c15\n\tfor _, b := range key.from {\n\t\th ^= uint64(b)\n\t\th *= 0x100000001b3\n\t}\n\tfor _, b := range key.to {\n\t\th ^= uint64(b) + 0x9d\n\t\th *= 0x100000001b3\n\t}\n\n\th ^= h >> 30\n\th *= 0xbf58476d1ce4e5b9\n\th ^= h >> 27\n\th *= 0x94d049bb133111eb\n\th ^= h >> 31\n\n\treturn float64(h>>11) / float64(uint64(1)<<53)\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.008\n\tcost += r.diversion[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tcost += 2.25 +\n\t\t\t3.25*float64(reserved)/float64(e.capacity)\n\t}\n\n\tif r.failures > 0 {\n\t\texploration := 0.08 + 0.02*float64(r.failures)\n\t\tif exploration > 0.55 {\n\t\t\texploration = 0.55\n\t\t}\n\t\tcost += exploration * candidateEdgeHash(\n\t\t\te.key, r.searchSalt,\n\t\t)\n\t}\n\n\treturn cost\n}\n\ntype candidateLabel struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n\tnextEdge *candidateEdge\n\tnext *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 candidateLabelContains(label *candidateLabel,\n\tnode route.Vertex) bool {\n\n\tfor current := label; current != nil; current = current.next {\n\t\tif current.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAddLabel(labels map[route.Vertex][]*candidateLabel,\n\tlabel *candidateLabel) bool {\n\n\texisting := labels[label.node]\n\tactiveCount := 0\n\n\tfor _, old := range existing {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tactiveCount++\n\n\t\tif old.score <= label.score && old.amt <= label.amt {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor _, old := range existing {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif label.score <= old.score && label.amt <= old.amt {\n\t\t\told.active = false\n\t\t\tactiveCount--\n\t\t}\n\t}\n\n\tif activeCount >= 10 {\n\t\tvar worst *candidateLabel\n\t\tfor _, old := range existing {\n\t\t\tif !old.active {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst == nil || old.score > worst.score {\n\t\t\t\tworst = old\n\t\t\t}\n\t\t}\n\t\tif worst != nil && worst.score <= label.score {\n\t\t\treturn false\n\t\t}\n\t\tif worst != nil {\n\t\t\tworst.active = false\n\t\t}\n\t}\n\n\tlabel.active = true\n\tlabels[label.node] = append(existing, label)\n\treturn true\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\ttarget := &candidateLabel{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamt: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidateLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tpq := &candidateLabelQueue{}\n\theap.Push(pq, target)\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\tif candidateLabelContains(item, edge.key.from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 8 * float64(fee) / denom\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 +\n\t\t\t\t\tr.edgeCost(edge, amtOver) + feeCost,\n\t\t\t\tamt: sending,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tnext: item,\n\t\t\t}\n\t\t\tif candidateAddLabel(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\tpath := make([]*candidateEdge, 0, 8)\n\tfor label := sourceLabel; label.nextEdge != nil; label = label.next {\n\t\tpath = append(path, label.nextEdge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tprobability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tprobability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, probability, 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\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamt, maximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amt > maximum {\n\t\tamt = maximum\n\t}\n\tif amt <= 0 {\n\t\treturn amounts\n\t}\n\n\tfor _, existing := range amounts {\n\t\tif existing == amt {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amt)\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRoutePath(rt *route.Route,\n\ttraversals []candidateTraversal) []*candidateEdge {\n\n\tpath := make([]*candidateEdge, 0, len(traversals))\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn path\n\t}\n\tfor _, traversal := range traversals {\n\t\tpath = append(path, traversal.edge)\n\t}\n\treturn path\n}\n\nfunc candidateRouteUtility(rt *route.Route, probability float64,\n\tshard, reference lnwire.MilliSatoshi) float64 {\n\n\tif probability < 1e-12 {\n\t\tprobability = 1e-12\n\t}\n\n\tprogress := float64(shard) / float64(reference)\n\tif progress < 1e-9 {\n\t\tprogress = 1e-9\n\t}\n\n\tdelivered := candidateDeliveredAmount(rt)\n\tfeeRatio := 0.0\n\tif delivered > 0 && rt.TotalAmount > delivered {\n\t\tfeeRatio = float64(rt.TotalAmount-delivered) /\n\t\t\tfloat64(delivered)\n\t}\n\n\treturn math.Log(probability) +\n\t\t0.62*math.Log(progress) - 3.0*feeRatio\n}\n\nfunc (r *candidateRouter) desiredPlanParts(inFlight uint32) uint32 {\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlight >= maxParts {\n\t\treturn 0\n\t}\n\n\ttarget := r.plannedParts\n\tif r.failures > 1 {\n\t\ttarget += (r.failures + 1) / 3\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target <= inFlight {\n\t\treturn 1\n\t}\n\treturn target - inFlight\n}\n\nfunc (r *candidateRouter) tryPlan(amt lnwire.MilliSatoshi,\n\tparts uint32) ([]candidatePlanPart, bool) {\n\n\tif parts == 0 || amt <= 0 {\n\t\treturn nil, false\n\t}\n\n\trealReserved := r.reserved\n\tvirtual := make(map[candidateEdgeKey]lnwire.MilliSatoshi,\n\t\tlen(realReserved))\n\tfor key, value := range realReserved {\n\t\tvirtual[key] = value\n\t}\n\tr.reserved = virtual\n\tdefer func() {\n\t\tr.reserved = realReserved\n\t}()\n\n\tremaining := amt\n\tplan := make([]candidatePlanPart, 0, parts)\n\n\tfor slot := uint32(0); slot < parts; slot++ {\n\t\tslotsLeft := parts - slot\n\t\tequal := candidateCeilDiv(remaining, slotsLeft)\n\n\t\tmaximum := remaining\n\t\tif slotsLeft > 1 {\n\t\t\tmaximum = remaining -\n\t\t\t\tlnwire.MilliSatoshi(slotsLeft-1)\n\t\t}\n\n\t\tamounts := make([]lnwire.MilliSatoshi, 0, 5)\n\t\tamounts = candidateAppendAmount(amounts, equal, maximum)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, equal*5/4, maximum,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, equal*3/2, maximum,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, equal*2, maximum,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, maximum, maximum,\n\t\t)\n\n\t\tvar bestRoute *route.Route\n\t\tvar bestPath []*candidateEdge\n\t\tbestScore := math.Inf(-1)\n\n\t\tfor _, shard := range amounts {\n\t\t\trt, probability, err := r.findRoute(shard)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttraversals := r.routeTraversals(rt)\n\t\t\tpath := candidateRoutePath(rt, traversals)\n\t\t\tif len(path) != len(rt.Hops) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := candidateRouteUtility(\n\t\t\t\trt, probability, shard, equal,\n\t\t\t)\n\t\t\tif score > bestScore {\n\t\t\t\tbestScore = score\n\t\t\t\tbestRoute = rt\n\t\t\t\tbestPath = path\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\tshard := candidateDeliveredAmount(bestRoute)\n\t\tplan = append(plan, candidatePlanPart{\n\t\t\tamt: shard,\n\t\t\tpath: bestPath,\n\t\t})\n\n\t\tfor _, traversal := range r.routeTraversals(bestRoute) {\n\t\t\tr.reserved[traversal.key] += traversal.amt\n\t\t}\n\n\t\tremaining -= shard\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, false\n\t}\n\treturn plan, true\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tinFlight uint32) {\n\n\tdesired := r.desiredPlanParts(inFlight)\n\tif desired == 0 {\n\t\treturn\n\t}\n\n\tfor parts := desired; parts >= 1; parts-- {\n\t\tplan, ok := r.tryPlan(amt, parts)\n\t\tif ok {\n\t\t\tr.plan = plan\n\t\t\tr.planIndex = 0\n\t\t\treturn\n\t\t}\n\t\tif parts == 1 {\n\t\t\tbreak\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tr.requestNo++\n\tr.searchSalt = r.requestNo*0x9e3779b97f4a7c15 +\n\t\tuint64(r.failures)*0xbf58476d1ce4e5b9\n\n\tif r.plan == nil {\n\t\tr.makePlan(amt, inFlightHtlcs)\n\t}\n\tif r.planIndex < len(r.plan) {\n\t\tpart := r.plan[r.planIndex]\n\t\tif part.amt > 0 && part.amt <= amt {\n\t\t\trt, err := r.buildRoute(part.amt, part.path)\n\t\t\tif err == nil {\n\t\t\t\tr.pendingPlan = true\n\t\t\t\treturn rt, nil\n\t\t\t}\n\t\t}\n\t\tr.plan = nil\n\t\tr.planIndex = 0\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tif partsLeft == 1 {\n\t\treturn r.findRouteOnly(amt)\n\t}\n\n\tequal := candidateCeilDiv(amt, partsLeft)\n\tamounts := make([]lnwire.MilliSatoshi, 0, 8)\n\n\tif r.retryCap > 0 {\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, r.retryCap*3/4, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, r.retryCap, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, r.retryCap*5/4, amt,\n\t\t)\n\t}\n\n\tamounts = candidateAppendAmount(amounts, equal/2, amt)\n\tamounts = candidateAppendAmount(amounts, equal, amt)\n\tamounts = candidateAppendAmount(amounts, equal*5/4, amt)\n\tamounts = candidateAppendAmount(amounts, equal*3/2, amt)\n\tamounts = candidateAppendAmount(amounts, amt, amt)\n\n\tvar bestRoute *route.Route\n\tbestScore := math.Inf(-1)\n\tvar lastErr error\n\n\tfor _, shard := range amounts {\n\t\trt, probability, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tscore := candidateRouteUtility(\n\t\t\trt, probability, shard, equal,\n\t\t)\n\n\t\tif shard < equal {\n\t\t\tdebt := float64(equal-shard) / float64(equal)\n\t\t\tscore -= 0.16 * debt\n\t\t}\n\n\t\tif score > bestScore {\n\t\t\tbestScore = score\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute != nil {\n\t\tr.pendingPlan = false\n\t\treturn bestRoute, nil\n\t}\n\tif lastErr != nil {\n\t\treturn nil, lastErr\n\t}\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) findRouteOnly(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\trt, _, err := r.findRoute(amt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr.pendingPlan = false\n\treturn rt, nil\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\t\tif belief == nil {\n\t\t\tbelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t\t}\n\n\t\tif required > belief.lowerOK {\n\t\t\tbelief.lowerOK = required\n\t\t}\n\n\t\testimate := traversal.edge.capacity * 92 / 100\n\t\tif required > estimate {\n\t\t\testimate = required\n\t\t}\n\t\tif estimate > belief.estimate {\n\t\t\tbelief.estimate = estimate\n\t\t}\n\t\tif belief.conf < 8 {\n\t\t\tbelief.conf++\n\t\t}\n\n\t\tif belief.upperFail > 0 && required >= belief.upperFail {\n\t\t\tbelief.upperFail = 0\n\t\t}\n\t\tif belief.suspectAmt > 0 && required >= belief.suspectAmt {\n\t\t\tbelief.suspectAmt = 0\n\t\t\tbelief.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.diversion[traversal.key] *= 0.20\n\t\tr.policyFails[traversal.key] = 0\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief := candidateBeliefStore.beliefs[key]; belief != nil {\n\t\t\tif belief.lowerOK > used {\n\t\t\t\tbelief.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tbelief.lowerOK = 0\n\t\t\t}\n\t\t\tif belief.estimate > used {\n\t\t\t\tbelief.estimate -= used\n\t\t\t} else {\n\t\t\t\tbelief.estimate = 0\n\t\t\t}\n\t\t\tif belief.upperFail > used {\n\t\t\t\tbelief.upperFail -= used\n\t\t\t} else {\n\t\t\t\tbelief.upperFail = 0\n\t\t\t}\n\t\t\tif belief.suspectAmt > used {\n\t\t\t\tbelief.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tbelief.suspectAmt = 0\n\t\t\t\tbelief.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\tcapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\tcapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\tcapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\t\tif capacity > 0 {\n\t\t\tif reverseBelief.lowerOK > capacity {\n\t\t\t\treverseBelief.lowerOK = capacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > capacity {\n\t\t\t\treverseBelief.estimate = capacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif capacity > 0 &&\n\t\t\t\treverseBelief.upperFail > capacity {\n\n\t\t\t\treverseBelief.upperFail = capacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex, traversals []candidateTraversal) (int, bool) {\n\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn 0, false\n\t}\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0, len(traversals) > 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\tindex := i + 1\n\t\tif index >= len(traversals) {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn index, true\n\t}\n\n\treturn 0, false\n}\n\nfunc (r *candidateRouter) addDiversion(\n\tkey candidateEdgeKey, amount float64) {\n\n\tr.diversion[key] += amount\n\tif r.diversion[key] > 7.0 {\n\t\tr.diversion[key] = 7.0\n\t}\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversals []candidateTraversal, index int) {\n\n\ttraversal := traversals[index]\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tr.addDiversion(traversal.key, 1.05)\n\tif index > 0 {\n\t\tr.addDiversion(traversals[index-1].key, 0.14)\n\t}\n\tif index+1 < len(traversals) {\n\t\tr.addDiversion(traversals[index+1].key, 0.14)\n\t}\n\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tif local.count < 255 {\n\t\tlocal.count++\n\t}\n\tr.localFails[traversal.key] = local\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\tif belief == nil {\n\t\tbelief = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t}\n\n\tif belief.suspectVotes == 0 {\n\t\tbelief.suspectAmt = required\n\t\tbelief.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif required < belief.suspectAmt {\n\t\tbelief.suspectAmt = required\n\t}\n\tif belief.suspectVotes < 255 {\n\t\tbelief.suspectVotes++\n\t}\n\n\tvotesNeeded := uint8(2)\n\tif belief.lowerOK > 0 &&\n\t\tbelief.suspectAmt <= belief.lowerOK {\n\n\t\tvotesNeeded = 3\n\t}\n\tif belief.suspectVotes < votesNeeded {\n\t\treturn\n\t}\n\n\tif belief.lowerOK > 0 &&\n\t\tbelief.suspectAmt <= belief.lowerOK {\n\n\t\tif belief.suspectAmt > 1 {\n\t\t\tbelief.lowerOK = belief.suspectAmt - 1\n\t\t} else {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\t}\n\n\tif belief.upperFail == 0 ||\n\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\tbelief.upperFail = belief.suspectAmt\n\t}\n\n\tfailedEstimate := belief.suspectAmt * 60 / 100\n\tif belief.estimate == 0 || failedEstimate < belief.estimate {\n\t\tbelief.estimate = failedEstimate\n\t}\n\tif belief.conf < 8 {\n\t\tbelief.conf++\n\t}\n}\n\nfunc (r *candidateRouter) diversifyUnknown(\n\ttraversals []candidateTraversal) {\n\n\tif len(traversals) == 0 {\n\t\treturn\n\t}\n\n\tindex := int((r.requestNo +\n\t\tuint64(r.unknownFails)*0x9e3779b97f4a7c15) %\n\t\tuint64(len(traversals)))\n\n\tr.addDiversion(traversals[index].key, 0.85)\n\n\tif r.unknownFails%12 == 0 {\n\t\tfor key, penalty := range r.diversion {\n\t\t\tpenalty *= 0.35\n\t\t\tif penalty < 0.01 {\n\t\t\t\tdelete(r.diversion, key)\n\t\t\t} else {\n\t\t\t\tr.diversion[key] = penalty\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\twasPlanned := r.pendingPlan\n\tr.pendingPlan = false\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\tif wasPlanned {\n\t\t\tr.planIndex++\n\t\t}\n\t\treturn nil\n\t}\n\n\tif wasPlanned {\n\t\tr.plan = nil\n\t\tr.planIndex = 0\n\t}\n\n\tr.failures++\n\ttraversals := r.routeTraversals(rt)\n\tcode := result.Failure.Code()\n\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tif index, ok := r.failureIndex(\n\t\t\trt, result.FailureSource, traversals,\n\t\t); ok {\n\t\t\tr.recordAttributedLiquidityFailure(\n\t\t\t\ttraversals, index,\n\t\t\t)\n\n\t\t\tdelivered := candidateDeliveredAmount(rt)\n\t\t\tif delivered > 1 {\n\t\t\t\tfactor := lnwire.MilliSatoshi(62)\n\t\t\t\tlocal := r.localFails[\n\t\t\t\t\ttraversals[index].key]\n\t\t\t\tif local.count >= 2 {\n\t\t\t\t\tfactor = 50\n\t\t\t\t}\n\t\t\t\tr.retryCap = delivered * factor / 100\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tr.unknownFails++\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tif index, ok := r.failureIndex(\n\t\t\trt, result.FailureSource, traversals,\n\t\t); ok {\n\t\t\tkey := traversals[index].key\n\t\t\tcount := r.policyFails[key]\n\t\t\tif count < 255 {\n\t\t\t\tcount++\n\t\t\t}\n\t\t\tr.policyFails[key] = count\n\n\t\t\tif count >= 2 {\n\t\t\t\tr.addDiversion(key, 4.0)\n\t\t\t} else {\n\t\t\t\tr.addDiversion(key, 1.5)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tr.diversifyUnknown(traversals)\n\treturn nil\n}"
}
},
{
"id": 18,
"parent": 1,
"score": 0.1587,
"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\"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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tswitch {\n\tcase amt <= 25_000_000:\n\t\treturn 1\n\tcase amt <= 150_000_000:\n\t\treturn 3\n\tcase amt <= 500_000_000:\n\t\treturn 4\n\tcase amt <= 1_500_000_000:\n\t\treturn 6\n\tcase amt <= 3_000_000_000:\n\t\treturn 8\n\tdefault:\n\t\treturn 12\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.06 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tswitch {\n\tcase z > 30:\n\t\treturn 0.005\n\tcase z < -30:\n\t\treturn 0.995\n\tdefault:\n\t\treturn 1 / (1 + math.Exp(z))\n\t}\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\testimated := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.15 * float64(belief.conf)\n\t\t\tif weight > 0.75 {\n\t\t\t\tweight = 0.75\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*estimated\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.008)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tposition := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tif position > 0 {\n\t\t\t\t\tbounded := 0.995*(1-position) +\n\t\t\t\t\t\t0.008*position\n\t\t\t\t\tp = 0.30*p + 0.70*bounded\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, ok := r.localFails[e.key]; ok &&\n\t\trequired >= local.upper {\n\n\t\tif local.count >= 2 {\n\t\t\tp = math.Min(p, 0.004)\n\t\t} else {\n\t\t\tp *= 0.07\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\n\t// Reliability and short paths dominate. Fees remain a secondary\n\t// objective and are added separately by the backward search.\n\tcost := -math.Log(p) + 0.07 + r.edgePenalty[e.key]\n\n\t// Held or previously settled shards should normally use separate\n\t// corridors. This is especially important for atomic MPP, where a\n\t// successful sibling still occupies real liquidity.\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tratio := float64(reserved) / float64(e.capacity)\n\t\tcost += 1.75 + 1.50*ratio\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tscore := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscore[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tqueue := &candidateDijkstraQueue{}\n\theap.Push(queue, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor queue.Len() > 0 {\n\t\titem := heap.Pop(queue).(*candidateDijkstraItem)\n\n\t\tbest, ok := score[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif arriving[item.node] != item.amt {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOverEdge := item.amt\n\t\t\tif !edge.policyAllows(amtOverEdge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOverEdge + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOverEdge\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOverEdge)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenominator := float64(amt)\n\t\t\t\tif denominator < 1 {\n\t\t\t\t\tdenominator = 1\n\t\t\t\t}\n\n\t\t\t\t// Fees matter, but should not purchase a long,\n\t\t\t\t// low-reliability detour.\n\t\t\t\tfeeCost = 5 * float64(fee) / denominator\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(edge, amtOverEdge) + feeCost\n\n\t\t\toldScore, exists := score[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(queue, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := score[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\tpathProbability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tpathProbability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, pathProbability, 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\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc candidateScaledAmount(amt lnwire.MilliSatoshi,\n\tnumerator int64) lnwire.MilliSatoshi {\n\n\tif amt <= 0 || numerator <= 0 {\n\t\treturn 0\n\t}\n\treturn amt * lnwire.MilliSatoshi(numerator) / 100\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamt, limit lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amt <= 0 {\n\t\treturn amounts\n\t}\n\tif amt > limit {\n\t\tamt = limit\n\t}\n\tfor _, existing := range amounts {\n\t\tif existing == amt {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amt)\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts\n\n\t// Spare part slots are opened after failures, but not so quickly\n\t// that a sequence of tiny probes consumes the entire MPP budget.\n\ttarget += r.failures / 2\n\tif r.unknownFails >= 3 {\n\t\ttarget++\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\n\treturn target\n}\n\nfunc (r *candidateRouter) routeUtility(rt *route.Route,\n\tpathProbability float64, shard,\n\treference lnwire.MilliSatoshi) float64 {\n\n\thops := len(rt.Hops)\n\tif hops == 0 || shard <= 0 {\n\t\treturn math.Inf(-1)\n\t}\n\n\tif pathProbability < 1e-300 {\n\t\tpathProbability = 1e-300\n\t}\n\n\t// Compare candidate shard sizes by geometric path reliability. This\n\t// prevents long paths from looking artificially attractive while\n\t// still rewarding a shard that commits meaningful value.\n\taverageLogProbability := math.Log(pathProbability) / float64(hops)\n\n\tref := float64(reference)\n\tif ref < 1 {\n\t\tref = 1\n\t}\n\tsizeUtility := math.Log(float64(shard) / ref)\n\n\tdelivered := candidateDeliveredAmount(rt)\n\tfeePenalty := 0.0\n\tif delivered > 0 && rt.TotalAmount > delivered {\n\t\tfeePenalty = 2 * float64(\n\t\t\trt.TotalAmount-delivered,\n\t\t) / float64(delivered)\n\t}\n\n\treturn 0.85*averageLogProbability +\n\t\t1.10*sizeUtility -\n\t\t0.025*float64(hops) -\n\t\tfeePenalty\n}\n\nfunc (r *candidateRouter) selectShardRoute(\n\tamt lnwire.MilliSatoshi, inFlightHtlcs,\n\tpartsLeft uint32) (*route.Route, error) {\n\n\tif partsLeft <= 1 {\n\t\trt, _, err := r.findRoute(amt)\n\t\treturn rt, err\n\t}\n\n\ttargetParts := r.targetPartCount()\n\tdesiredSlots := uint32(1)\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\n\treference := candidateCeilDiv(amt, desiredSlots)\n\tamounts := make([]lnwire.MilliSatoshi, 0, 8)\n\n\tif r.retryCap > 0 {\n\t\tcapAmount := r.retryCap\n\t\tif capAmount > amt {\n\t\t\tcapAmount = amt\n\t\t}\n\n\t\t// A liquidity failure changes the amount, not just the path.\n\t\t// Several sizes are compared because the bimodal prior often\n\t\t// makes either a much smaller shard or a new full corridor best.\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, capAmount, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, candidateScaledAmount(capAmount, 78), amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, candidateScaledAmount(capAmount, 58), amt,\n\t\t)\n\t} else {\n\t\t// Jointly choose the shard size and its route. Larger candidates\n\t\t// fill high-mode corridors efficiently; smaller candidates win\n\t\t// when they cross into the near-certain low-amount prior.\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, candidateScaledAmount(reference, 145), amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, candidateScaledAmount(reference, 120), amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, reference, amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, candidateScaledAmount(reference, 82), amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, candidateScaledAmount(reference, 62), amt,\n\t\t)\n\t}\n\n\tvar bestRoute *route.Route\n\tbestUtility := math.Inf(-1)\n\tvar lastErr error\n\n\tfor _, shard := range amounts {\n\t\trt, probability, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tutility := r.routeUtility(\n\t\t\trt, probability, shard, reference,\n\t\t)\n\t\tif utility > bestUtility {\n\t\t\tbestUtility = utility\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute != nil {\n\t\treturn bestRoute, nil\n\t}\n\n\t// Structural constraints can make all planned sizes impossible.\n\t// Search downward without imposing the old amount/parts floor:\n\t// failed probes do not consume a part, and unequal shards are legal.\n\tshard := reference\n\tif r.retryCap > 0 && r.retryCap < shard {\n\t\tshard = r.retryCap\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tfor shard > 1 {\n\t\trt, _, err := r.findRoute(shard)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\t\tlastErr = err\n\n\t\tnext := shard * 2 / 3\n\t\tif next >= shard {\n\t\t\tnext = shard - 1\n\t\t}\n\t\tshard = next\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\treturn nil, lastErr\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\t// A one-part payment should not be converted into a probe ladder.\n\tif maxParts == 1 || r.plannedParts == 1 {\n\t\trt, _, err := r.findRoute(amt)\n\t\treturn rt, err\n\t}\n\n\treturn r.selectShardRoute(amt, inFlightHtlcs, partsLeft)\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\t\tif belief == nil {\n\t\t\tbelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t\t}\n\n\t\tif required > belief.lowerOK {\n\t\t\tbelief.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > belief.estimate {\n\t\t\tbelief.estimate = highEstimate\n\t\t}\n\t\tif belief.conf < 8 {\n\t\t\tbelief.conf++\n\t\t}\n\n\t\t// Success is truthful and therefore overrides conflicting,\n\t\t// potentially shifted failure attribution.\n\t\tif belief.upperFail > 0 &&\n\t\t\trequired >= belief.upperFail {\n\n\t\t\tbelief.upperFail = 0\n\t\t}\n\t\tif belief.suspectAmt > 0 &&\n\t\t\trequired >= belief.suspectAmt {\n\n\t\t\tbelief.suspectAmt = 0\n\t\t\tbelief.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief := candidateBeliefStore.beliefs[key]; belief != nil {\n\t\t\tif belief.lowerOK > used {\n\t\t\t\tbelief.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tbelief.lowerOK = 0\n\t\t\t}\n\n\t\t\tif belief.estimate > used {\n\t\t\t\tbelief.estimate -= used\n\t\t\t} else {\n\t\t\t\tbelief.estimate = 0\n\t\t\t}\n\n\t\t\tif belief.upperFail > used {\n\t\t\t\tbelief.upperFail -= used\n\t\t\t} else {\n\t\t\t\tbelief.upperFail = 0\n\t\t\t}\n\n\t\t\tif belief.suspectAmt > used {\n\t\t\t\tbelief.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tbelief.suspectAmt = 0\n\t\t\t\tbelief.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureEdge(rt *route.Route,\n\tsource route.Vertex) (candidateTraversal, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\tindex := -1\n\tif source == rt.SourcePubKey {\n\t\tindex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tindex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif index < 0 || index >= len(traversals) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\treturn traversals[index], true\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversal candidateTraversal) uint8 {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tif local.count < 255 {\n\t\tlocal.count++\n\t}\n\tr.localFails[traversal.key] = local\n\n\tr.edgePenalty[traversal.key] += 1.8\n\n\t// Exact source balances are more trustworthy than remote blame.\n\tif traversal.key.from == r.source {\n\t\treturn local.count\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\tif belief == nil {\n\t\tbelief = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t}\n\n\tif belief.suspectVotes == 0 {\n\t\tbelief.suspectAmt = required\n\t\tbelief.suspectVotes = 1\n\t\treturn local.count\n\t}\n\n\tif belief.suspectAmt == 0 || required < belief.suspectAmt {\n\t\tbelief.suspectAmt = required\n\t}\n\tif belief.suspectVotes < 255 {\n\t\tbelief.suspectVotes++\n\t}\n\n\t// A single plausible attribution only changes this payment's route.\n\t// Persistent bounds require corroboration because blame can shift to\n\t// an adjacent node.\n\tif belief.suspectVotes >= 2 {\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\n\t\tfailedEstimate := belief.suspectAmt * 68 / 100\n\t\tif belief.estimate == 0 ||\n\t\t\tfailedEstimate < belief.estimate {\n\n\t\t\tbelief.estimate = failedEstimate\n\t\t}\n\t\tif belief.conf < 8 {\n\t\t\tbelief.conf++\n\t\t}\n\t}\n\n\treturn local.count\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\n\ttraversals := r.routeTraversals(rt)\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.08\n\t}\n\n\tdelivered := candidateDeliveredAmount(rt)\n\tcode := result.Failure.Code()\n\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tcount := r.recordAttributedLiquidityFailure(\n\t\t\t\ttraversal,\n\t\t\t)\n\n\t\t\tif delivered > 1 {\n\t\t\t\tfactor := int64(64)\n\t\t\t\tif count >= 2 {\n\t\t\t\t\tfactor = 54\n\t\t\t\t}\n\t\t\t\tr.retryCap = candidateScaledAmount(\n\t\t\t\t\tdelivered, factor,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// Off-route sources explicitly carry no channel information. Only\n\t// ephemeral route-diversification penalties are written.\n\tr.unknownFails++\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.28\n\t}\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tr.edgePenalty[traversal.key] += 2.4\n\t\t}\n\t}\n\n\tif r.unknownFails%2 == 0 && delivered > 1 {\n\t\tr.retryCap = candidateScaledAmount(delivered, 70)\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 19,
"parent": 0,
"score": 0.5413,
"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\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskCostMsat = 100000.0\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto 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) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\n// Beliefs intentionally outlive a router instance. The simulator creates a\n// new router for each payment, while channel liquidity persists across a\n// scenario batch.\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\n\t// upperFail is installed only after repeated attribution to the same\n\t// directed edge. This avoids turning one shifted failure into a hard\n\t// and persistent false bound.\n\tupperFail lnwire.MilliSatoshi\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectCount uint8\n}\n\nvar candidateBeliefs = struct {\n\tsync.RWMutex\n\tm map[candidateEdgeKey]candidateBelief\n}{\n\tm: make(map[candidateEdgeKey]candidateBelief),\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\t// used is liquidity already consumed or held by successful sibling\n\t// shards. Evaluating the aggregate amount prevents atomic MPP shards\n\t// from repeatedly leaning on the same corridor.\n\tused map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// Per-payment evidence is deliberately softer than persistent belief.\n\t// Any failure diversifies the next route, while an attributed liquidity\n\t// failure discourages only amounts at or above the observed ceiling.\n\tpenalty map[candidateEdgeKey]float64\n\tfailCeil map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\tsuccessfulParts uint32\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: make(map[uint64]lnwire.MilliSatoshi),\n\t\tused: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tpenalty: make(map[candidateEdgeKey]float64),\n\t\tfailCeil: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t}\n\tfor chanID, balance := range localBalances {\n\t\tr.localBalances[chanID] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// bimodalPrior models a channel as usually being almost full or almost\n// empty in a particular direction. Tiny transfers can succeed in either\n// mode, while larger transfers increasingly require the high-liquidity\n// mode.\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\thighMode := 1 / (1 + math.Exp((x-0.91)/0.035))\n\n\treturn clampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc edgeBeliefProbability(key candidateEdgeKey,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\n\n\tprior := bimodalPrior(amt, capacity)\n\n\tcandidateBeliefs.RLock()\n\tbelief, ok := candidateBeliefs.m[key]\n\tcandidateBeliefs.RUnlock()\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK > 0 && amt <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\treturn 0.006\n\t}\n\n\tp := prior\n\tif belief.lowerOK > 0 {\n\t\texcess := amt - belief.lowerOK\n\t\tif excess < 0 {\n\t\t\texcess = 0\n\t\t}\n\t\tscale := math.Max(float64(capacity)*0.25, 1)\n\t\tsuccessTail := 0.93 * math.Exp(-float64(excess)/scale)\n\t\tif successTail > p {\n\t\t\tp = successTail\n\t\t}\n\t}\n\n\tif belief.lowerOK > 0 && belief.upperFail > belief.lowerOK {\n\t\tspan := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(amt-belief.lowerOK) / span\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\tbounded := 0.985 - 0.965*pos\n\t\tp = 0.65*bounded + 0.35*prior\n\t}\n\n\tif belief.suspectCount == 1 &&\n\t\tbelief.suspectAmt > 0 &&\n\t\tamt >= belief.suspectAmt {\n\n\t\tp *= 0.55\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc recordCandidateSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tcandidateBeliefs.Lock()\n\tdefer candidateBeliefs.Unlock()\n\n\tbelief := candidateBeliefs.m[key]\n\tif amt > belief.lowerOK {\n\t\tbelief.lowerOK = amt\n\t}\n\n\tif belief.upperFail > 0 && amt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 && amt >= belief.suspectAmt {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectCount = 0\n\t}\n\n\tcandidateBeliefs.m[key] = belief\n}\n\nfunc recordCandidateFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tcandidateBeliefs.Lock()\n\tdefer candidateBeliefs.Unlock()\n\n\tbelief := candidateBeliefs.m[key]\n\n\tif belief.suspectCount == 0 {\n\t\tbelief.suspectAmt = amt\n\t\tbelief.suspectCount = 1\n\t\tcandidateBeliefs.m[key] = belief\n\t\treturn\n\t}\n\n\tif amt < belief.suspectAmt || belief.suspectAmt == 0 {\n\t\tbelief.suspectAmt = amt\n\t}\n\tif belief.suspectCount < 255 {\n\t\tbelief.suspectCount++\n\t}\n\n\tif belief.suspectCount >= 2 {\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\t}\n\n\tcandidateBeliefs.m[key] = belief\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\tquality float64\n\tindex int\n}\n\ntype candidateQueue []*candidateQueueItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\n\nfunc (q *candidateQueue) Push(x any) {\n\titem := x.(*candidateQueueItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\t*q = old[:last]\n\treturn item\n}\n\ntype candidateRoutePlan struct {\n\troute *route.Route\n\tquality float64\n\tfee lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) (float64, bool) {\n\n\taggregate := amt + r.used[edge.key]\n\tif aggregate > edge.capacity {\n\t\treturn 0, false\n\t}\n\n\tif edge.key.from == r.source {\n\t\tbalance := r.localBalances[edge.key.chanID]\n\t\tif aggregate > balance {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn 0.995, true\n\t}\n\n\treturn edgeBeliefProbability(\n\t\tedge.key, aggregate, edge.capacity,\n\t), true\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidateRoutePlan, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tbest := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbest[r.spec.Target] = 0\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t})\n\n\tvar sourceQuality float64\n\tfound := false\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateQueueItem)\n\t\tcurrentBest, ok := best[item.node]\n\t\tif !ok || item.score > currentBest+0.000001 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\tsourceQuality = item.quality\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amount\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability, usable := r.edgeProbability(edge, amtOver)\n\t\t\tif !usable {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\taggregate := amtOver + r.used[edge.key]\n\t\t\tlocalPenalty := r.penalty[edge.key]\n\t\t\tif ceiling := r.failCeil[edge.key]; ceiling > 0 &&\n\t\t\t\taggregate >= ceiling {\n\n\t\t\t\tlocalPenalty += 2.0\n\t\t\t}\n\n\t\t\t// Reusing a corridor is allowed when it plausibly has room,\n\t\t\t// but increasingly expensive. This naturally produces\n\t\t\t// disjoint atomic-MPP route sets when alternatives exist.\n\t\t\tif edge.capacity > 0 && r.used[edge.key] > 0 {\n\t\t\t\tlocalPenalty += 1.2 *\n\t\t\t\t\tfloat64(r.used[edge.key]) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t}\n\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tsending := amtOver\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tlogProbability := math.Log(\n\t\t\t\tclampProbability(probability),\n\t\t\t)\n\t\t\tedgeRisk := -logProbability + localPenalty\n\t\t\tscore := item.score + float64(fee) +\n\t\t\t\triskCostMsat*edgeRisk\n\t\t\tquality := item.quality + logProbability -\n\t\t\t\tlocalPenalty\n\n\t\t\told, exists := best[edge.key.from]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[edge.key.from] = score\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &candidateQueueItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tamount: sending,\n\t\t\t\tscore: score,\n\t\t\t\tquality: quality,\n\t\t\t})\n\t\t}\n\t}\n\n\tif !found {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &candidateRoutePlan{\n\t\troute: rt,\n\t\tquality: sourceQuality,\n\t\tfee: rt.TotalAmount - amt,\n\t}, 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\tfor node := r.source; node != r.spec.Target; {\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\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\tforwardingEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tforwardingEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = 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: amtToForward,\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 appendCandidateAmount(amounts []lnwire.MilliSatoshi,\n\tamt, limit, minimum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amt < minimum {\n\t\tamt = minimum\n\t}\n\tif amt > limit {\n\t\tamt = limit\n\t}\n\tif amt <= 0 {\n\t\treturn amounts\n\t}\n\n\tfor _, existing := range amounts {\n\t\tif existing == amt {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amt)\n}\n\n// RequestRoute jointly considers several feasible shard sizes. It chooses\n// the amount and route together instead of discovering every split through\n// a ladder of failed full-size probes.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment remainder\")\n\t}\n\n\tpartsUsed := r.successfulParts\n\tif inFlightHtlcs > partsUsed {\n\t\tpartsUsed = inFlightHtlcs\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif partsUsed >= maxParts {\n\t\treturn nil, errors.New(\"maximum number of parts reached\")\n\t}\n\n\tpartsLeft := maxParts - partsUsed\n\tif partsLeft == 1 {\n\t\tplan, err := r.findRoute(amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn plan.route, nil\n\t}\n\n\tminimum := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\n\tamounts := make([]lnwire.MilliSatoshi, 0, 12)\n\tamounts = appendCandidateAmount(amounts, amt, amt, minimum)\n\n\tdivisorLimit := partsLeft\n\tif divisorLimit > 8 {\n\t\tdivisorLimit = 8\n\t}\n\tfor divisor := uint32(2); divisor <= divisorLimit; divisor++ {\n\t\tshard := (amt + lnwire.MilliSatoshi(divisor) - 1) /\n\t\t\tlnwire.MilliSatoshi(divisor)\n\t\tamounts = appendCandidateAmount(\n\t\t\tamounts, shard, amt, minimum,\n\t\t)\n\t}\n\n\tamounts = appendCandidateAmount(\n\t\tamounts, minimum, amt, minimum,\n\t)\n\tamounts = appendCandidateAmount(\n\t\tamounts, minimum+minimum/2, amt, minimum,\n\t)\n\tamounts = appendCandidateAmount(\n\t\tamounts, minimum*2, amt, minimum,\n\t)\n\n\tvar bestPlan *candidateRoutePlan\n\tbestUtility := math.Inf(-1)\n\n\tfor _, shard := range amounts {\n\t\tplan, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// The amount term favors committing useful liquidity quickly.\n\t\t// Total path probability favors shards that avoid the bimodal\n\t\t// cliff. Fees remain a secondary tie breaker.\n\t\tfeeRatio := float64(plan.fee) /\n\t\t\tmath.Max(float64(shard), 1)\n\t\tutility := math.Log(float64(shard)) +\n\t\t\t0.55*plan.quality - 20*feeRatio\n\n\t\tif bestPlan == nil || utility > bestUtility ||\n\t\t\t(utility == bestUtility &&\n\t\t\t\tshard > bestPlan.route.Hops[\n\t\t\t\t\tlen(bestPlan.route.Hops)-1].AmtToForward) {\n\n\t\t\tbestPlan = plan\n\t\t\tbestUtility = utility\n\t\t}\n\t}\n\n\tif bestPlan == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn bestPlan.route, nil\n}\n\ntype candidateRouteEdge struct {\n\tkey candidateEdgeKey\n\tamt lnwire.MilliSatoshi\n}\n\nfunc routeEdges(rt *route.Route) []candidateRouteEdge {\n\tedges := make([]candidateRouteEdge, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tamtOver := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamtOver = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\tedges = append(edges, candidateRouteEdge{\n\t\t\tkey: candidateEdgeKey{\n\t\t\t\tchanID: hop.ChannelID,\n\t\t\t\tfrom: from,\n\t\t\t\tto: hop.PubKeyBytes,\n\t\t\t},\n\t\t\tamt: amtOver,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn edges\n}\n\nfunc failureEdgeIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tedges := routeEdges(rt)\n\n\tif result.Failure == nil {\n\t\tfor _, edge := range edges {\n\t\t\taggregate := edge.amt + r.used[edge.key]\n\t\t\trecordCandidateSuccess(edge.key, aggregate)\n\t\t\tr.used[edge.key] = aggregate\n\t\t}\n\t\tr.successfulParts++\n\t\treturn nil\n\t}\n\n\t// A route-wide, payment-local penalty extracts the only trustworthy\n\t// fact from an unreadable failure: this exact corridor just failed.\n\t// It does not contaminate persistent liquidity beliefs.\n\tfor _, edge := range edges {\n\t\tr.penalty[edge.key] += 0.16\n\t}\n\n\tfailIdx := failureEdgeIndex(rt, result.FailureSource)\n\tif failIdx < 0 || failIdx >= len(edges) {\n\t\treturn nil\n\t}\n\n\t// Policy errors should cause route diversification, but they are not\n\t// evidence about hidden liquidity.\n\tif result.Failure.Code() !=\n\t\tlnwire.CodeTemporaryChannelFailure {\n\n\t\tr.penalty[edges[failIdx].key] += 0.8\n\t\treturn nil\n\t}\n\n\tfailedEdge := edges[failIdx]\n\taggregate := failedEdge.amt + r.used[failedEdge.key]\n\n\toldCeiling := r.failCeil[failedEdge.key]\n\tif oldCeiling == 0 || aggregate < oldCeiling {\n\t\tr.failCeil[failedEdge.key] = aggregate\n\t}\n\n\t// Persistent hard evidence requires corroboration. A first report is\n\t// only a soft suspicion because otherwise a shifted source can poison\n\t// future payments.\n\trecordCandidateFailure(failedEdge.key, aggregate)\n\n\treturn nil\n}"
}
},
{
"id": 20,
"parent": 1,
"score": 0.621,
"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(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(\n\tamt lnwire.MilliSatoshi) bool {\n\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectWeight float64\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tweight float64\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tsuccessfulParts uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tswitch {\n\tcase amt <= 20_000_000:\n\t\treturn 1\n\n\tcase amt <= 75_000_000:\n\t\treturn 3\n\n\tcase amt <= 200_000_000:\n\t\treturn 5\n\n\tcase amt <= 400_000_000:\n\t\treturn 8\n\n\tcase amt <= 900_000_000:\n\t\treturn 10\n\n\tcase amt <= 2_000_000_000:\n\t\treturn 12\n\n\tdefault:\n\t\treturn 16\n\t}\n}\n\nfunc candidatePrior(amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\n\tcase p > 0.985:\n\t\treturn 0.985\n\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.065 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tswitch {\n\tcase z > 30:\n\t\treturn 0.005\n\n\tcase z < -30:\n\t\treturn 0.995\n\n\tdefault:\n\t\treturn 1 / (1 + math.Exp(z))\n\t}\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.003:\n\t\treturn 0.003\n\n\tcase p > 0.995:\n\t\treturn 0.995\n\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif stored != nil {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif stored != nil {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\testimateP := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\n\t\t\tweight := 0.13 * float64(belief.conf)\n\t\t\tif weight > 0.78 {\n\t\t\t\tweight = 0.78\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*estimateP\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.012)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK &&\n\t\t\t\trequired > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tposition := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\n\t\t\t\tbounded := 0.995*(1-position) +\n\t\t\t\t\t0.012*position\n\t\t\t\tp = 0.30*p + 0.70*bounded\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, ok := r.localFails[e.key]; ok &&\n\t\tlocal.upper > 0 && required >= local.upper {\n\n\t\tp *= math.Exp(-1.85 * local.weight)\n\t}\n\n\treturn candidateClampProbability(p)\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.018 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tratio := float64(reserved) / float64(e.capacity)\n\n\t\t// A successful shard is evidence that the corridor is live, so\n\t\t// reuse remains possible. The increasing marginal charge keeps\n\t\t// atomic siblings from all leaning on a narrow channel.\n\t\tcost += 0.08 + 0.65*ratio*ratio\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tscore := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscore[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\n\t\tbestScore, ok := score[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif arriving[item.node] != item.amt {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOverEdge := item.amt\n\t\t\tif !edge.policyAllows(amtOverEdge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOverEdge + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOverEdge\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOverEdge)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenominator := float64(amt)\n\t\t\t\tif denominator < 1 {\n\t\t\t\t\tdenominator = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 42 * float64(fee) / denominator\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(edge, amtOverEdge) + feeCost\n\n\t\t\toldScore, exists := score[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := score[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\tprobability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tprobability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, probability, 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\tfor node := r.source; node != r.spec.Target; {\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\tforwardingEdge := path[i+1]\n\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tforwardingEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts\n\n\t// Failed large shards make additional slots valuable. Unknown\n\t// failures diversify routes but do not immediately force a probing\n\t// ladder because they carry no amount bound.\n\ttarget += r.failures / 3\n\tif r.unknownFails >= 4 {\n\t\ttarget++\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\n\treturn target\n}\n\nfunc (r *candidateRouter) tryExpandedShard(\n\tbase lnwire.MilliSatoshi, amt lnwire.MilliSatoshi,\n\tbaseRoute *route.Route, baseProbability float64) *route.Route {\n\n\tif r.successfulParts == 0 || base >= amt {\n\t\treturn baseRoute\n\t}\n\n\tbestRoute := baseRoute\n\tbestAmount := base\n\tbestProbability := baseProbability\n\n\tcandidates := []lnwire.MilliSatoshi{\n\t\tbase * 3 / 2,\n\t\tbase * 2,\n\t\tbase * 3,\n\t\tamt,\n\t}\n\n\tfor _, candidate := range candidates {\n\t\tif candidate <= bestAmount || candidate > amt {\n\t\t\tcontinue\n\t\t}\n\n\t\trt, probability, err := r.findRoute(candidate)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Scaling is reserved for corridors supported by truthful recent\n\t\t// success. This creates unequal MPP allocations without probing\n\t\t// an unproven large shard.\n\t\trequiredProbability := 0.74\n\t\tif len(rt.Hops) >= 12 {\n\t\t\trequiredProbability = 0.68\n\t\t}\n\t\tif probability < requiredProbability {\n\t\t\tcontinue\n\t\t}\n\t\tif probability+0.08 < bestProbability {\n\t\t\tcontinue\n\t\t}\n\n\t\tbestRoute = rt\n\t\tbestAmount = candidate\n\t\tbestProbability = probability\n\t}\n\n\treturn bestRoute\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminimumShard := candidateCeilDiv(amt, partsLeft)\n\n\tvar wholeRoute *route.Route\n\tif rt, probability, err := r.findRoute(amt); err == nil {\n\t\twholeRoute = rt\n\n\t\tif r.plannedParts == 1 || probability >= 0.82 {\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\ttargetParts := r.targetPartCount()\n\tdesiredSlots := uint32(1)\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\n\tshard := candidateCeilDiv(amt, desiredSlots)\n\tif r.retryCap > 0 && r.retryCap < shard {\n\t\tshard = r.retryCap\n\t}\n\tif shard < minimumShard {\n\t\tshard = minimumShard\n\t}\n\tif shard > amt {\n\t\tshard = amt\n\t}\n\n\tfor {\n\t\trt, probability, err := r.findRoute(shard)\n\t\tif err == nil {\n\t\t\treturn r.tryExpandedShard(\n\t\t\t\tshard, amt, rt, probability,\n\t\t\t), nil\n\t\t}\n\n\t\tif shard <= minimumShard {\n\t\t\tif wholeRoute != nil && shard != amt {\n\t\t\t\treturn wholeRoute, nil\n\t\t\t}\n\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnextShard := shard * 2 / 3\n\t\tif nextShard < minimumShard {\n\t\t\tnextShard = minimumShard\n\t\t}\n\t\tif nextShard == shard {\n\t\t\tnextShard--\n\t\t}\n\n\t\tshard = nextShard\n\t}\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make(\n\t\t[]candidateTraversal, 0, len(rt.Hops),\n\t)\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amount,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(\n\trt *route.Route) lnwire.MilliSatoshi {\n\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\t\tif belief == nil {\n\t\t\tbelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t\t}\n\n\t\tif required > belief.lowerOK {\n\t\t\tbelief.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > belief.estimate {\n\t\t\tbelief.estimate = highEstimate\n\t\t}\n\t\tif belief.conf < 8 {\n\t\t\tbelief.conf++\n\t\t}\n\n\t\tif belief.upperFail > 0 &&\n\t\t\trequired >= belief.upperFail {\n\n\t\t\tbelief.upperFail = 0\n\t\t}\n\t\tif belief.suspectAmt > 0 &&\n\t\t\trequired >= belief.suspectAmt {\n\n\t\t\tbelief.suspectAmt = 0\n\t\t\tbelief.suspectWeight = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\n\t\t// A successful edge should rapidly recover from route-level\n\t\t// diversification penalties.\n\t\tr.edgePenalty[traversal.key] *= 0.30\n\t}\n\n\tr.successfulParts++\n\tr.delivered += candidateDeliveredAmount(rt)\n\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc candidateSubtractFloor(value,\n\tdelta lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value > delta {\n\t\treturn value - delta\n\t}\n\n\treturn 0\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief := candidateBeliefStore.beliefs[key]; belief != nil {\n\t\t\tbelief.lowerOK = candidateSubtractFloor(\n\t\t\t\tbelief.lowerOK, used,\n\t\t\t)\n\t\t\tbelief.estimate = candidateSubtractFloor(\n\t\t\t\tbelief.estimate, used,\n\t\t\t)\n\t\t\tbelief.upperFail = candidateSubtractFloor(\n\t\t\t\tbelief.upperFail, used,\n\t\t\t)\n\t\t\tbelief.suspectAmt = candidateSubtractFloor(\n\t\t\t\tbelief.suspectAmt, used,\n\t\t\t)\n\t\t\tif belief.suspectAmt == 0 {\n\t\t\t\tbelief.suspectWeight = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureTraversalIndex(\n\trt *route.Route, source route.Vertex) (int, bool) {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0, len(rt.Hops) > 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\tindex := i + 1\n\t\tif index >= len(rt.Hops) {\n\t\t\treturn 0, false\n\t\t}\n\n\t\treturn index, true\n\t}\n\n\treturn 0, false\n}\n\nfunc (r *candidateRouter) addLocalFailure(\n\ttraversal candidateTraversal, weight float64) {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tlocal.weight += weight\n\tif local.weight > 4 {\n\t\tlocal.weight = 4\n\t}\n\tr.localFails[traversal.key] = local\n}\n\nfunc (r *candidateRouter) recordPersistentFailure(\n\ttraversal candidateTraversal, weight float64) {\n\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\tif belief == nil {\n\t\tbelief = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t}\n\n\tif belief.suspectAmt == 0 || required < belief.suspectAmt {\n\t\tbelief.suspectAmt = required\n\t}\n\tbelief.suspectWeight += weight\n\n\t// Three direct reports are needed before noisy attribution becomes\n\t// a persistent bound. Until then it affects only this payment.\n\tif belief.suspectWeight < 2.05 {\n\t\treturn\n\t}\n\n\tif belief.upperFail == 0 ||\n\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\tbelief.upperFail = belief.suspectAmt\n\t}\n\n\tfailedEstimate := belief.suspectAmt * 68 / 100\n\tif belief.estimate == 0 ||\n\t\tfailedEstimate < belief.estimate {\n\n\t\tbelief.estimate = failedEstimate\n\t}\n\tif belief.conf < 8 {\n\t\tbelief.conf++\n\t}\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversals []candidateTraversal, claimed int) {\n\n\tfor offset := -1; offset <= 1; offset++ {\n\t\tindex := claimed + offset\n\t\tif index < 0 || index >= len(traversals) {\n\t\t\tcontinue\n\t\t}\n\n\t\tweight := 0.14\n\t\tpenalty := 0.20\n\t\tif offset == 0 {\n\t\t\tweight = 0.72\n\t\t\tpenalty = 1.05\n\t\t}\n\n\t\ttraversal := traversals[index]\n\t\tr.addLocalFailure(traversal, weight)\n\t\tr.edgePenalty[traversal.key] += penalty\n\n\t\t// Only the reported edge contributes to persistent evidence.\n\t\t// Adjacent weights are useful inside this payment for shifted\n\t\t// blame, but are too ambiguous to retain across payments.\n\t\tif offset == 0 {\n\t\t\tr.recordPersistentFailure(traversal, weight)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) recordPolicyFailure(\n\ttraversals []candidateTraversal, claimed int) {\n\n\tfor offset := -1; offset <= 1; offset++ {\n\t\tindex := claimed + offset\n\t\tif index < 0 || index >= len(traversals) {\n\t\t\tcontinue\n\t\t}\n\n\t\tpenalty := 0.35\n\t\tif offset == 0 {\n\t\t\tpenalty = 1.80\n\t\t}\n\t\tr.edgePenalty[traversals[index].key] += penalty\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\tr.unknownFails++\n\t\treturn nil\n\t}\n\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.025\n\t}\n\n\tcode := result.Failure.Code()\n\tclaimed, attributed := r.failureTraversalIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tif attributed {\n\t\t\tr.recordAttributedLiquidityFailure(\n\t\t\t\ttraversals, claimed,\n\t\t\t)\n\n\t\t\tdelivered := candidateDeliveredAmount(rt)\n\t\t\tif delivered > 1 {\n\t\t\t\tnext := delivered * 58 / 100\n\t\t\t\tif r.retryCap == 0 || next < r.retryCap {\n\t\t\t\t\tr.retryCap = next\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tif attributed {\n\t\t\tr.recordPolicyFailure(traversals, claimed)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// Unreadable failures write no liquidity observation. A modest\n\t// payment-local route penalty encourages a genuinely different\n\t// attempt without poisoning shared beliefs or forcing tiny probes.\n\tr.unknownFails++\n\troutePenalty := 0.16\n\tif len(traversals) > 12 {\n\t\troutePenalty = 0.11\n\t}\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += routePenalty\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 21,
"parent": 1,
"score": 0.1765,
"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\"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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\trouteFailure map[uint64]uint8\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tattempts uint32\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\trouteFailure: make(map[uint64]uint8),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tmaxParts := r.maxParts()\n\tif r.plannedParts > maxParts {\n\t\tr.plannedParts = maxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) maxParts() uint32 {\n\tif r.spec.MaxParts == 0 {\n\t\treturn 1\n\t}\n\treturn r.spec.MaxParts\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tmaxParts := r.maxParts()\n\n\tswitch {\n\tcase amt <= 25_000_000:\n\t\treturn 1\n\n\tcase amt <= 100_000_000:\n\t\tif maxParts < 4 {\n\t\t\treturn maxParts\n\t\t}\n\t\treturn 4\n\n\tdefault:\n\t\t// Large payments benefit from committing the available part\n\t\t// budget immediately. Limit extreme part counts because fixed\n\t\t// per-hop fees eventually dominate tiny shards.\n\t\tif maxParts > 20 {\n\t\t\treturn 20\n\t\t}\n\t\treturn maxParts\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.07 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tif z > 30 {\n\t\treturn 0.005\n\t}\n\tif z < -30 {\n\t\treturn 0.995\n\t}\n\n\treturn 1 / (1 + math.Exp(z))\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.14 * float64(belief.conf)\n\t\t\tif weight > 0.70 {\n\t\t\t\tweight = 0.70\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.008)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tposition := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tif position > 0 {\n\t\t\t\t\tbounded := 0.995*(1-position) +\n\t\t\t\t\t\t0.008*position\n\t\t\t\t\tp = 0.35*p + 0.65*bounded\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, ok := r.localFails[e.key]; ok &&\n\t\trequired >= local.upper {\n\n\t\tif local.count >= 2 {\n\t\t\tp = math.Min(p, 0.004)\n\t\t} else {\n\t\t\tp *= 0.10\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.01 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tshare := float64(reserved) / float64(e.capacity)\n\t\tcost += 0.08 + 0.70*share\n\t}\n\n\treturn cost\n}\n\ntype candidateLabel struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n\tedge *candidateEdge\n\tnext *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 candidateDominates(aScore float64, aAmt lnwire.MilliSatoshi,\n\tbScore float64, bAmt lnwire.MilliSatoshi) bool {\n\n\treturn aScore <= bScore+1e-12 && aAmt <= bAmt\n}\n\nfunc candidateInsertLabel(labels map[route.Vertex][]*candidateLabel,\n\tlabel *candidateLabel, paymentAmt lnwire.MilliSatoshi) bool {\n\n\tcurrent := labels[label.node]\n\tfor _, old := range current {\n\t\tif old.active && candidateDominates(\n\t\t\told.score, old.amt, label.score, label.amt,\n\t\t) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor _, old := range current {\n\t\tif old.active && candidateDominates(\n\t\t\tlabel.score, label.amt, old.score, old.amt,\n\t\t) {\n\t\t\told.active = false\n\t\t}\n\t}\n\n\tlabel.active = true\n\tcurrent = append(current, label)\n\tlabels[label.node] = current\n\n\tactiveCount := 0\n\tfor _, old := range current {\n\t\tif old.active {\n\t\t\tactiveCount++\n\t\t}\n\t}\n\tif activeCount <= 12 {\n\t\treturn true\n\t}\n\n\tdenom := float64(paymentAmt)\n\tif denom < 1 {\n\t\tdenom = 1\n\t}\n\n\tvar worst *candidateLabel\n\tworstValue := math.Inf(-1)\n\tfor _, old := range current {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\n\t\tvalue := old.score + 0.45*float64(old.amt)/denom\n\t\tif value > worstValue {\n\t\t\tworstValue = value\n\t\t\tworst = old\n\t\t}\n\t}\n\n\tif worst != nil {\n\t\tworst.active = false\n\t}\n\treturn label.active\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\ttarget := &candidateLabel{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamt: amt,\n\t\tactive: true,\n\t}\n\tlabels := map[route.Vertex][]*candidateLabel{\n\t\tr.spec.Target: {target},\n\t}\n\n\tpq := &candidateLabelQueue{}\n\theap.Push(pq, target)\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.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 18 * float64(fee) / denom\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 +\n\t\t\t\t\tr.edgeCost(edge, amtOver) + feeCost,\n\t\t\t\tamt: sending,\n\t\t\t\tedge: edge,\n\t\t\t\tnext: item,\n\t\t\t}\n\n\t\t\tif candidateInsertLabel(labels, label, amt) {\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 != nil && label.edge != nil;\n\t\tlabel = label.next {\n\n\t\tpath = append(path, label.edge)\n\t}\n\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, sourceLabel.score, 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\tif path[0].key.from != r.source {\n\t\treturn nil, errors.New(\"path does not start at source\")\n\t}\n\tif path[len(path)-1].key.to != r.spec.Target {\n\t\treturn nil, errors.New(\"path does not end at target\")\n\t}\n\n\tfor i := 1; i < len(path); i++ {\n\t\tif path[i-1].key.to != path[i].key.from {\n\t\t\treturn nil, fmt.Errorf(\"broken path at hop %d\", i)\n\t\t}\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamtToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tamtToForward = amtOver[i+1]\n\t\t\toutgoingExpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: amtToForward,\n\t\t\tOutgoingTimeLock: outgoingExpiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiryOver[0],\n\t\tTotalAmount: amtOver[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc candidateScaled(amt lnwire.MilliSatoshi,\n\tnumerator, denominator int64) lnwire.MilliSatoshi {\n\n\tif denominator <= 0 {\n\t\treturn amt\n\t}\n\n\tresult := amt / lnwire.MilliSatoshi(denominator) *\n\t\tlnwire.MilliSatoshi(numerator)\n\tresult += amt % lnwire.MilliSatoshi(denominator) *\n\t\tlnwire.MilliSatoshi(numerator) /\n\t\tlnwire.MilliSatoshi(denominator)\n\n\tif result <= 0 {\n\t\treturn 1\n\t}\n\treturn result\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamt, maximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amt <= 0 {\n\t\tamt = 1\n\t}\n\tif amt > maximum {\n\t\tamt = maximum\n\t}\n\n\tfor _, existing := range amounts {\n\t\tif existing == amt {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amt)\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts\n\tmaxParts := r.maxParts()\n\n\tif r.failures >= 2 {\n\t\ttarget += 1 + r.failures/4\n\t}\n\tif r.unknownFails >= 2 {\n\t\ttarget++\n\t}\n\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\treturn target\n}\n\nfunc (r *candidateRouter) attemptLimit() uint32 {\n\tlimit := uint32(48) + 5*r.maxParts()\n\tif limit < 72 {\n\t\tlimit = 72\n\t}\n\tif limit > 160 {\n\t\tlimit = 160\n\t}\n\treturn limit\n}\n\nfunc (r *candidateRouter) reserveRoute(rt *route.Route) {\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tr.reserved[traversal.key] += traversal.amt\n\t}\n}\n\nfunc (r *candidateRouter) unreserveRoute(rt *route.Route) {\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tcurrent := r.reserved[traversal.key]\n\t\tif current <= traversal.amt {\n\t\t\tdelete(r.reserved, traversal.key)\n\t\t} else {\n\t\t\tr.reserved[traversal.key] = current - traversal.amt\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 is zero\")\n\t}\n\tif r.attempts >= r.attemptLimit() {\n\t\treturn nil, errors.New(\"routing attempt limit reached\")\n\t}\n\n\tmaxParts := r.maxParts()\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\ttargetParts := r.targetPartCount()\n\n\tvar planSlots uint32\n\tif targetParts > inFlightHtlcs {\n\t\tplanSlots = targetParts - inFlightHtlcs\n\t} else {\n\t\tplanSlots = 1\n\t}\n\tif planSlots > partsLeft {\n\t\tplanSlots = partsLeft\n\t}\n\tif planSlots == 0 {\n\t\tplanSlots = 1\n\t}\n\n\tbase := candidateCeilDiv(amt, planSlots)\n\tamounts := make([]lnwire.MilliSatoshi, 0, 8)\n\n\t// Once a corridor fails, consider a genuinely smaller retry. A\n\t// one-step lookahead below prevents that retry from consuming a part\n\t// slot when the remaining amount cannot plausibly fit later.\n\tif r.failures > 0 && planSlots > 1 {\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, candidateScaled(base, 3, 5), amt,\n\t\t)\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, candidateScaled(base, 4, 5), amt,\n\t\t)\n\t}\n\n\tamounts = candidateAppendAmount(amounts, base, amt)\n\tamounts = candidateAppendAmount(\n\t\tamounts, candidateScaled(base, 5, 4), amt,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, candidateScaled(base, 3, 2), amt,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, candidateScaled(base, 2, 1), amt,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, candidateScaled(base, 3, 1), amt,\n\t)\n\tamounts = candidateAppendAmount(amounts, amt, amt)\n\n\tvar bestRoute *route.Route\n\tbestObjective := math.Inf(1)\n\tvar lastErr error\n\n\tfor _, shard := range amounts {\n\t\trt, routeScore, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprogress := float64(shard) / float64(base)\n\t\tif progress < 0.05 {\n\t\t\tprogress = 0.05\n\t\t}\n\n\t\t// This is expected-progress selection: use a larger shard only\n\t\t// when its route remains reliable enough to justify doing so.\n\t\tobjective := routeScore - 0.90*math.Log(progress)\n\n\t\tremaining := amt - shard\n\t\tif remaining > 0 && planSlots > 1 {\n\t\t\tr.reserveRoute(rt)\n\n\t\t\tfutureSlots := planSlots - 1\n\t\t\tfutureAmt := candidateCeilDiv(\n\t\t\t\tremaining, futureSlots,\n\t\t\t)\n\t\t\t_, futureScore, futureErr := r.findRoute(futureAmt)\n\n\t\t\tr.unreserveRoute(rt)\n\n\t\t\tif futureErr != nil {\n\t\t\t\t// Keep the candidate as a last resort, but strongly\n\t\t\t\t// prefer a shard that leaves a routable continuation.\n\t\t\t\tobjective += 6.0\n\t\t\t} else {\n\t\t\t\tobjective += 0.25 * futureScore\n\t\t\t}\n\t\t} else if remaining > 0 {\n\t\t\tobjective += 8.0\n\t\t}\n\n\t\tif objective < bestObjective {\n\t\t\tbestObjective = objective\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute != nil {\n\t\treturn bestRoute, nil\n\t}\n\tif lastErr != nil {\n\t\treturn nil, lastErr\n\t}\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc candidateRouteFingerprint(rt *route.Route) uint64 {\n\thash := uint64(1469598103934665603)\n\n\tfor i, hop := range rt.Hops {\n\t\thash ^= hop.ChannelID + uint64(i+1)*0x9e3779b97f4a7c15\n\t\thash *= 1099511628211\n\t}\n\n\thash ^= uint64(candidateDeliveredAmount(rt) / 1000)\n\thash *= 1099511628211\n\n\treturn hash\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tb := candidateBeliefStore.beliefs[traversal.key]\n\t\tif b == nil {\n\t\t\tb = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t\t}\n\n\t\tif required > b.lowerOK {\n\t\t\tb.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > b.estimate {\n\t\t\tb.estimate = highEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\n\t\tif b.upperFail > 0 && required >= b.upperFail {\n\t\t\tb.upperFail = 0\n\t\t}\n\t\tif b.suspectAmt > 0 && required >= b.suspectAmt {\n\t\t\tb.suspectAmt = 0\n\t\t\tb.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\n\t\tif r.edgePenalty[traversal.key] > 0.05 {\n\t\t\tr.edgePenalty[traversal.key] *= 0.65\n\t\t}\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif b := candidateBeliefStore.beliefs[key]; b != nil {\n\t\t\tif b.lowerOK > used {\n\t\t\t\tb.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tb.lowerOK = 0\n\t\t\t}\n\n\t\t\tif b.estimate > used {\n\t\t\t\tb.estimate -= used\n\t\t\t} else {\n\t\t\t\tb.estimate = 0\n\t\t\t}\n\n\t\t\tif b.upperFail > used {\n\t\t\t\tb.upperFail -= used\n\t\t\t} else {\n\t\t\t\tb.upperFail = 0\n\t\t\t}\n\n\t\t\tif b.suspectAmt > used {\n\t\t\t\tb.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tb.suspectAmt = 0\n\t\t\t\tb.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureEdge(rt *route.Route,\n\tsource route.Vertex) (candidateTraversal, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\tindex := -1\n\tif source == rt.SourcePubKey {\n\t\tindex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tindex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif index < 0 || index >= len(traversals) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\treturn traversals[index], true\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversal candidateTraversal) {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tif local.count < 255 {\n\t\tlocal.count++\n\t}\n\tr.localFails[traversal.key] = local\n\n\t// Keep the amount-independent penalty small so the same channel can\n\t// immediately compete again at a lower amount.\n\tr.edgePenalty[traversal.key] += 0.18\n\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tb := candidateBeliefStore.beliefs[traversal.key]\n\tif b == nil {\n\t\tb = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t}\n\n\tif b.suspectVotes == 0 {\n\t\tb.suspectAmt = required\n\t\tb.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif b.suspectAmt == 0 || required < b.suspectAmt {\n\t\tb.suspectAmt = required\n\t}\n\tif b.suspectVotes < 255 {\n\t\tb.suspectVotes++\n\t}\n\n\t// A single shifted attribution stays local to this payment. Only\n\t// corroborated reports become persistent, and even then the bound\n\t// remains soft and can be overturned by a truthful success.\n\tif b.suspectVotes >= 2 {\n\t\tif b.upperFail == 0 || b.suspectAmt < b.upperFail {\n\t\t\tb.upperFail = b.suspectAmt\n\t\t}\n\n\t\tfailedEstimate := b.suspectAmt * 70 / 100\n\t\tif b.estimate == 0 || failedEstimate < b.estimate {\n\t\t\tb.estimate = failedEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) penalizeFailedRoute(rt *route.Route,\n\tbase float64) {\n\n\tfingerprint := candidateRouteFingerprint(rt)\n\tcount := r.routeFailure[fingerprint]\n\tif count < 255 {\n\t\tcount++\n\t}\n\tr.routeFailure[fingerprint] = count\n\n\trepeat := float64(count-1) * 0.06\n\tif repeat > 0.60 {\n\t\trepeat = 0.60\n\t}\n\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tr.edgePenalty[traversal.key] += base + repeat\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tr.attempts++\n\n\tif result.Failure == nil {\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\tcode := result.Failure.Code()\n\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\n\t\t\t// Diversify exact repeats without destroying the lower-amount\n\t\t\t// retry path for the specifically blamed channel.\n\t\t\tr.penalizeFailedRoute(rt, 0.035)\n\t\t\tr.recordAttributedLiquidityFailure(traversal)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tr.penalizeFailedRoute(rt, 0.12)\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\n\t\t\tr.edgePenalty[traversal.key] += 1.25\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// Empty codes and off-route sources contain no channel evidence.\n\t// They influence only this router's exploration order.\n\tr.unknownFails++\n\tr.penalizeFailedRoute(rt, 0.20)\n\n\treturn nil\n}"
}
},
{
"id": 22,
"parent": 0,
"score": 0.3321,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\tminShardAmt = lnwire.MilliSatoshi(10_000_000)\n\ttargetShardAmt = lnwire.MilliSatoshi(75_000_000)\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usable(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 liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tfailureVotes uint8\n\tupdated time.Time\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype localFailure struct {\n\tamt lnwire.MilliSatoshi\n\tvotes int\n}\n\ntype planEntry struct {\n\trt *route.Route\n}\n\ntype candidateRouter struct {\n\tview routing.SimNetworkView\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[edgeKey]liquidityBelief\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tlocalFailures map[edgeKey]localFailure\n\tpolicyStrikes map[edgeKey]int\n\n\tfailedRoutes map[uint64]int\n\trouteFailAmt map[uint64]lnwire.MilliSatoshi\n\n\tplan []planEntry\n\tretryHint lnwire.MilliSatoshi\n\tattempts int\n\tplanVersion uint64\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\tview: view,\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tlocalFailures: make(map[edgeKey]localFailure),\n\t\tpolicyStrikes: make(map[edgeKey]int),\n\t\tfailedRoutes: make(map[uint64]int),\n\t\trouteFailAmt: make(map[uint64]lnwire.MilliSatoshi),\n\t\tretryHint: spec.Amount,\n\t\tplanVersion: 1,\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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: edgeKey{\n\t\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\t},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsharedBeliefs.Lock()\n\tfor key := range r.edges {\n\t\tif belief, ok := sharedBeliefs.m[key]; ok {\n\t\t\tr.beliefs[key] = belief\n\t\t}\n\t}\n\tsharedBeliefs.Unlock()\n\n\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\thighMode := 1 / (1 + math.Exp((x-0.94)/0.035))\n\n\treturn clampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) evidenceWeight(b liquidityBelief) float64 {\n\tif b.updated.IsZero() {\n\t\treturn 0\n\t}\n\n\tnow := r.view.Now()\n\tif now.Before(b.updated) {\n\t\treturn 0\n\t}\n\n\tage := now.Sub(b.updated)\n\treturn math.Exp(-float64(age) / float64(180*time.Minute))\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tavailable := r.localBalances[edge.chanID]\n\t\tif amt <= available {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := bimodalPrior(amt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok {\n\t\treturn prior\n\t}\n\n\tweight := r.evidenceWeight(belief)\n\tif weight < 0.01 {\n\t\treturn prior\n\t}\n\n\tp := prior\n\n\tif belief.lowerOK > 0 {\n\t\tif amt <= belief.lowerOK {\n\t\t\tp += weight * (0.995 - p)\n\t\t} else {\n\t\t\tratio := float64(amt-belief.lowerOK) /\n\t\t\t\tfloat64(maxMSat(1, edge.capacity-belief.lowerOK))\n\t\t\tboost := (0.995 - p) * math.Exp(-5*ratio)\n\t\t\tp += weight * boost\n\t\t}\n\t}\n\n\tif belief.failureVotes >= 2 && belief.upperBad > 0 {\n\t\tif amt >= belief.upperBad {\n\t\t\tp = p*(1-weight) + 0.005*weight\n\t\t} else if belief.lowerOK > 0 &&\n\t\t\tbelief.upperBad > belief.lowerOK {\n\n\t\t\tx := float64(amt-belief.lowerOK) /\n\t\t\t\tfloat64(belief.upperBad-belief.lowerOK)\n\t\t\tif x > 0 {\n\t\t\t\tevidenceP := clampProbability(0.995 - 0.99*x)\n\t\t\t\tp = p*(1-weight) + evidenceP*weight\n\t\t\t}\n\t\t} else {\n\t\t\tx := float64(amt) / float64(belief.upperBad)\n\t\t\tif x > 0.45 {\n\t\t\t\tpenalty := weight * 0.8 *\n\t\t\t\t\t(x - 0.45) / 0.55\n\t\t\t\tif penalty > 0.8 {\n\t\t\t\t\tpenalty = 0.8\n\t\t\t\t}\n\t\t\t\tp *= 1 - penalty\n\t\t\t}\n\t\t}\n\t} else if belief.failureVotes == 1 && belief.suspectAmt > 0 {\n\t\tx := float64(amt) / float64(belief.suspectAmt)\n\t\tif x > 1 {\n\t\t\tx = 1\n\t\t}\n\t\tp *= 1 - 0.30*weight*x*x*x\n\t}\n\n\treturn clampProbability(p)\n}\n\ntype pathLabel struct {\n\tscore float64\n\tneed lnwire.MilliSatoshi\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tneed lnwire.MilliSatoshi\n\tindex int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\tif q[i].score == q[j].score {\n\t\treturn q[i].need < q[j].need\n\t}\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) 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 *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *dijkstraQueue) 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) findPath(amt lnwire.MilliSatoshi,\n\textraReserved map[edgeKey]lnwire.MilliSatoshi,\n\tbanned map[edgeKey]bool) ([]*candidateEdge, error) {\n\n\tlabels := make(map[route.Vertex]pathLabel)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tlabels[r.spec.Target] = pathLabel{\n\t\tscore: 0,\n\t\tneed: amt,\n\t}\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tneed: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tlabel, ok := labels[item.node]\n\t\tif !ok || item.score > label.score+0.000001 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif banned != nil && banned[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.need\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif extraReserved != nil {\n\t\t\t\treserved += extraReserved[edge.key]\n\t\t\t}\n\t\t\tcombined := amtOver + reserved\n\t\t\tif combined > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tif combined > r.localBalances[edge.chanID] {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, combined)\n\t\t\triskCost := -math.Log(p) * 250_000\n\n\t\t\tif failure, ok := r.localFailures[edge.key]; ok &&\n\t\t\t\tfailure.amt > 0 {\n\n\t\t\t\tx := float64(combined) / float64(failure.amt)\n\t\t\t\tif x > 1 {\n\t\t\t\t\tx = 1\n\t\t\t\t}\n\t\t\t\triskCost += float64(failure.votes) *\n\t\t\t\t\t300_000 * x * x * x * x\n\t\t\t}\n\n\t\t\tif strikes := r.policyStrikes[edge.key]; strikes > 0 {\n\t\t\t\triskCost += float64(strikes) * 1_500_000\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := float64(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\t\t\t\tfeeCost = float64(fee)\n\t\t\t}\n\n\t\t\tscore := item.score + riskCost + feeCost + 50\n\t\t\told, exists := labels[edge.from]\n\t\t\tif exists && score >= old.score {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlabels[edge.from] = pathLabel{\n\t\t\t\tscore: score,\n\t\t\t\tneed: sending,\n\t\t\t}\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: score,\n\t\t\t\tneed: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := labels[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, errors.New(\"routing cycle\")\n\t\t}\n\t\tseen[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.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\treturn path, 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\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\tforwardEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tforwardEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(forwardEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforwardAmt := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforwardAmt = 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.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forwardAmt,\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 pathHash(path []*candidateEdge) uint64 {\n\thash := uint64(1469598103934665603)\n\tfor _, edge := range path {\n\t\thash ^= edge.chanID\n\t\thash *= 1099511628211\n\t\tfor _, b := range edge.from {\n\t\t\thash ^= uint64(b)\n\t\t\thash *= 1099511628211\n\t\t}\n\t}\n\treturn hash\n}\n\nfunc routeHash(rt *route.Route) uint64 {\n\thash := uint64(1469598103934665603)\n\tfrom := rt.SourcePubKey\n\tfor _, hop := range rt.Hops {\n\t\thash ^= hop.ChannelID\n\t\thash *= 1099511628211\n\t\tfor _, b := range from {\n\t\t\thash ^= uint64(b)\n\t\t\thash *= 1099511628211\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\treturn hash\n}\n\nfunc (r *candidateRouter) findAlternative(amt lnwire.MilliSatoshi,\n\textraReserved map[edgeKey]lnwire.MilliSatoshi) (\n\t[]*candidateEdge, error) {\n\n\tbanned := make(map[edgeKey]bool)\n\tvar fallback []*candidateEdge\n\n\tfor tries := 0; tries < 12; tries++ {\n\t\tpath, err := r.findPath(amt, extraReserved, banned)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif fallback == nil {\n\t\t\tfallback = path\n\t\t}\n\n\t\thash := pathHash(path)\n\t\tfailures := r.failedRoutes[hash]\n\t\tfailedAmt := r.routeFailAmt[hash]\n\t\tif failures == 0 || failedAmt == 0 ||\n\t\t\tamt*100 <= failedAmt*65 {\n\n\t\t\treturn path, nil\n\t\t}\n\n\t\tindex := (failures + tries + len(path)/2) % len(path)\n\t\tbanned[path[index].key] = true\n\t}\n\n\tif fallback != nil {\n\t\treturn fallback, nil\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc routeDeliveredAmt(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc routeEdgeAmt(rt *route.Route, index int) lnwire.MilliSatoshi {\n\tif index == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[index-1].AmtToForward\n}\n\nfunc (r *candidateRouter) routeProbability(rt *route.Route,\n\textraReserved map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\tp := 1.0\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t}\n\t\tedge, ok := r.edges[key]\n\t\tif !ok {\n\t\t\treturn 0.005\n\t\t}\n\n\t\tcombined := routeEdgeAmt(rt, i) + r.reserved[key]\n\t\tif extraReserved != nil {\n\t\t\tcombined += extraReserved[key]\n\t\t}\n\t\tp *= r.edgeProbability(edge, combined)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) routeFeasible(rt *route.Route,\n\textraReserved map[edgeKey]lnwire.MilliSatoshi) bool {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t}\n\t\tedge, ok := r.edges[key]\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tindividual := routeEdgeAmt(rt, i)\n\t\tif !edge.usable(individual) {\n\t\t\treturn false\n\t\t}\n\n\t\tcombined := individual + r.reserved[key]\n\t\tif extraReserved != nil {\n\t\t\tcombined += extraReserved[key]\n\t\t}\n\t\tif combined > edge.capacity {\n\t\t\treturn false\n\t\t}\n\t\tif edge.from == r.source &&\n\t\t\tcombined > r.localBalances[edge.chanID] {\n\n\t\t\treturn false\n\t\t}\n\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn true\n}\n\nfunc reserveRoute(rt *route.Route,\n\treservations map[edgeKey]lnwire.MilliSatoshi) {\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t}\n\t\treservations[key] += routeEdgeAmt(rt, i)\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc (r *candidateRouter) tryPlan(total lnwire.MilliSatoshi, parts int,\n\tunequal bool) ([]planEntry, bool) {\n\n\tif parts < 1 {\n\t\treturn nil, false\n\t}\n\n\tremaining := total\n\tplanned := make(map[edgeKey]lnwire.MilliSatoshi)\n\tentries := make([]planEntry, 0, parts)\n\n\tfor i := 0; i < parts && remaining > 0; i++ {\n\t\tleft := parts - i\n\t\tdeliver := (remaining + lnwire.MilliSatoshi(left) - 1) /\n\t\t\tlnwire.MilliSatoshi(left)\n\n\t\tpath, err := r.findAlternative(deliver, planned)\n\t\tif err != nil {\n\t\t\treturn entries, false\n\t\t}\n\n\t\trt, err := r.buildRoute(deliver, path)\n\t\tif err != nil {\n\t\t\treturn entries, false\n\t\t}\n\n\t\tif unequal && left > 1 {\n\t\t\tmaxExtra := deliver / 2\n\t\t\tminForRest := minShardAmt *\n\t\t\t\tlnwire.MilliSatoshi(left-1)\n\t\t\tif remaining > deliver+minForRest {\n\t\t\t\troom := remaining - deliver - minForRest\n\t\t\t\tif maxExtra > room {\n\t\t\t\t\tmaxExtra = room\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmaxExtra = 0\n\t\t\t}\n\n\t\t\tif maxExtra > 0 {\n\t\t\t\tlarger, buildErr := r.buildRoute(\n\t\t\t\t\tdeliver+maxExtra, path,\n\t\t\t\t)\n\t\t\t\tif buildErr == nil &&\n\t\t\t\t\tr.routeFeasible(larger, planned) &&\n\t\t\t\t\tr.routeProbability(larger, planned) >= 0.03 {\n\n\t\t\t\t\trt = larger\n\t\t\t\t\tdeliver += maxExtra\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !r.routeFeasible(rt, planned) {\n\t\t\treturn entries, false\n\t\t}\n\n\t\tentries = append(entries, planEntry{rt: rt})\n\t\treserveRoute(rt, planned)\n\t\tremaining -= deliver\n\t}\n\n\treturn entries, remaining == 0\n}\n\nfunc (r *candidateRouter) createPlan(total lnwire.MilliSatoshi,\n\tinFlight uint32) error {\n\n\tif total <= 0 {\n\t\treturn errors.New(\"no amount remaining\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlight >= maxParts {\n\t\treturn errors.New(\"maximum parts already in flight\")\n\t}\n\tslots := int(maxParts - inFlight)\n\n\tfullPath, fullErr := r.findAlternative(total, nil)\n\tif fullErr == nil {\n\t\tfullRoute, buildErr := r.buildRoute(total, fullPath)\n\t\tif buildErr == nil {\n\t\t\tfullP := r.routeProbability(fullRoute, nil)\n\t\t\tif slots == 1 ||\n\t\t\t\t(r.attempts == 0 && inFlight == 0 && fullP >= 0.12) {\n\n\t\t\t\tr.plan = []planEntry{{rt: fullRoute}}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\n\tif slots == 1 {\n\t\tif fullErr != nil {\n\t\t\treturn fullErr\n\t\t}\n\t\trt, err := r.buildRoute(total, fullPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.plan = []planEntry{{rt: rt}}\n\t\treturn nil\n\t}\n\n\tdesired := targetShardAmt\n\tif r.retryHint > 0 && r.retryHint < desired {\n\t\tdesired = r.retryHint\n\t}\n\tif desired < minShardAmt {\n\t\tdesired = minShardAmt\n\t}\n\n\tparts := int((total + desired - 1) / desired)\n\tif parts < 2 {\n\t\tparts = 2\n\t}\n\tif parts > slots {\n\t\tparts = slots\n\t}\n\n\tvar best []planEntry\n\tfor count := parts; count <= slots; count++ {\n\t\tentries, complete := r.tryPlan(total, count, true)\n\t\tif len(entries) > len(best) {\n\t\t\tbest = entries\n\t\t}\n\t\tif complete {\n\t\t\tr.plan = entries\n\t\t\treturn nil\n\t\t}\n\n\t\tentries, complete = r.tryPlan(total, count, false)\n\t\tif len(entries) > len(best) {\n\t\t\tbest = entries\n\t\t}\n\t\tif complete {\n\t\t\tr.plan = entries\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif len(best) > 0 {\n\t\tr.plan = best\n\t\treturn nil\n\t}\n\n\tif fullErr != nil {\n\t\treturn fullErr\n\t}\n\trt, err := r.buildRoute(total, fullPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.plan = []planEntry{{rt: rt}}\n\treturn nil\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tfor {\n\t\tfor len(r.plan) > 0 {\n\t\t\tentry := r.plan[0]\n\t\t\tr.plan = r.plan[1:]\n\n\t\t\tdeliver := routeDeliveredAmt(entry.rt)\n\t\t\tif deliver <= 0 || deliver > amt {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\thash := routeHash(entry.rt)\n\t\t\tif failedAmt := r.routeFailAmt[hash]; failedAmt > 0 &&\n\t\t\t\tdeliver*100 > failedAmt*65 {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr.attempts++\n\t\t\treturn entry.rt, nil\n\t\t}\n\n\t\tif err := r.createPlan(amt, inFlightHtlcs); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) updateShared(key edgeKey,\n\tupdate func(*liquidityBelief)) {\n\n\tbelief := r.beliefs[key]\n\tupdate(&belief)\n\tbelief.updated = r.view.Now()\n\tr.beliefs[key] = belief\n\n\tsharedBeliefs.Lock()\n\tshared := sharedBeliefs.m[key]\n\tif !shared.updated.IsZero() &&\n\t\tbelief.updated.Before(shared.updated) {\n\n\t\tsharedBeliefs.Unlock()\n\t\treturn\n\t}\n\tsharedBeliefs.m[key] = belief\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t}\n\t\tamtOver := routeEdgeAmt(rt, i)\n\n\t\tr.reserved[key] += amtOver\n\n\t\tfailure := r.localFailures[key]\n\t\tif failure.amt > 0 && amtOver >= failure.amt {\n\t\t\tdelete(r.localFailures, key)\n\t\t} else if failure.votes > 0 {\n\t\t\tfailure.votes--\n\t\t\tr.localFailures[key] = failure\n\t\t}\n\t\tdelete(r.policyStrikes, key)\n\n\t\tif from != r.source {\n\t\t\tr.updateShared(key, func(b *liquidityBelief) {\n\t\t\t\tif amtOver > b.lowerOK {\n\t\t\t\t\tb.lowerOK = amtOver\n\t\t\t\t}\n\t\t\t\tif b.upperBad > 0 &&\n\t\t\t\t\tamtOver >= b.upperBad {\n\n\t\t\t\t\tb.upperBad = 0\n\t\t\t\t\tb.failureVotes = 0\n\t\t\t\t\tb.suspectAmt = 0\n\t\t\t\t} else if b.failureVotes > 0 {\n\t\t\t\t\tb.failureVotes--\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tfrom = hop.PubKeyBytes\n\t}\n}\n\nfunc locateFailure(rt *route.Route,\n\tsource route.Vertex) (int, edgeKey, bool) {\n\n\tfailIndex := -1\n\tif source == rt.SourcePubKey {\n\t\tfailIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\treturn 0, edgeKey{}, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tif failIndex > 0 {\n\t\tfrom = rt.Hops[failIndex-1].PubKeyBytes\n\t}\n\n\treturn failIndex, edgeKey{\n\t\tchanID: rt.Hops[failIndex].ChannelID,\n\t\tfrom: from,\n\t}, true\n}\n\nfunc routeContainsKey(rt *route.Route, wanted edgeKey) bool {\n\tfrom := rt.SourcePubKey\n\tfor _, hop := range rt.Hops {\n\t\tkey := edgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t}\n\t\tif key == wanted {\n\t\t\treturn true\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\treturn false\n}\n\nfunc (r *candidateRouter) removePlansUsing(key edgeKey) {\n\tfiltered := r.plan[:0]\n\tfor _, entry := range r.plan {\n\t\tif !routeContainsKey(entry.rt, key) {\n\t\t\tfiltered = append(filtered, entry)\n\t\t}\n\t}\n\tr.plan = filtered\n\tr.planVersion++\n}\n\nfunc (r *candidateRouter) recordLiquidityFailure(key edgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tlocal := r.localFailures[key]\n\tif local.amt == 0 || amt < local.amt {\n\t\tlocal.amt = amt\n\t}\n\tlocal.votes++\n\tr.localFailures[key] = local\n\n\tif key.from == r.source {\n\t\treturn\n\t}\n\n\tr.updateShared(key, func(b *liquidityBelief) {\n\t\tnow := r.view.Now()\n\t\tstale := !b.updated.IsZero() &&\n\t\t\t(now.Before(b.updated) ||\n\t\t\t\tnow.Sub(b.updated) > 180*time.Minute)\n\n\t\tif stale {\n\t\t\tb.failureVotes = 0\n\t\t\tb.upperBad = 0\n\t\t\tb.suspectAmt = 0\n\t\t}\n\n\t\tif b.suspectAmt == 0 {\n\t\t\tb.suspectAmt = amt\n\t\t\tb.failureVotes = 1\n\t\t\treturn\n\t\t}\n\n\t\tlarge := maxMSat(amt, b.suspectAmt)\n\t\tsmall := minMSat(amt, b.suspectAmt)\n\t\tif small*4 < large {\n\t\t\tb.suspectAmt = amt\n\t\t\tb.failureVotes = 1\n\t\t\treturn\n\t\t}\n\n\t\tif b.failureVotes < 255 {\n\t\t\tb.failureVotes++\n\t\t}\n\t\tif amt < b.suspectAmt {\n\t\t\tb.suspectAmt = amt\n\t\t}\n\n\t\tif b.failureVotes >= 2 {\n\t\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\t\tb.upperBad = amt\n\t\t\t}\n\t\t\tif b.lowerOK >= b.upperBad {\n\t\t\t\tb.lowerOK = 0\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\thash := routeHash(rt)\n\tr.failedRoutes[hash]++\n\tdelivered := routeDeliveredAmt(rt)\n\tif old := r.routeFailAmt[hash]; old == 0 || delivered < old {\n\t\tr.routeFailAmt[hash] = delivered\n\t}\n\n\tif delivered > minShardAmt {\n\t\thint := delivered * 55 / 100\n\t\tif hint < minShardAmt {\n\t\t\thint = minShardAmt\n\t\t}\n\t\tif r.retryHint == 0 || hint < r.retryHint {\n\t\t\tr.retryHint = hint\n\t\t}\n\t}\n\n\tindex, key, attributed := locateFailure(\n\t\trt, result.FailureSource,\n\t)\n\tif !attributed {\n\t\treturn nil\n\t}\n\n\tamtOver := routeEdgeAmt(rt, index)\n\tr.removePlansUsing(key)\n\n\tif result.Failure.Code() == lnwire.CodeTemporaryChannelFailure {\n\t\tr.recordLiquidityFailure(key, amtOver)\n\t\treturn nil\n\t}\n\n\tr.policyStrikes[key]++\n\treturn nil\n}\n\nfunc minMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc maxMSat(a, b lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}"
}
},
{
"id": 23,
"parent": 0,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\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 route.Vertex\n\tto 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) usable(amt, reserved lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\tif reserved >= e.capacity {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity-reserved\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\n\tsoftBad lnwire.MilliSatoshi\n\tbadSeen uint8\n}\n\ntype candidateScopedKey struct {\n\tscope uintptr\n\tedge candidateEdgeKey\n}\n\nvar candidateKnowledge = struct {\n\tsync.Mutex\n\tbeliefs map[candidateScopedKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateScopedKey]candidateBelief),\n}\n\ntype candidatePenalty struct {\n\tamount lnwire.MilliSatoshi\n\tweight float64\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tscope uintptr\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\t// reserved accounts for both atomic-MPP holds and liquidity already\n\t// consumed by successful non-atomic shards.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// Penalties are payment-local. They diversify retries without turning\n\t// unreliable failure attribution into permanent graph poisoning.\n\tpenalties map[candidateEdgeKey]candidatePenalty\n\n\ttargetShard lnwire.MilliSatoshi\n\trequests uint32\n\tfailuresSincePass uint32\n\ttotalFailures uint32\n}\n\nfunc candidateViewScope(view routing.SimNetworkView) uintptr {\n\tv := reflect.ValueOf(view)\n\tswitch v.Kind() {\n\tcase reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr,\n\t\treflect.Slice, reflect.UnsafePointer:\n\n\t\treturn v.Pointer()\n\tdefault:\n\t\treturn 0\n\t}\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\tscope: candidateViewScope(view),\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tpenalties: make(map[candidateEdgeKey]candidatePenalty),\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tcandidateKnowledge.Lock()\n\tfor key := range r.edges {\n\t\tr.beliefs[key] = candidateKnowledge.beliefs[\n\t\t\tcandidateScopedKey{scope: r.scope, edge: key},\n\t\t]\n\t}\n\tcandidateKnowledge.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// candidatePrior models a channel as a roughly equal mixture of a depleted\n// and a well-funded direction. Tiny HTLCs can pass in either mode, while\n// amounts near capacity encounter the rich-mode cliff.\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif amt <= 0 {\n\t\treturn 0.985\n\t}\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-x/0.025)\n\trichMode := 0.5 / (1 + math.Exp((x-0.92)/0.045))\n\n\treturn candidateClampProbability(lowMode + richMode)\n}\n\nfunc candidateBeliefProbability(b candidateBelief, amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tprior := candidatePrior(amt, capacity)\n\n\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\treturn 0.006\n\t}\n\n\tif b.lowerOK > 0 && b.upperBad > b.lowerOK &&\n\t\tamt > b.lowerOK && amt < b.upperBad {\n\n\t\tt := float64(amt-b.lowerOK) /\n\t\t\tfloat64(b.upperBad-b.lowerOK)\n\t\tbounded := 0.995*(1-t) + 0.006*t\n\t\tprior = 0.25*prior + 0.75*bounded\n\t}\n\n\t// A single attributed failure is only a soft hint. A second compatible\n\t// observation is required before upperBad becomes a hard bound.\n\tif b.softBad > 0 {\n\t\tratio := float64(amt) / float64(b.softBad)\n\t\tif ratio > 1 {\n\t\t\tratio = 1\n\t\t}\n\t\tif ratio > 0 {\n\t\t\tprior *= 1 - 0.55*ratio*ratio\n\t\t}\n\t}\n\n\treturn candidateClampProbability(prior)\n}\n\nfunc candidateObserveSuccess(b candidateBelief,\n\tamt lnwire.MilliSatoshi) candidateBelief {\n\n\tif amt > b.lowerOK {\n\t\tb.lowerOK = amt\n\t}\n\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\tb.upperBad = 0\n\t}\n\tif b.softBad > 0 && amt >= b.softBad {\n\t\tb.softBad = 0\n\t\tb.badSeen = 0\n\t}\n\treturn b\n}\n\nfunc candidateObserveFailure(b candidateBelief,\n\tamt lnwire.MilliSatoshi) candidateBelief {\n\n\tif amt <= 0 {\n\t\treturn b\n\t}\n\n\tif b.softBad == 0 {\n\t\tb.softBad = amt\n\t\tb.badSeen = 1\n\t\treturn b\n\t}\n\n\tlo, hi := b.softBad, amt\n\tif lo > hi {\n\t\tlo, hi = hi, lo\n\t}\n\n\t// Require two observations within a factor of two. Correct blame tends\n\t// to recur at similar amounts; shifted blame usually does not.\n\tif lo > 0 && hi <= lo*2 {\n\t\tif b.badSeen < 255 {\n\t\t\tb.badSeen++\n\t\t}\n\t\tif amt < b.softBad {\n\t\t\tb.softBad = amt\n\t\t}\n\t\tif b.badSeen >= 2 &&\n\t\t\t(b.upperBad == 0 || hi < b.upperBad) {\n\n\t\t\tb.upperBad = hi\n\t\t}\n\t} else if amt < b.softBad {\n\t\tb.softBad = amt\n\t\tb.badSeen = 1\n\t}\n\n\treturn b\n}\n\nfunc candidateResidualBelief(b candidateBelief,\n\tamt lnwire.MilliSatoshi) candidateBelief {\n\n\tif b.lowerOK > amt {\n\t\tb.lowerOK -= amt\n\t} else {\n\t\tb.lowerOK = 0\n\t}\n\tif b.upperBad > amt {\n\t\tb.upperBad -= amt\n\t} else {\n\t\tb.upperBad = 0\n\t}\n\tif b.softBad > amt {\n\t\tb.softBad -= amt\n\t} else {\n\t\tb.softBad = 0\n\t\tb.badSeen = 0\n\t}\n\n\treturn b\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n\trisk 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) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\treserved := r.reserved[edge.key]\n\tneed := amt + reserved\n\n\tif edge.key.from == r.source {\n\t\tbalance := r.localBalances[edge.key.chanID]\n\t\tif need > balance {\n\t\t\treturn 0.005\n\t\t}\n\t\treturn 0.999\n\t}\n\n\treturn candidateBeliefProbability(\n\t\tr.beliefs[edge.key], need, edge.capacity,\n\t)\n}\n\nfunc (r *candidateRouter) edgePenalty(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp, ok := r.penalties[edge.key]\n\tif !ok || p.amount <= 0 {\n\t\treturn 0\n\t}\n\n\tneed := amt + r.reserved[edge.key]\n\tratio := float64(need) / float64(p.amount)\n\tif ratio > 1 {\n\t\tratio = 1\n\t}\n\tif ratio < 0 {\n\t\tratio = 0\n\t}\n\n\treturn p.weight * ratio * ratio\n}\n\nfunc (r *candidateRouter) addPenalty(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi, weight float64) {\n\n\tif amt <= 0 || weight <= 0 {\n\t\treturn\n\t}\n\n\tp := r.penalties[key]\n\tif p.amount == 0 || amt < p.amount {\n\t\tp.amount = amt\n\t}\n\tp.weight += weight\n\tif p.weight > 16 {\n\t\tp.weight = 16\n\t}\n\tr.penalties[key] = p\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\n\tbest := make(map[route.Vertex]float64)\n\tbestRisk := make(map[route.Vertex]float64)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbest[r.spec.Target] = 0\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tif score, ok := best[item.node]; ok &&\n\t\t\titem.score > score+1e-12 {\n\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\treserved := r.reserved[edge.key]\n\t\t\tif !edge.usable(item.amount, reserved) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tbalance := r.localBalances[edge.key.chanID]\n\t\t\t\tif item.amount > balance ||\n\t\t\t\t\treserved > balance-item.amount {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, item.amount)\n\t\t\tif probability <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := item.amount\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(item.amount)\n\t\t\t\tsending += fee\n\t\t\t}\n\n\t\t\tedgeRisk := -math.Log(probability)\n\t\t\tfeeCost := 0.0\n\t\t\tif item.amount > 0 {\n\t\t\t\tfeeCost = 0.15 * float64(fee) /\n\t\t\t\t\tfloat64(item.amount)\n\t\t\t}\n\n\t\t\tscore := item.score + edgeRisk + feeCost + 0.01 +\n\t\t\t\tr.edgePenalty(edge, item.amount)\n\t\t\trisk := item.risk + edgeRisk\n\n\t\t\told, ok := best[edge.key.from]\n\t\t\tif !ok || score < old {\n\t\t\t\tbest[edge.key.from] = score\n\t\t\t\tbestRisk[edge.key.from] = risk\n\t\t\t\tnext[edge.key.from] = edge\n\t\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\t\tnode: edge.key.from,\n\t\t\t\t\tamount: sending,\n\t\t\t\t\tscore: score,\n\t\t\t\t\trisk: risk,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tif _, ok := best[r.source]; !ok {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn rt, math.Exp(-bestRisk[r.source]), 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\tnode := r.source\n\tfor node != r.spec.Target {\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\n\t\tif len(path) > 1000 {\n\t\t\treturn nil, errors.New(\"route contains a cycle\")\n\t\t}\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\texpiries := make([]uint32, len(path))\n\n\tlast := len(path) - 1\n\tamounts[last] = amt\n\texpiries[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tforwardingEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tforwardingEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amounts[i+1]\n\t\t\texpiry = expiries[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.key.to,\n\t\t\tChannelID: edge.key.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\n\t\t}\n\t}\n\n\treturn &route.Route{\n\t\tTotalTimeLock: expiries[0],\n\t\tTotalAmount: amounts[0],\n\t\tSourcePubKey: r.source,\n\t\tHops: hops,\n\t}, nil\n}\n\nfunc candidateCeilDiv(amount lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amount\n\t}\n\tp := lnwire.MilliSatoshi(parts)\n\treturn amount/p + lnwire.MilliSatoshi(boolToInt(amount%p != 0))\n}\n\nfunc boolToInt(v bool) int64 {\n\tif v {\n\t\treturn 1\n\t}\n\treturn 0\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 is zero\")\n\t}\n\tif r.requests >= 48 {\n\t\treturn nil, errors.New(\"route attempt budget exhausted\")\n\t}\n\tr.requests++\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 parts already in flight\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminShard := candidateCeilDiv(amt, partsLeft)\n\n\tif r.targetShard == 0 {\n\t\tfullRoute, fullProbability, fullErr := r.findRoute(amt)\n\t\tif fullErr == nil &&\n\t\t\t(partsLeft == 1 || fullProbability >= 0.18 ||\n\t\t\t\tamt < 10_000_000) {\n\n\t\t\tr.targetShard = amt\n\t\t\treturn fullRoute, nil\n\t\t}\n\n\t\tplannedParts := uint32(2)\n\t\tif fullErr != nil {\n\t\t\tplannedParts = 2\n\t\t} else if fullProbability < 0.008 {\n\t\t\tplannedParts = 4\n\t\t} else if fullProbability < 0.025 {\n\t\t\tplannedParts = 3\n\t\t}\n\t\tif plannedParts > partsLeft {\n\t\t\tplannedParts = partsLeft\n\t\t}\n\t\tif plannedParts == 0 {\n\t\t\tplannedParts = 1\n\t\t}\n\n\t\tr.targetShard = candidateCeilDiv(amt, plannedParts)\n\t}\n\n\tdesired := r.targetShard\n\tif desired > amt {\n\t\tdesired = amt\n\t}\n\tif desired < minShard {\n\t\tdesired = minShard\n\t}\n\n\tfor {\n\t\trt, _, err := r.findRoute(desired)\n\t\tif err == nil {\n\t\t\tr.targetShard = desired\n\t\t\treturn rt, nil\n\t\t}\n\n\t\tif desired <= minShard {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnext := desired * 2 / 3\n\t\tif next < minShard {\n\t\t\tnext = minShard\n\t\t}\n\t\tif next == desired {\n\t\t\treturn nil, err\n\t\t}\n\t\tdesired = next\n\t}\n}\n\nfunc (r *candidateRouter) routeEdges(rt *route.Route) (\n\t[]candidateEdgeKey, []lnwire.MilliSatoshi) {\n\n\tkeys := make([]candidateEdgeKey, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, len(rt.Hops))\n\n\tfrom := rt.SourcePubKey\n\tfor i, hop := range rt.Hops {\n\t\tkeys[i] = candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tif i == 0 {\n\t\t\tamounts[i] = rt.TotalAmount\n\t\t} else {\n\t\t\tamounts[i] = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn keys, amounts\n}\n\nfunc (r *candidateRouter) reportedFailureIndex(\n\trt *route.Route, source route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) persistFailure(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tscoped := candidateScopedKey{scope: r.scope, edge: key}\n\n\tcandidateKnowledge.Lock()\n\tb := candidateKnowledge.beliefs[scoped]\n\tcandidateKnowledge.beliefs[scoped] =\n\t\tcandidateObserveFailure(b, amt)\n\tcandidateKnowledge.Unlock()\n}\n\nfunc (r *candidateRouter) persistSuccess(key candidateEdgeKey,\n\tamt lnwire.MilliSatoshi) {\n\n\tscoped := candidateScopedKey{scope: r.scope, edge: key}\n\treverseKey := candidateEdgeKey{\n\t\tchanID: key.chanID,\n\t\tfrom: key.to,\n\t\tto: key.from,\n\t}\n\treverseScoped := candidateScopedKey{\n\t\tscope: r.scope,\n\t\tedge: reverseKey,\n\t}\n\n\tcandidateKnowledge.Lock()\n\n\tb := candidateKnowledge.beliefs[scoped]\n\tb = candidateObserveSuccess(b, amt)\n\tcandidateKnowledge.beliefs[scoped] =\n\t\tcandidateResidualBelief(b, amt)\n\n\treverse := candidateKnowledge.beliefs[reverseScoped]\n\treverse = candidateObserveSuccess(reverse, amt)\n\tcandidateKnowledge.beliefs[reverseScoped] = reverse\n\n\tcandidateKnowledge.Unlock()\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := r.routeEdges(rt)\n\n\tif result.Failure == nil {\n\t\tr.failuresSincePass = 0\n\n\t\tfor i, key := range keys {\n\t\t\tneed := amounts[i] + r.reserved[key]\n\t\t\tr.beliefs[key] = candidateObserveSuccess(\n\t\t\t\tr.beliefs[key], need,\n\t\t\t)\n\t\t\tr.reserved[key] += amounts[i]\n\n\t\t\t// Prefer a distinct corridor for the next atomic shard, but\n\t\t\t// retain this one as a fallback at lower cumulative amounts.\n\t\t\tr.addPenalty(key, need, 0.8)\n\t\t\tr.persistSuccess(key, amounts[i])\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.totalFailures++\n\tr.failuresSincePass++\n\n\tfailIndex := r.reportedFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\t// Every failed route gets a small, payment-local diversification\n\t// penalty. Unreadable failures deliberately update no persistent\n\t// liquidity state.\n\tfor i, key := range keys {\n\t\tneed := amounts[i] + r.reserved[key]\n\t\tr.addPenalty(key, need, 0.45)\n\t}\n\n\tif failIndex >= 0 && failIndex < len(keys) {\n\t\tkey := keys[failIndex]\n\t\tneed := amounts[failIndex] + r.reserved[key]\n\n\t\t_, liquidityFailure :=\n\t\t\tresult.Failure.(*lnwire.FailTemporaryChannelFailure)\n\n\t\tif liquidityFailure {\n\t\t\tr.addPenalty(key, need, 2.8)\n\n\t\t\t// Adjacent hops receive only weak local suspicion because the\n\t\t\t// simulator may shift blame by one hop.\n\t\t\tif failIndex > 0 {\n\t\t\t\tprevNeed := amounts[failIndex-1] +\n\t\t\t\t\tr.reserved[keys[failIndex-1]]\n\t\t\t\tr.addPenalty(\n\t\t\t\t\tkeys[failIndex-1], prevNeed, 0.35,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif failIndex+1 < len(keys) {\n\t\t\t\tnextNeed := amounts[failIndex+1] +\n\t\t\t\t\tr.reserved[keys[failIndex+1]]\n\t\t\t\tr.addPenalty(\n\t\t\t\t\tkeys[failIndex+1], nextNeed, 0.35,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tr.beliefs[key] = candidateObserveFailure(\n\t\t\t\tr.beliefs[key], need,\n\t\t\t)\n\t\t\tr.persistFailure(key, amounts[failIndex])\n\t\t} else {\n\t\t\t// A policy failure should not become a liquidity bound.\n\t\t\tr.addPenalty(key, need, 5.0)\n\t\t}\n\t}\n\n\t// Retry failed corridors at lower amounts instead of permanently\n\t// blacklisting them. Two same-size alternatives are explored before\n\t// reducing the planned shard.\n\tif r.failuresSincePass%2 == 0 && r.targetShard > 1 {\n\t\tr.targetShard = r.targetShard * 2 / 3\n\t\tif r.targetShard == 0 {\n\t\t\tr.targetShard = 1\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 24,
"parent": 3,
"score": 0.6102,
"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\"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\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\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) usable(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\n// Successes are trustworthy. Failures remain soft until several compatible\n// reports corroborate the same directed channel.\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n\n\tstamp uint64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\t// Successful atomic-MPP shards reserve liquidity until completion. This\n\t// also models liquidity consumed by settled shards for the lifetime of\n\t// the current payment.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// These penalties are deliberately payment-local. They diversify retries\n\t// without poisoning persistent beliefs when failure attribution lies.\n\tedgePenalty map[candidateEdgeKey]float64\n\n\t// Planned routes are constructed together using virtual reservations.\n\t// This makes the route set account for sibling contention before sending\n\t// the first shard.\n\tplannedRoutes []*route.Route\n\n\tretryLimit lnwire.MilliSatoshi\n\tfailedAttempts uint32\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\tcandidateMemory.Lock()\n\tcandidateMemory.epoch++\n\tepoch := candidateMemory.epoch\n\n\tsnapshot := make(map[candidateEdgeKey]candidateBelief,\n\t\tlen(candidateMemory.beliefs))\n\tfor key, belief := range candidateMemory.beliefs {\n\t\tsnapshot[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: snapshot,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\t// InPolicy is announced by OtherNode and governs the\n\t\t\t\t// directed edge OtherNode -> node.\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},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// Most channels are almost empty or almost full in one direction. The low\n// mode falls rapidly, while the high mode has a cliff near usable capacity.\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.022)\n\thighMode := 1 / (1 + math.Exp(20*(x-0.84)))\n\n\treturn candidateClampProbability(\n\t\t0.48*lowMode + 0.52*highMode,\n\t)\n}\n\nfunc (r *candidateRouter) evidenceConfidence(b candidateBelief) float64 {\n\tif b.stamp == 0 || r.epoch <= b.stamp+4 {\n\t\treturn 1\n\t}\n\n\tage := float64(r.epoch - b.stamp - 4)\n\treturn math.Max(0.15, math.Exp(-age/14))\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tif totalAmt <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := candidatePrior(totalAmt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.stamp == 0 {\n\t\treturn prior\n\t}\n\n\tlearned := prior\n\n\tswitch {\n\tcase belief.lowerOK > 0 && totalAmt <= belief.lowerOK:\n\t\tlearned = 0.995\n\n\tcase belief.upperFail > 0 && totalAmt >= belief.upperFail:\n\t\tlearned = 0.008\n\n\tcase belief.lowerOK > 0 && belief.upperFail > belief.lowerOK:\n\t\tspan := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(totalAmt-belief.lowerOK) / span\n\t\tpos = math.Max(0, math.Min(1, pos))\n\t\tlearned = 0.995*(1-pos) + 0.008*pos\n\n\tcase belief.lowerOK > 0 && totalAmt > belief.lowerOK:\n\t\tratio := float64(belief.lowerOK) / float64(totalAmt)\n\t\tlearned = prior + (0.995-prior)*ratio*ratio\n\n\tcase belief.upperFail > 0 && totalAmt < belief.upperFail:\n\t\tratio := float64(totalAmt) / float64(belief.upperFail)\n\t\tlearned = prior * (1 - 0.72*ratio*ratio)\n\t}\n\n\t// One plausible but uncorroborated report is only a hint because blame\n\t// may have moved to an adjacent hop.\n\tif belief.upperFail == 0 && belief.suspectAmt > 0 &&\n\t\ttotalAmt >= belief.suspectAmt {\n\n\t\tlearned *= 0.65\n\t}\n\n\tconfidence := r.evidenceConfidence(belief)\n\treturn candidateClampProbability(\n\t\tconfidence*learned + (1-confidence)*prior,\n\t)\n}\n\ntype candidateItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype candidateQueue []*candidateItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype candidatePath struct {\n\troute *route.Route\n\tedges []*candidateEdge\n\tprobability float64\n\tpressure float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tamounts := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tamounts[r.spec.Target] = amt\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateItem)\n\t\tbestScore, ok := scores[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amounts[item.node]\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usable(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := arriving + r.reserved[edge.key]\n\t\t\tif totalLiquidity < arriving ||\n\t\t\t\ttotalLiquidity > edge.capacity {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source &&\n\t\t\t\ttotalLiquidity > r.localBalances[edge.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, totalLiquidity)\n\t\t\tedgeCost := -math.Log(probability) + 0.02\n\n\t\t\tif arriving > 0 {\n\t\t\t\tedgeCost += 4.2 * float64(fee) /\n\t\t\t\t\tfloat64(arriving)\n\t\t\t}\n\n\t\t\tedgeCost += r.edgePenalty[edge.key]\n\n\t\t\t// Reusing an already-held corridor remains possible, but a\n\t\t\t// fresh corridor is preferred for atomic-MPP siblings.\n\t\t\tif edge.from != r.source &&\n\t\t\t\tr.reserved[edge.key] > 0 &&\n\t\t\t\tedge.capacity > 0 {\n\n\t\t\t\treuse := float64(r.reserved[edge.key]) /\n\t\t\t\t\tfloat64(edge.capacity)\n\t\t\t\tedgeCost += 0.35 + 0.9*math.Sqrt(reuse)\n\t\t\t}\n\n\t\t\tnewScore := item.score + edgeCost\n\t\t\toldScore, visited := scores[edge.from]\n\t\t\tif visited && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.from] = newScore\n\t\t\tamounts[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &candidateItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tpressure := 0.0\n\n\tfor i, edge := range path {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\ttotalLiquidity := amtOver + r.reserved[edge.key]\n\n\t\tprobability *= r.probability(edge, totalLiquidity)\n\n\t\tdenominator := edge.capacity\n\t\tif edge.from == r.source {\n\t\t\tdenominator = r.localBalances[edge.chanID]\n\t\t} else if belief, ok := r.beliefs[edge.key]; ok &&\n\t\t\tbelief.upperFail > 0 &&\n\t\t\tr.evidenceConfidence(belief) >= 0.75 &&\n\t\t\tbelief.upperFail < denominator {\n\n\t\t\tdenominator = belief.upperFail\n\t\t}\n\n\t\tif denominator > 0 {\n\t\t\tratio := float64(totalLiquidity) /\n\t\t\t\tfloat64(denominator)\n\t\t\tif ratio > pressure {\n\t\t\t\tpressure = ratio\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &candidatePath{\n\t\troute: rt,\n\t\tedges: path,\n\t\tprobability: probability,\n\t\tpressure: pressure,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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 := 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.to,\n\t\t\tChannelID: edge.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}, path, nil\n}\n\nfunc candidateCeilDiv(a lnwire.MilliSatoshi,\n\tb uint32) lnwire.MilliSatoshi {\n\n\tif b <= 1 {\n\t\treturn a\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(b)\n\treturn a/divisor + boolToMSat(a%divisor != 0)\n}\n\nfunc boolToMSat(value bool) lnwire.MilliSatoshi {\n\tif value {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc candidateCloneAmountMap(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, value := range source {\n\t\tresult[key] = value\n\t}\n\treturn result\n}\n\nfunc candidateClonePenaltyMap(\n\tsource map[candidateEdgeKey]float64,\n) map[candidateEdgeKey]float64 {\n\n\tresult := make(map[candidateEdgeKey]float64, len(source))\n\tfor key, value := range source {\n\t\tresult[key] = value\n\t}\n\treturn result\n}\n\n// planRouteSet chooses all shard routes against virtual reservations. Later\n// shards therefore see the capacity claimed by earlier siblings, and route\n// diversity is selected before attempts allow the network to drift.\nfunc (r *candidateRouter) planRouteSet(\n\ttotal lnwire.MilliSatoshi, numParts uint32) ([]*route.Route, error) {\n\n\tif numParts < 2 {\n\t\treturn nil, errors.New(\"route set requires multiple parts\")\n\t}\n\n\tsavedReserved := r.reserved\n\tsavedPenalty := r.edgePenalty\n\n\tr.reserved = candidateCloneAmountMap(savedReserved)\n\tr.edgePenalty = candidateClonePenaltyMap(savedPenalty)\n\n\tdefer func() {\n\t\tr.reserved = savedReserved\n\t\tr.edgePenalty = savedPenalty\n\t}()\n\n\troutes := make([]*route.Route, 0, numParts)\n\tremaining := total\n\n\tfor part := uint32(0); part < numParts; part++ {\n\t\tpartsRemaining := numParts - part\n\t\tshard := candidateCeilDiv(remaining, partsRemaining)\n\n\t\tpath, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\troutes = append(routes, path.route)\n\t\tremaining -= shard\n\n\t\tfor i, edge := range path.edges {\n\t\t\tkey := edge.key\n\t\t\tr.reserved[key] += candidateRouteAmount(\n\t\t\t\tpath.route, i,\n\t\t\t)\n\n\t\t\t// Sharing the sender's limited local channels is sometimes\n\t\t\t// unavoidable. Internal corridors receive a stronger virtual\n\t\t\t// diversity price.\n\t\t\tif edge.from == r.source {\n\t\t\t\tr.edgePenalty[key] += 0.12\n\t\t\t} else {\n\t\t\t\tr.edgePenalty[key] += 1.05\n\t\t\t}\n\t\t}\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, errors.New(\"incomplete route set\")\n\t}\n\n\treturn routes, nil\n}\n\nfunc (r *candidateRouter) preferredPartCount(\n\tfull *candidatePath, fullErr error,\n\tpartsLeft uint32) uint32 {\n\n\tmaxParts := partsLeft\n\tif maxParts > 12 {\n\t\tmaxParts = 12\n\t}\n\tif maxParts < 2 {\n\t\treturn 1\n\t}\n\n\tpreferred := uint32(2)\n\n\tif fullErr != nil {\n\t\tpreferred = 4\n\t} else {\n\t\tcapacityParts := uint32(math.Ceil(\n\t\t\tfull.pressure / 0.42,\n\t\t))\n\t\tif capacityParts > preferred {\n\t\t\tpreferred = capacityParts\n\t\t}\n\n\t\tswitch {\n\t\tcase full.probability < 0.16:\n\t\t\tif preferred < 4 {\n\t\t\t\tpreferred = 4\n\t\t\t}\n\t\tcase full.probability < 0.38:\n\t\t\tif preferred < 3 {\n\t\t\t\tpreferred = 3\n\t\t\t}\n\t\t}\n\t}\n\n\tif r.failedAttempts > 0 && preferred < 3 {\n\t\tpreferred = 3\n\t}\n\n\tif preferred > maxParts {\n\t\tpreferred = maxParts\n\t}\n\treturn preferred\n}\n\nfunc candidateDeliveredAmount(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 (r *candidateRouter) takePlannedRoute(\n\tremaining lnwire.MilliSatoshi,\n\tpartsLeft uint32) (*route.Route, bool) {\n\n\tif len(r.plannedRoutes) == 0 {\n\t\treturn nil, false\n\t}\n\n\tif partsLeft <= 1 {\n\t\tr.plannedRoutes = nil\n\t\treturn nil, false\n\t}\n\n\trt := r.plannedRoutes[0]\n\tdelivered := candidateDeliveredAmount(rt)\n\tif delivered <= 0 || delivered > remaining {\n\t\tr.plannedRoutes = nil\n\t\treturn nil, false\n\t}\n\n\tr.plannedRoutes = r.plannedRoutes[1:]\n\treturn rt, true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tif rt, ok := r.takePlannedRoute(amt, partsLeft); ok {\n\t\treturn rt, nil\n\t}\n\n\t// The final available part must carry the complete remainder.\n\tif partsLeft <= 1 {\n\t\tpath, err := r.findRoute(amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn path.route, nil\n\t}\n\n\tfull, fullErr := r.findRoute(amt)\n\n\t// Whole payments avoid unnecessary MPP fees when evidence makes the\n\t// route genuinely convincing. A previous failure raises this bar.\n\twholeThreshold := 0.76\n\tif r.failedAttempts > 0 {\n\t\twholeThreshold = 0.92\n\t}\n\n\tif fullErr == nil &&\n\t\t(full.probability >= wholeThreshold ||\n\t\t\t(amt <= 15_000_000 &&\n\t\t\t\tfull.probability >= 0.48)) {\n\n\t\treturn full.route, nil\n\t}\n\n\tpreferred := r.preferredPartCount(\n\t\tfull, fullErr, partsLeft,\n\t)\n\n\tmaxPlanParts := partsLeft\n\tif maxPlanParts > 12 {\n\t\tmaxPlanParts = 12\n\t}\n\n\t// Try increasingly fine joint plans. This is computation rather than a\n\t// probe ladder: no attempt is spent until a complete route set exists.\n\tfor numParts := preferred; numParts <= maxPlanParts; numParts++ {\n\t\tplans, err := r.planRouteSet(amt, numParts)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tr.plannedRoutes = plans\n\t\trt := r.plannedRoutes[0]\n\t\tr.plannedRoutes = r.plannedRoutes[1:]\n\t\treturn rt, nil\n\t}\n\n\t// If a complete route set cannot be formed, retain success-oriented\n\t// fallback behavior. Corroborated bounds are probabilities rather than\n\t// permanent exclusions, so a necessary corridor remains reachable.\n\tif fullErr == nil && r.failedAttempts == 0 {\n\t\treturn full.route, nil\n\t}\n\n\tdesired := r.retryLimit\n\tif desired <= 0 || desired > amt {\n\t\tdesired = candidateCeilDiv(amt, partsLeft)\n\t}\n\n\tconst minimumShard = lnwire.MilliSatoshi(500_000)\n\tvar lastErr error\n\n\tfor {\n\t\tpath, err := r.findRoute(desired)\n\t\tif err == nil {\n\t\t\treturn path.route, nil\n\t\t}\n\t\tlastErr = err\n\n\t\tif desired <= minimumShard {\n\t\t\tbreak\n\t\t}\n\n\t\tsmaller := desired * 55 / 100\n\t\tif smaller < minimumShard {\n\t\t\tsmaller = minimumShard\n\t\t}\n\t\tif smaller >= desired {\n\t\t\tbreak\n\t\t}\n\n\t\tdesired = smaller\n\t\tr.retryLimit = smaller\n\t}\n\n\tif fullErr == nil {\n\t\treturn full.route, nil\n\t}\n\treturn nil, lastErr\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tedgeIndex int) lnwire.MilliSatoshi {\n\n\tif edgeIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[edgeIndex-1].AmtToForward\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tedgeIndex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif edgeIndex > 0 {\n\t\tfrom = rt.Hops[edgeIndex-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[edgeIndex].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc candidateFailureEdge(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc candidateIsLiquidityFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc candidateIsPolicyFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey, belief candidateBelief) {\n\n\tbelief.stamp = r.epoch\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tcandidateMemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tif totalAmt > belief.lowerOK {\n\t\tbelief.lowerOK = totalAmt\n\t}\n\n\t// A truthful settlement disproves contradictory failure observations.\n\tif belief.upperFail > 0 && totalAmt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 && totalAmt >= belief.suspectAmt {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tcorroborates := false\n\tif belief.suspectAmt > 0 {\n\t\tlow := belief.suspectAmt * 60 / 100\n\t\thigh := belief.suspectAmt * 155 / 100\n\t\tcorroborates = totalAmt >= low && totalAmt <= high\n\t}\n\n\tif corroborates {\n\t\tif belief.suspectVotes < 255 {\n\t\t\tbelief.suspectVotes++\n\t\t}\n\t\tif totalAmt < belief.suspectAmt {\n\t\t\tbelief.suspectAmt = totalAmt\n\t\t}\n\t} else {\n\t\tbelief.suspectAmt = totalAmt\n\t\tbelief.suspectVotes = 1\n\t}\n\n\t// Three compatible reports make accidental repeated shifted blame rare.\n\trequiredVotes := uint8(3)\n\tif belief.lowerOK > 0 && totalAmt <= belief.lowerOK {\n\t\trequiredVotes = 4\n\t}\n\n\tif belief.suspectVotes >= requiredVotes {\n\t\tif totalAmt <= belief.lowerOK {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\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 nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, totalAmt)\n\t\t\tr.reserved[key] += amtOver\n\t\t\tr.edgePenalty[key] *= 0.35\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.failedAttempts++\n\n\t// The route certainly failed, even if its onion attribution is unreadable\n\t// or shifted. Diversify softly across every edge without persisting this\n\t// route-level fact as false liquidity knowledge.\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t4.5, r.edgePenalty[key]+0.42,\n\t\t)\n\t}\n\n\tfailIndex := candidateFailureEdge(\n\t\trt, result.FailureSource,\n\t)\n\tattributed := failIndex >= 0 && failIndex < len(rt.Hops)\n\n\tdelivered := candidateDeliveredAmount(rt)\n\n\tif candidateIsLiquidityFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tamtOver := candidateRouteAmount(rt, failIndex)\n\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\tr.recordFailure(key, totalAmt)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t7, r.edgePenalty[key]+1.15,\n\t\t)\n\n\t\tlowerRetry := delivered * 55 / 100\n\t\tif lowerRetry < 500_000 {\n\t\t\tlowerRetry = 500_000\n\t\t}\n\t\tif r.retryLimit == 0 || lowerRetry < r.retryLimit {\n\t\t\tr.retryLimit = lowerRetry\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif candidateIsPolicyFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tr.edgePenalty[key] = math.Min(\n\t\t\t7, r.edgePenalty[key]+1.8,\n\t\t)\n\t\treturn nil\n\t}\n\n\t// An unreadable failure supplies no channel-level evidence. Preserve any\n\t// jointly planned sibling routes; if they are exhausted, use a moderately\n\t// smaller fallback rather than repeating the same route indefinitely.\n\tlowerRetry := delivered * 70 / 100\n\tif lowerRetry < 500_000 {\n\t\tlowerRetry = 500_000\n\t}\n\tif r.retryLimit == 0 || lowerRetry < r.retryLimit {\n\t\tr.retryLimit = lowerRetry\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 25,
"parent": 1,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"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\testimate lnwire.MilliSatoshi\n\tconf uint8\n\n\tfailAmt lnwire.MilliSatoshi\n\tfailWeight float64\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tweight float64\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\trouteFails map[uint64]float64\n\n\tplannedParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\trouteFails: make(map[uint64]float64),\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tswitch {\n\tcase amt <= 25_000_000:\n\t\treturn 1\n\tcase amt <= 100_000_000:\n\t\treturn minCandidateParts(2, maxParts)\n\tcase amt <= 500_000_000:\n\t\treturn minCandidateParts(4, maxParts)\n\tcase amt <= 2_000_000_000:\n\t\treturn minCandidateParts(8, maxParts)\n\tdefault:\n\t\treturn maxParts\n\t}\n}\n\nfunc minCandidateParts(a, b uint32) uint32 {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.065 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tif z > 30 {\n\t\treturn 0.005\n\t}\n\tif z < -30 {\n\t\treturn 0.995\n\t}\n\treturn 1 / (1 + math.Exp(z))\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.13 * float64(belief.conf)\n\t\t\tif weight > 0.72 {\n\t\t\t\tweight = 0.72\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.failAmt > 0 && required >= belief.failAmt {\n\t\t\tif belief.failWeight >= 1.55 {\n\t\t\t\tp = math.Min(p, 0.009)\n\t\t\t} else {\n\t\t\t\tp *= math.Exp(-0.28 * belief.failWeight)\n\t\t\t}\n\t\t} else if belief.failAmt > belief.lowerOK &&\n\t\t\tbelief.lowerOK > 0 && required > belief.lowerOK {\n\n\t\t\tspan := float64(belief.failAmt - belief.lowerOK)\n\t\t\tpos := float64(required-belief.lowerOK) / span\n\t\t\tif pos > 1 {\n\t\t\t\tpos = 1\n\t\t\t}\n\t\t\tbounded := 0.995*(1-pos) + 0.009*pos\n\t\t\tweight := 0.25 + 0.25*math.Min(\n\t\t\t\tbelief.failWeight, 1,\n\t\t\t)\n\t\t\tp = (1-weight)*p + weight*bounded\n\t\t}\n\t}\n\n\tif local, ok := r.localFails[e.key]; ok &&\n\t\trequired >= local.upper {\n\n\t\tif local.weight >= 1.55 {\n\t\t\tp = math.Min(p, 0.005)\n\t\t} else {\n\t\t\tp *= math.Exp(-0.95 * local.weight)\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.012 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tshare := float64(reserved) / float64(e.capacity)\n\t\tcost += 0.90 + 0.80*share\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tscore := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscore[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tbestScore, ok := score[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.amt != arriving[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 16 * float64(fee) / denom\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(edge, amtOver) + feeCost\n\n\t\t\toldScore, exists := score[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\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: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := score[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\tpathProbability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tpathProbability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\tif pathProbability < 1e-300 {\n\t\tpathProbability = 1e-300\n\t}\n\n\treturn rt, pathProbability, 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\tfor node := r.source; node != r.spec.Target; {\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts\n\n\tif r.failures > 0 {\n\t\ttarget += 1 + r.failures/3\n\t}\n\tif r.unknownFails >= 2 {\n\t\ttarget++\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\treturn target\n}\n\nfunc appendCandidateAmount(amounts *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, amt, minimum,\n\tmaximum lnwire.MilliSatoshi) {\n\n\tif amt < minimum {\n\t\tamt = minimum\n\t}\n\tif amt > maximum {\n\t\tamt = maximum\n\t}\n\tif amt <= 0 || seen[amt] {\n\t\treturn\n\t}\n\n\tseen[amt] = true\n\t*amounts = append(*amounts, amt)\n}\n\nfunc candidateRouteSignature(rt *route.Route) uint64 {\n\tconst (\n\t\toffset = uint64(1469598103934665603)\n\t\tprime = uint64(1099511628211)\n\t)\n\n\th := offset\n\tfor _, hop := range rt.Hops {\n\t\tv := hop.ChannelID\n\t\tfor i := 0; i < 8; i++ {\n\t\t\th ^= v & 0xff\n\t\t\th *= prime\n\t\t\tv >>= 8\n\t\t}\n\t}\n\treturn h\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminimum := candidateCeilDiv(amt, partsLeft)\n\n\ttargetParts := r.targetPartCount()\n\tdesiredSlots := uint32(1)\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\n\tdesired := candidateCeilDiv(amt, desiredSlots)\n\tmaximum := amt\n\tif r.retryCap > 0 && r.retryCap < maximum {\n\t\tmaximum = r.retryCap\n\t}\n\tif maximum < minimum {\n\t\tmaximum = minimum\n\t}\n\n\tvar amounts []lnwire.MilliSatoshi\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\tappendCandidateAmount(&amounts, seen, desired, minimum, maximum)\n\tappendCandidateAmount(\n\t\t&amounts, seen, desired+desired/3, minimum, maximum,\n\t)\n\tappendCandidateAmount(\n\t\t&amounts, seen, desired+desired/2, minimum, maximum,\n\t)\n\tappendCandidateAmount(\n\t\t&amounts, seen, desired*2, minimum, maximum,\n\t)\n\tappendCandidateAmount(&amounts, seen, maximum, minimum, maximum)\n\tappendCandidateAmount(\n\t\t&amounts, seen, (desired+minimum)/2, minimum, maximum,\n\t)\n\tappendCandidateAmount(&amounts, seen, minimum, minimum, maximum)\n\n\tvar (\n\t\tbestRoute *route.Route\n\t\tbestUtility = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shard := range amounts {\n\t\trt, probability, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprogress := float64(shard) / float64(minimum)\n\t\tif progress < 1 {\n\t\t\tprogress = 1\n\t\t}\n\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tfeeRatio := 0.0\n\t\tif delivered > 0 && rt.TotalAmount > delivered {\n\t\t\tfeeRatio = float64(rt.TotalAmount-delivered) /\n\t\t\t\tfloat64(delivered)\n\t\t}\n\n\t\tsignature := candidateRouteSignature(rt)\n\t\tutility := math.Log(probability) +\n\t\t\t0.28*math.Log(progress) -\n\t\t\t4.0*feeRatio -\n\t\t\t0.24*r.routeFails[signature]\n\n\t\tif utility > bestUtility {\n\t\t\tbestUtility = utility\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute != nil {\n\t\treturn bestRoute, nil\n\t}\n\tif lastErr != nil {\n\t\treturn nil, lastErr\n\t}\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tb := candidateBeliefStore.beliefs[traversal.key]\n\t\tif b == nil {\n\t\t\tb = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t\t}\n\n\t\tif required > b.lowerOK {\n\t\t\tb.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > b.estimate {\n\t\t\tb.estimate = highEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\n\t\tif b.failAmt > 0 && required >= b.failAmt {\n\t\t\tb.failAmt = 0\n\t\t\tb.failWeight = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tdelete(r.routeFails, candidateRouteSignature(rt))\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif b := candidateBeliefStore.beliefs[key]; b != nil {\n\t\t\tif b.lowerOK > used {\n\t\t\t\tb.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tb.lowerOK = 0\n\t\t\t}\n\t\t\tif b.estimate > used {\n\t\t\t\tb.estimate -= used\n\t\t\t} else {\n\t\t\t\tb.estimate = 0\n\t\t\t}\n\t\t\tif b.failAmt > used {\n\t\t\t\tb.failAmt -= used\n\t\t\t} else {\n\t\t\t\tb.failAmt = 0\n\t\t\t\tb.failWeight = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.failAmt > 0 {\n\t\t\treverseBelief.failAmt += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.failAmt > reverseCapacity {\n\n\t\t\t\treverseBelief.failAmt = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) ([]candidateTraversal, int, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn nil, -1, false\n\t}\n\n\tif source == rt.SourcePubKey {\n\t\treturn traversals, 0, true\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\tindex := i + 1\n\t\tif index >= len(traversals) {\n\t\t\treturn nil, -1, false\n\t\t}\n\t\treturn traversals, index, true\n\t}\n\n\treturn nil, -1, false\n}\n\nfunc (r *candidateRouter) observeLiquidityFailure(\n\ttraversal candidateTraversal, weight float64) {\n\n\tif weight <= 0 || traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tlocal.weight += weight\n\tif local.weight > 4 {\n\t\tlocal.weight = 4\n\t}\n\tr.localFails[traversal.key] = local\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tb := candidateBeliefStore.beliefs[traversal.key]\n\tif b == nil {\n\t\tb = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t}\n\n\tif b.failAmt == 0 || required < b.failAmt {\n\t\tb.failAmt = required\n\t}\n\tb.failWeight += weight\n\tif b.failWeight > 4 {\n\t\tb.failWeight = 4\n\t}\n\n\tif b.failWeight >= 1.55 {\n\t\tfailedEstimate := b.failAmt * 70 / 100\n\t\tif b.estimate == 0 || failedEstimate < b.estimate {\n\t\t\tb.estimate = failedEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversals []candidateTraversal, index int) {\n\n\tfor i, traversal := range traversals {\n\t\tdistance := i - index\n\t\tif distance < 0 {\n\t\t\tdistance = -distance\n\t\t}\n\n\t\tswitch distance {\n\t\tcase 0:\n\t\t\tr.edgePenalty[traversal.key] += 0.90\n\t\t\tr.observeLiquidityFailure(traversal, 0.78)\n\n\t\tcase 1:\n\t\t\tr.edgePenalty[traversal.key] += 0.14\n\t\t\tr.observeLiquidityFailure(traversal, 0.11)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) lowerRetryCap(\n\tamount lnwire.MilliSatoshi, numerator int64) {\n\n\tif amount <= 1 {\n\t\treturn\n\t}\n\n\tcap := amount * lnwire.MilliSatoshi(numerator) / 100\n\tif cap < 1 {\n\t\tcap = 1\n\t}\n\tif r.retryCap == 0 || cap < r.retryCap {\n\t\tr.retryCap = cap\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\tsignature := candidateRouteSignature(rt)\n\tr.routeFails[signature] += 1\n\n\ttraversals := r.routeTraversals(rt)\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.035\n\t}\n\n\tcode := result.Failure.Code()\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tattemptTraversals, index, ok := r.failureIndex(\n\t\t\trt, result.FailureSource,\n\t\t)\n\t\tif ok {\n\t\t\tr.recordAttributedLiquidityFailure(\n\t\t\t\tattemptTraversals, index,\n\t\t\t)\n\t\t\tr.lowerRetryCap(candidateDeliveredAmount(rt), 64)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tr.unknownFails++\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.20\n\t}\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tattemptTraversals, index, ok := r.failureIndex(\n\t\t\trt, result.FailureSource,\n\t\t)\n\t\tif ok {\n\t\t\tattemptTraversals[index].edgePenaltyFallback(r)\n\t\t\tif index > 0 {\n\t\t\t\tr.edgePenalty[\n\t\t\t\t\tattemptTraversals[index-1].key\n\t\t\t\t] += 0.25\n\t\t\t}\n\t\t\tif index+1 < len(attemptTraversals) {\n\t\t\t\tr.edgePenalty[\n\t\t\t\t\tattemptTraversals[index+1].key\n\t\t\t\t] += 0.25\n\t\t\t}\n\t\t}\n\t}\n\n\tr.lowerRetryCap(candidateDeliveredAmount(rt), 76)\n\treturn nil\n}\n\nfunc (t candidateTraversal) edgePenaltyFallback(r *candidateRouter) {\n\tr.edgePenalty[t.key] += 2.25\n}"
}
},
{
"id": 26,
"parent": 0,
"score": 0.6693,
"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 (\n\tfinalCltvDelta = 40\n\triskWeight = 2_500_000.0\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n\tcap lnwire.MilliSatoshi\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupper lnwire.MilliSatoshi\n\n\testimate lnwire.MilliSatoshi\n\tconfidence float64\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tfailVotes uint8\n\tlastFailPath uint64\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity\n}\n\ntype edgeUse struct {\n\tedge *candidateEdge\n\tamount lnwire.MilliSatoshi\n}\n\ntype searchResult struct {\n\trt *route.Route\n\tpath []*candidateEdge\n\tuses []edgeUse\n\tlogProb float64\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\tresult *searchResult\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedgeByKey map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\t// reserved includes successful shards and routes returned but not yet\n\t// reported. This prevents atomic MPP siblings from double-spending a\n\t// corridor.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route][]edgeUse\n\n\t// penalty is payment-local. Unlike shared beliefs, it can react\n\t// aggressively without permanently poisoning later payments.\n\tpenalty map[edgeKey]float64\n\n\t// tried records the smallest failed delivery amount for an exact path.\n\t// The same path remains eligible at a lower amount.\n\ttried map[uint64]lnwire.MilliSatoshi\n\n\tplan []plannedRoute\n\tretryCeiling lnwire.MilliSatoshi\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\tedgeByKey: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route][]edgeUse),\n\t\tpenalty: make(map[edgeKey]float64),\n\t\ttried: make(map[uint64]lnwire.MilliSatoshi),\n\t}\n\n\tfor id, balance := range localBalances {\n\t\tr.localBalances[id] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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\tcapacity := lnwire.NewMSatFromSatoshis(ch.Capacity)\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcap: capacity,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: capacity,\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[edge.to] = append(\n\t\t\t\t\tr.incomingEdges[edge.to], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeByKey[key] = edge\n\n\t\t\t\tsharedBeliefs.Lock()\n\t\t\t\tb := sharedBeliefs.m[key]\n\t\t\t\tsharedBeliefs.Unlock()\n\t\t\t\tr.beliefs[key] = b\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\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// bimodalPrior models a channel as roughly equally likely to be in a\n// depleted mode or a well-funded mode. Tiny HTLCs can pass either mode,\n// while amounts near capacity fall off a logistic cliff.\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\n\tz := (x - 0.90) / 0.045\n\tvar highMode float64\n\tswitch {\n\tcase z > 40:\n\t\thighMode = 0\n\tcase z < -40:\n\t\thighMode = 1\n\tdefault:\n\t\thighMode = 1 / (1 + math.Exp(z))\n\t}\n\n\treturn clampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) totalReserved(key edgeKey,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\ttotal := r.reserved[key]\n\tif extra != nil {\n\t\ttotal += extra[key]\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\treserved := r.totalReserved(edge.key, extra)\n\tneeded := reserved + amt\n\n\tif edge.from == r.source {\n\t\tif needed <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.995\n\t\t}\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(needed, edge.capacity)\n\tb := r.beliefs[edge.key]\n\n\tif b.lowerOK > edge.capacity {\n\t\tb.lowerOK = edge.capacity\n\t}\n\tif b.upper > edge.capacity {\n\t\tb.upper = edge.capacity\n\t}\n\tif b.estimate > edge.capacity {\n\t\tb.estimate = edge.capacity\n\t}\n\n\t// Truthful successes dominate uncertain failures.\n\tif b.lowerOK > 0 && needed <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\tif b.upper > 0 && needed >= b.upper {\n\t\treturn 0.008\n\t}\n\n\tp := prior\n\tif b.confidence > 0 && b.estimate > 0 {\n\t\tscale := math.Max(float64(edge.capacity)*0.10, 1)\n\t\tz := (float64(needed) - float64(b.estimate)) / scale\n\n\t\tvar estimated float64\n\t\tswitch {\n\t\tcase z > 40:\n\t\t\testimated = 0\n\t\tcase z < -40:\n\t\t\testimated = 1\n\t\tdefault:\n\t\t\testimated = 1 / (1 + math.Exp(z))\n\t\t}\n\n\t\tweight := math.Min(0.70, b.confidence*0.70)\n\t\tp = (1-weight)*p + weight*estimated\n\t}\n\n\t// A single uncorroborated blame is only soft evidence.\n\tif b.failVotes == 1 && b.suspectAmt > 0 &&\n\t\tneeded >= b.suspectAmt*3/4 {\n\n\t\tp *= 0.62\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) edgeAvailable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) bool {\n\n\tif !edge.usableHTLC(amt) {\n\t\treturn false\n\t}\n\n\treserved := r.totalReserved(edge.key, extra)\n\tif reserved+amt > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.from == r.source &&\n\t\treserved+amt > r.localBalances[edge.chanID] {\n\n\t\treturn false\n\t}\n\n\treturn true\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamount lnwire.MilliSatoshi\n\tindex int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int { return len(q) }\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\nfunc (q dijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].index = i\n\tq[j].index = j\n}\nfunc (q *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *dijkstraQueue) 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\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int, bias map[edgeKey]float64) (*searchResult, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tbestScore := make(map[route.Vertex]float64)\n\trequired := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tlogProbability := make(map[route.Vertex]float64)\n\n\tbestScore[r.spec.Target] = 0\n\trequired[r.spec.Target] = amt\n\tlogProbability[r.spec.Target] = 0\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamount: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode := item.node\n\n\t\tcurrent, ok := bestScore[node]\n\t\tif !ok || item.score > current+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := required[node]\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tif !r.edgeAvailable(edge, arriving, extra) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, arriving, extra)\n\t\t\tedgeFee := lnwire.MilliSatoshi(0)\n\t\t\tsending := arriving\n\t\t\tif edge.from != r.source {\n\t\t\t\tedgeFee = edge.fee(arriving)\n\t\t\t\tsending += edgeFee\n\t\t\t}\n\n\t\t\tedgeCost := float64(edgeFee) -\n\t\t\t\triskWeight*math.Log(p) + 2_000\n\n\t\t\tedgeCost += r.penalty[edge.key]\n\t\t\tif bias != nil {\n\t\t\t\tedgeCost += bias[edge.key]\n\t\t\t}\n\t\t\tif reuse != nil && reuse[edge.key] > 0 {\n\t\t\t\tedgeCost += float64(reuse[edge.key]) * 4_000_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeCost\n\t\t\told, exists := bestScore[edge.from]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.from] = score\n\t\t\trequired[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\tlogProbability[edge.from] =\n\t\t\t\tlogProbability[node] + math.Log(p)\n\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: score,\n\t\t\t\tamount: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuses := routeUses(rt, path)\n\treturn &searchResult{\n\t\trt: rt,\n\t\tpath: path,\n\t\tuses: uses,\n\t\tlogProb: logProbability[r.source],\n\t\tfee: rt.TotalAmount - amt,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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\tnextEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tnextEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\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}, path, nil\n}\n\nfunc routeUses(rt *route.Route, path []*candidateEdge) []edgeUse {\n\tuses := make([]edgeUse, len(path))\n\tfor i, edge := range path {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tuses[i] = edgeUse{edge: edge, amount: amount}\n\t}\n\treturn uses\n}\n\nfunc deliveredAmount(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 routeHash(rt *route.Route) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, hop := range rt.Hops {\n\t\th ^= hop.ChannelID\n\t\th *= 1099511628211\n\t\tfor _, b := range hop.PubKeyBytes {\n\t\t\th ^= uint64(b)\n\t\t\th *= 1099511628211\n\t\t}\n\t}\n\treturn h\n}\n\nfunc (r *candidateRouter) findFreshRoute(amt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int) (*searchResult, error) {\n\n\tbias := make(map[edgeKey]float64)\n\tvar lastErr error\n\n\tfor n := 0; n < 10; n++ {\n\t\tresult, err := r.findRoute(amt, extra, reuse, bias)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thash := routeHash(result.rt)\n\t\tfailedAt, failed := r.tried[hash]\n\t\tif !failed || amt < failedAt {\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = errors.New(\"all candidate routes already failed\")\n\t\tif len(result.path) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// Exclude a different edge on successive duplicate paths. This\n\t\t// uses only payment-local information and does not turn an\n\t\t// unreadable onion error into a permanent channel accusation.\n\t\tindex := int((hash + uint64(n)) % uint64(len(result.path)))\n\t\tbias[result.path[index].key] += 7_000_000\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no fresh route found\")\n\t}\n\treturn nil, lastErr\n}\n\nfunc (r *candidateRouter) reliableDeliveryLimit(result *searchResult,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tdelivery := deliveredAmount(result.rt)\n\tlimit := delivery * 8\n\tif limit < delivery {\n\t\tlimit = delivery\n\t}\n\n\tfor _, use := range result.uses {\n\t\tedge := use.edge\n\t\treserved := r.totalReserved(edge.key, extra)\n\n\t\tvar safeTotal lnwire.MilliSatoshi\n\t\tif edge.from == r.source {\n\t\t\tsafeTotal = r.localBalances[edge.chanID]\n\t\t} else {\n\t\t\tb := r.beliefs[edge.key]\n\t\t\tsafeTotal = edge.capacity * 78 / 100\n\n\t\t\tif b.estimate > 0 && b.confidence > 0 {\n\t\t\t\tw := math.Min(0.85, b.confidence)\n\t\t\t\tsafeTotal = lnwire.MilliSatoshi(\n\t\t\t\t\t(1-w)*float64(safeTotal) +\n\t\t\t\t\t\tw*float64(b.estimate),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif b.lowerOK > safeTotal {\n\t\t\t\tsafeTotal = b.lowerOK\n\t\t\t}\n\t\t\tif b.upper > 0 && safeTotal >= b.upper {\n\t\t\t\tsafeTotal = b.upper * 9 / 10\n\t\t\t}\n\t\t}\n\n\t\tif safeTotal > edge.capacity {\n\t\t\tsafeTotal = edge.capacity\n\t\t}\n\t\tif safeTotal <= reserved {\n\t\t\treturn 0\n\t\t}\n\n\t\tavailable := safeTotal - reserved\n\t\tedgeLimit := delivery * available / use.amount\n\n\t\tif edge.maxHTLC != 0 && use.amount > 0 {\n\t\t\tpolicyLimit := delivery * edge.maxHTLC / use.amount\n\t\t\tif policyLimit < edgeLimit {\n\t\t\t\tedgeLimit = policyLimit\n\t\t\t}\n\t\t}\n\t\tif edgeLimit < limit {\n\t\t\tlimit = edgeLimit\n\t\t}\n\t}\n\n\treturn limit\n}\n\nfunc addReservations(dst map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tdst[use.edge.key] += use.amount\n\t}\n}\n\nfunc planMetric(results []*searchResult) float64 {\n\tmetric := -0.04 * float64(len(results))\n\tfor _, result := range results {\n\t\tmetric += result.logProb\n\t\tmetric -= float64(result.fee) / 5_000_000\n\t}\n\treturn metric\n}\n\nfunc (r *candidateRouter) buildPlanForParts(amt lnwire.MilliSatoshi,\n\tparts int) ([]*searchResult, bool) {\n\n\tremaining := amt\n\tvirtual := make(map[edgeKey]lnwire.MilliSatoshi)\n\treuse := make(map[edgeKey]int)\n\tresults := make([]*searchResult, 0, parts)\n\n\tfor i := 0; i < parts; i++ {\n\t\tslots := parts - i\n\t\tif remaining <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tshare := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tresult, err := r.findFreshRoute(share, virtual, reuse)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tsafe := r.reliableDeliveryLimit(result, virtual)\n\t\tif safe < share {\n\t\t\tif slots == 1 || safe <= 0 {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tshare = safe\n\t\t\tresult, err = r.findFreshRoute(share, virtual, reuse)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t}\n\n\t\tif share <= 0 || share >= remaining && slots > 1 {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tresults = append(results, result)\n\t\taddReservations(virtual, result.uses)\n\t\tfor _, use := range result.uses {\n\t\t\treuse[use.edge.key]++\n\t\t}\n\t\tremaining -= share\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, false\n\t}\n\treturn results, true\n}\n\n// preparePlan compares a direct attempt with jointly constructed route sets.\n// The set planner accounts for all earlier shards as virtual reservations,\n// so atomic siblings cannot unknowingly depend on the same liquidity twice.\nfunc (r *candidateRouter) preparePlan(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) error {\n\n\tr.plan = nil\n\tif partsLeft == 0 {\n\t\treturn errors.New(\"maximum in-flight parts reached\")\n\t}\n\n\tvar best []*searchResult\n\tbestMetric := math.Inf(-1)\n\n\tdirect, directErr := r.findFreshRoute(amt, nil, nil)\n\tif directErr == nil {\n\t\tbest = []*searchResult{direct}\n\t\tbestMetric = planMetric(best)\n\t}\n\n\tmaxParts := int(partsLeft)\n\tif maxParts > 16 {\n\t\tmaxParts = 16\n\t}\n\n\tfor parts := 2; parts <= maxParts; parts++ {\n\t\tresults, ok := r.buildPlanForParts(amt, parts)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tmetric := planMetric(results)\n\t\tif metric > bestMetric+0.08 {\n\t\t\tbest = results\n\t\t\tbestMetric = metric\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\tif directErr != nil {\n\t\t\treturn directErr\n\t\t}\n\t\treturn errors.New(\"no route set found\")\n\t}\n\n\tr.plan = make([]plannedRoute, len(best))\n\tfor i, result := range best {\n\t\tr.plan[i] = plannedRoute{result: result}\n\t}\n\treturn nil\n}\n\nfunc (r *candidateRouter) issue(result *searchResult) *route.Route {\n\taddReservations(r.reserved, result.uses)\n\tr.issued[result.rt] = result.uses\n\treturn result.rt\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum in-flight parts reached\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 {\n\t\tresult := r.plan[0].result\n\t\tif deliveredAmount(result.rt) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tif err := r.preparePlan(amt, partsLeft); err == nil &&\n\t\tlen(r.plan) > 0 {\n\n\t\tresult := r.plan[0].result\n\t\tr.plan = r.plan[1:]\n\t\treturn r.issue(result), nil\n\t}\n\n\t// If joint planning cannot complete the amount, retain a conservative\n\t// lower-amount escape hatch. It never proposes a shard so small that\n\t// the currently available atomic slots could not finish the payment.\n\tif partsLeft <= 1 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tminShard := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\ttryAmt := amt\n\tif r.retryCeiling > 0 && r.retryCeiling < tryAmt {\n\t\ttryAmt = r.retryCeiling\n\t}\n\tif tryAmt < minShard {\n\t\ttryAmt = minShard\n\t}\n\n\tfor tryAmt >= minShard {\n\t\tresult, err := r.findFreshRoute(tryAmt, nil, nil)\n\t\tif err == nil {\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t\tif tryAmt == minShard {\n\t\t\tbreak\n\t\t}\n\n\t\tnext := tryAmt * 3 / 5\n\t\tif next < minShard {\n\t\t\tnext = minShard\n\t\t}\n\t\tif next == tryAmt {\n\t\t\tbreak\n\t\t}\n\t\ttryAmt = next\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tb liquidityBelief) {\n\n\tr.beliefs[key] = b\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = b\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(use edgeUse,\n\ttotalObserved lnwire.MilliSatoshi) {\n\n\tkey := use.edge.key\n\tb := r.beliefs[key]\n\n\tif totalObserved > b.lowerOK {\n\t\tb.lowerOK = totalObserved\n\t}\n\tif totalObserved > b.estimate {\n\t\tb.estimate = totalObserved\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.35)\n\n\t// A success is truthful and therefore disproves any ceiling at or\n\t// below the amount that just passed.\n\tif b.upper > 0 && totalObserved >= b.upper {\n\t\tb.upper = 0\n\t}\n\tif b.suspectAmt > 0 && totalObserved >= b.suspectAmt {\n\t\tb.suspectAmt = 0\n\t\tb.failVotes = 0\n\t\tb.lastFailPath = 0\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) learnFailure(edge *candidateEdge,\n\ttotalObserved lnwire.MilliSatoshi, pathHash uint64) {\n\n\tkey := edge.key\n\tb := r.beliefs[key]\n\n\tif b.suspectAmt == 0 ||\n\t\ttotalObserved < b.suspectAmt/2 ||\n\t\ttotalObserved > b.suspectAmt*2 {\n\n\t\tb.suspectAmt = totalObserved\n\t\tb.failVotes = 1\n\t\tb.lastFailPath = pathHash\n\t} else {\n\t\tif totalObserved < b.suspectAmt {\n\t\t\tb.suspectAmt = totalObserved\n\t\t}\n\t\t// Repeated blame on the identical route does not corroborate the\n\t\t// reported hop. A second, distinct route does.\n\t\tif pathHash != b.lastFailPath && b.failVotes < 255 {\n\t\t\tb.failVotes++\n\t\t\tb.lastFailPath = pathHash\n\t\t}\n\t}\n\n\tfailedEstimate := totalObserved * 3 / 4\n\tif b.estimate == 0 {\n\t\tb.estimate = failedEstimate\n\t} else {\n\t\tb.estimate = (b.estimate*3 + failedEstimate) / 4\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.15)\n\n\tif b.failVotes >= 2 {\n\t\tif b.upper == 0 || b.suspectAmt < b.upper {\n\t\t\tb.upper = b.suspectAmt\n\t\t}\n\t\tif b.lowerOK >= b.upper {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc releaseReservations(reserved map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tcurrent := reserved[use.edge.key]\n\t\tif current <= use.amount {\n\t\t\tdelete(reserved, use.edge.key)\n\t\t} else {\n\t\t\treserved[use.edge.key] = current - use.amount\n\t\t}\n\t}\n}\n\nfunc findFailureNode(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tuses, ok := r.issued[rt]\n\tif !ok {\n\t\t// The simulator normally returns the same route pointer. Rebuild\n\t\t// the edge uses defensively if it returns an equivalent route.\n\t\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\t\tfrom := rt.SourcePubKey\n\t\tfor _, hop := range rt.Hops {\n\t\t\tvar found *candidateEdge\n\t\t\tfor _, edge := range r.incomingEdges[hop.PubKeyBytes] {\n\t\t\t\tif edge.from == from && edge.chanID == hop.ChannelID {\n\t\t\t\t\tfound = edge\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif found == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tpath = append(path, found)\n\t\t\tfrom = hop.PubKeyBytes\n\t\t}\n\t\tif len(path) == len(rt.Hops) {\n\t\t\tuses = routeUses(rt, path)\n\t\t}\n\t}\n\tdelete(r.issued, rt)\n\n\tif result.Failure == nil {\n\t\tfor _, use := range uses {\n\t\t\t// reserved includes this shard and any prior successful\n\t\t\t// siblings, which is the total liquidity proven to have\n\t\t\t// existed before those reservations.\n\t\t\tr.learnSuccess(use, r.reserved[use.edge.key])\n\t\t}\n\t\treturn nil\n\t}\n\n\thash := routeHash(rt)\n\tdelivered := deliveredAmount(rt)\n\tif old, exists := r.tried[hash]; !exists || delivered < old {\n\t\tr.tried[hash] = delivered\n\t}\n\n\t// Capture total demand before releasing this failed attempt.\n\ttotalDemand := make(map[edgeKey]lnwire.MilliSatoshi, len(uses))\n\tfor _, use := range uses {\n\t\ttotalDemand[use.edge.key] = r.reserved[use.edge.key]\n\t}\n\treleaseReservations(r.reserved, uses)\n\tr.plan = nil\n\n\tnextCeiling := delivered * 3 / 5\n\tif nextCeiling > 0 &&\n\t\t(r.retryCeiling == 0 || nextCeiling < r.retryCeiling) {\n\n\t\tr.retryCeiling = nextCeiling\n\t}\n\n\tfailNode := findFailureNode(rt, result.FailureSource)\n\tif failNode < 0 {\n\t\t// Unreadable attribution is not written to the persistent\n\t\t// channel store. The exact failed route is still avoided at this\n\t\t// amount through r.tried.\n\t\treturn nil\n\t}\n\n\tcode := result.Failure.Code()\n\tisLiquidity := code == lnwire.CodeTemporaryChannelFailure\n\n\t// A node failure refers to its outgoing channel, whose hop index is\n\t// the node index. The target has no outgoing channel.\n\tif failNode >= len(uses) {\n\t\treturn nil\n\t}\n\n\tprimary := failNode\n\tif isLiquidity {\n\t\tuse := uses[primary]\n\t\tr.penalty[use.edge.key] += 5_000_000\n\t\tr.learnFailure(\n\t\t\tuse.edge, totalDemand[use.edge.key], hash,\n\t\t)\n\n\t\t// Shifted attribution is plausible. Adjacent edges receive only\n\t\t// payment-local soft penalties; they never receive persistent\n\t\t// hard bounds without being reported directly and corroborated.\n\t\tif primary > 0 {\n\t\t\tr.penalty[uses[primary-1].edge.key] += 900_000\n\t\t}\n\t\tif primary+1 < len(uses) {\n\t\t\tr.penalty[uses[primary+1].edge.key] += 900_000\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Policy failures cannot safely establish a liquidity ceiling,\n\t// especially when the reported source may have shifted.\n\tr.penalty[uses[primary].edge.key] += 8_000_000\n\tif primary > 0 {\n\t\tr.penalty[uses[primary-1].edge.key] += 1_000_000\n\t}\n\tif primary+1 < len(uses) {\n\t\tr.penalty[uses[primary+1].edge.key] += 1_000_000\n\t}\n\n\treturn nil\n}"
},
"role": "best"
},
{
"id": 27,
"parent": 3,
"score": 0.4464,
"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\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateMinimumShard = lnwire.MilliSatoshi(1_000_000)\n\tcandidateAttemptLimit = uint32(48)\n)\n\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usable(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\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n\n\tstamp uint64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\t// Successful shards consume or hold liquidity for the rest of the\n\t// payment. Accounting for that aggregate use is essential for atomic\n\t// MPP, where sibling shards compete with existing holds.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// These penalties are deliberately payment-local. They diversify\n\t// retries without turning uncertain failure attribution into persistent\n\t// channel knowledge.\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tretryLimit lnwire.MilliSatoshi\n\tattempts uint32\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\tcandidateMemory.Lock()\n\tcandidateMemory.epoch++\n\tepoch := candidateMemory.epoch\n\n\tsnapshot := make(map[candidateEdgeKey]candidateBelief,\n\t\tlen(candidateMemory.beliefs))\n\tfor key, belief := range candidateMemory.beliefs {\n\t\tsnapshot[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: snapshot,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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},\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// The prior reflects a channel whose funds are usually concentrated almost\n// entirely on one side. The low mode explains tiny payments that pass even\n// on a depleted direction, while the high mode has a steep capacity cliff.\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\thighMode := 1 / (1 + math.Exp(18*(x-0.82)))\n\n\treturn candidateClampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) evidenceConfidence(b candidateBelief) float64 {\n\tif b.stamp == 0 || r.epoch <= b.stamp+3 {\n\t\treturn 1\n\t}\n\n\tage := float64(r.epoch - b.stamp - 3)\n\treturn math.Max(0.12, math.Exp(-age/12))\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tavailable := r.localBalances[edge.chanID]\n\t\tif totalAmt <= available {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := candidatePrior(totalAmt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.stamp == 0 {\n\t\treturn prior\n\t}\n\n\tlearned := prior\n\n\tswitch {\n\tcase belief.lowerOK > 0 && totalAmt <= belief.lowerOK:\n\t\tlearned = 0.995\n\n\tcase belief.upperFail > 0 && totalAmt >= belief.upperFail:\n\t\tlearned = 0.008\n\n\tcase belief.lowerOK > 0 && belief.upperFail > belief.lowerOK:\n\t\tspan := float64(belief.upperFail - belief.lowerOK)\n\t\tpos := float64(totalAmt-belief.lowerOK) / span\n\t\tpos = math.Max(0, math.Min(1, pos))\n\t\tlearned = 0.995*(1-pos) + 0.008*pos\n\n\tcase belief.lowerOK > 0 && totalAmt > belief.lowerOK:\n\t\tratio := float64(belief.lowerOK) / float64(totalAmt)\n\t\tlearned = prior + (0.995-prior)*ratio*ratio\n\n\tcase belief.upperFail > 0 && totalAmt < belief.upperFail:\n\t\tratio := float64(totalAmt) / float64(belief.upperFail)\n\t\tlearned = prior * (1 - 0.75*ratio*ratio)\n\t}\n\n\t// A single plausible-but-unconfirmed blame remains a soft hint.\n\tif belief.upperFail == 0 && belief.suspectAmt > 0 &&\n\t\ttotalAmt >= belief.suspectAmt {\n\n\t\tlearned *= 0.58\n\t}\n\n\tconfidence := r.evidenceConfidence(belief)\n\tp := confidence*learned + (1-confidence)*prior\n\n\treturn candidateClampProbability(p)\n}\n\nfunc (r *candidateRouter) hardFailed(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) bool {\n\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.upperFail == 0 {\n\t\treturn false\n\t}\n\n\treturn r.evidenceConfidence(belief) >= 0.72 &&\n\t\ttotalAmt >= belief.upperFail\n}\n\ntype candidateItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype candidateQueue []*candidateItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype candidatePath struct {\n\troute *route.Route\n\tedges []*candidateEdge\n\tprobability float64\n\tpressure float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tamounts := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tamounts[r.spec.Target] = amt\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateItem)\n\t\tbestScore, ok := scores[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amounts[item.node]\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usable(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := arriving + r.reserved[edge.key]\n\t\t\tif totalLiquidity < arriving ||\n\t\t\t\ttotalLiquidity > edge.capacity {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source {\n\t\t\t\tif totalLiquidity >\n\t\t\t\t\tr.localBalances[edge.chanID] {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else if r.hardFailed(edge, totalLiquidity) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, totalLiquidity)\n\n\t\t\t// Reliability dominates. The small hop charge discourages the\n\t\t\t// extremely long routes that compound many bimodal risks.\n\t\t\tedgeCost := -math.Log(probability) + 0.06\n\t\t\tif arriving > 0 {\n\t\t\t\tedgeCost += 3 * float64(fee) /\n\t\t\t\t\tfloat64(arriving)\n\t\t\t}\n\t\t\tedgeCost += r.edgePenalty[edge.key]\n\n\t\t\tnewScore := item.score + edgeCost\n\t\t\toldScore, visited := scores[edge.from]\n\t\t\tif visited && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.from] = newScore\n\t\t\tamounts[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &candidateItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tpressure := 0.0\n\n\tfor i, edge := range path {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\ttotalLiquidity := amtOver + r.reserved[edge.key]\n\n\t\tprobability *= r.probability(edge, totalLiquidity)\n\n\t\tdenominator := edge.capacity\n\t\tif edge.from == r.source {\n\t\t\tdenominator = r.localBalances[edge.chanID]\n\t\t} else if belief, ok := r.beliefs[edge.key]; ok &&\n\t\t\tbelief.upperFail > 0 &&\n\t\t\tr.evidenceConfidence(belief) >= 0.72 &&\n\t\t\tbelief.upperFail < denominator {\n\n\t\t\tdenominator = belief.upperFail\n\t\t}\n\n\t\tif denominator > 0 {\n\t\t\tratio := float64(totalLiquidity) /\n\t\t\t\tfloat64(denominator)\n\t\t\tif ratio > pressure {\n\t\t\t\tpressure = ratio\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &candidatePath{\n\t\troute: rt,\n\t\tedges: path,\n\t\tprobability: probability,\n\t\tpressure: pressure,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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 := 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.to,\n\t\t\tChannelID: edge.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}, path, nil\n}\n\nfunc candidateCeilDiv(a lnwire.MilliSatoshi,\n\tb uint32) lnwire.MilliSatoshi {\n\n\tif b <= 1 {\n\t\treturn a\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(b)\n\treturn (a + divisor - 1) / divisor\n}\n\nfunc candidateAddAmount(amounts *[]lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, amt,\n\tremaining lnwire.MilliSatoshi) {\n\n\tif amt < candidateMinimumShard {\n\t\tamt = candidateMinimumShard\n\t}\n\tif amt > remaining {\n\t\tamt = remaining\n\t}\n\tif amt <= 0 || seen[amt] {\n\t\treturn\n\t}\n\n\tseen[amt] = true\n\t*amounts = append(*amounts, amt)\n}\n\n// RequestRoute compares several feasible shard sizes rather than discovering\n// a split through a long halving ladder. The score rewards reliable progress,\n// allowing unequal corridors to receive unequal shares while keeping shards\n// near the size required to finish within MaxParts.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\tif r.attempts >= candidateAttemptLimit {\n\t\treturn nil, errors.New(\"route portfolio exhausted\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > inFlightHtlcs {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tif partsLeft <= 1 {\n\t\tpath, err := r.findRoute(amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn path.route, nil\n\t}\n\n\tfull, fullErr := r.findRoute(amt)\n\tif r.attempts == 0 && fullErr == nil {\n\t\tif full.probability >= 0.68 ||\n\t\t\tfull.pressure < 0.28 ||\n\t\t\t(amt < 50_000_000 &&\n\t\t\t\tfull.probability >= 0.25) {\n\n\t\t\treturn full.route, nil\n\t\t}\n\t}\n\n\taverage := candidateCeilDiv(amt, partsLeft)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tvar amounts []lnwire.MilliSatoshi\n\n\t// A broad route gets more than the average share. A narrow but reliable\n\t// route may get less, with later parts taking a correspondingly larger\n\t// share. This discrete portfolio captures most of the benefit of joint\n\t// route/amount planning without probing every possible split.\n\tcandidateAddAmount(&amounts, seen, average, amt)\n\tcandidateAddAmount(\n\t\t&amounts, seen,\n\t\tcandidateCeilDiv(2*amt, partsLeft+1), amt,\n\t)\n\tcandidateAddAmount(\n\t\t&amounts, seen, average*3/2, amt,\n\t)\n\tcandidateAddAmount(&amounts, seen, amt/2, amt)\n\n\tif r.retryLimit > 0 {\n\t\tcandidateAddAmount(\n\t\t\t&amounts, seen, r.retryLimit, amt,\n\t\t)\n\t}\n\n\t// A below-average shard is useful when one narrow corridor complements\n\t// a larger one. It is not the default, which prevents successful dust\n\t// shards from consuming all available part slots.\n\tif r.attempts > 1 {\n\t\tcandidateAddAmount(\n\t\t\t&amounts, seen, average*3/4, amt,\n\t\t)\n\t}\n\tif r.attempts > 5 {\n\t\tcandidateAddAmount(\n\t\t\t&amounts, seen, average/2, amt,\n\t\t)\n\t}\n\n\t// An alternate full route remains useful after an uncertain or shifted\n\t// failure, but it competes with planned shards instead of being retried\n\t// unconditionally.\n\tcandidateAddAmount(&amounts, seen, amt, amt)\n\n\tvar (\n\t\tbest *candidatePath\n\t\tbestScore = math.Inf(-1)\n\t\tlastErr error\n\t)\n\n\tfor _, shardAmt := range amounts {\n\t\tpath, err := r.findRoute(shardAmt)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tprogress := float64(shardAmt) / float64(average)\n\t\tif progress < 0.01 {\n\t\t\tprogress = 0.01\n\t\t}\n\n\t\tscore := math.Log(path.probability)\n\t\tscore += 0.34 * math.Log(progress)\n\t\tscore -= 0.18 * path.pressure\n\n\t\tdelivered := path.route.Hops[\n\t\t\tlen(path.route.Hops)-1].AmtToForward\n\t\tfee := path.route.TotalAmount - delivered\n\t\tif delivered > 0 && fee > 0 {\n\t\t\tfeePPM := 1_000_000 * float64(fee) /\n\t\t\t\tfloat64(delivered)\n\t\t\tscore -= feePPM / 20_000\n\t\t}\n\n\t\tif r.retryLimit > 0 && shardAmt > r.retryLimit {\n\t\t\tratio := float64(shardAmt) /\n\t\t\t\tfloat64(r.retryLimit)\n\t\t\tscore -= 0.22 * math.Log(ratio)\n\t\t}\n\n\t\tif r.attempts > 0 && shardAmt == amt {\n\t\t\tscore -= 0.20\n\t\t}\n\n\t\tif score > bestScore {\n\t\t\tbestScore = score\n\t\t\tbest = path\n\t\t}\n\t}\n\n\tif best != nil {\n\t\treturn best.route, nil\n\t}\n\tif lastErr != nil {\n\t\treturn nil, lastErr\n\t}\n\tif fullErr != nil {\n\t\treturn nil, fullErr\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tedgeIndex int) lnwire.MilliSatoshi {\n\n\tif edgeIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\treturn rt.Hops[edgeIndex-1].AmtToForward\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tedgeIndex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif edgeIndex > 0 {\n\t\tfrom = rt.Hops[edgeIndex-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[edgeIndex].ChannelID,\n\t\tfrom: from,\n\t}\n}\n\nfunc candidateFailureEdge(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc candidateIsLiquidityFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc candidateIsPolicyFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(\n\tkey candidateEdgeKey, belief candidateBelief) {\n\n\tbelief.stamp = r.epoch\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tcandidateMemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\tif totalAmt > belief.lowerOK {\n\t\tbelief.lowerOK = totalAmt\n\t}\n\n\t// Success attribution is always truthful.\n\tif belief.upperFail > 0 && totalAmt >= belief.upperFail {\n\t\tbelief.upperFail = 0\n\t}\n\tif belief.suspectAmt > 0 && totalAmt >= belief.suspectAmt {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[key]\n\n\t// Do not combine widely separated observations into corroboration in a\n\t// moving network.\n\tif belief.stamp > 0 && r.epoch > belief.stamp+6 {\n\t\tbelief.suspectAmt = 0\n\t\tbelief.suspectVotes = 0\n\t}\n\n\tcorroborates := false\n\tif belief.suspectAmt > 0 {\n\t\tlow := belief.suspectAmt * 60 / 100\n\t\thigh := belief.suspectAmt * 150 / 100\n\t\tcorroborates = totalAmt >= low && totalAmt <= high\n\t}\n\n\tif corroborates {\n\t\tif belief.suspectVotes < 255 {\n\t\t\tbelief.suspectVotes++\n\t\t}\n\t\tif totalAmt < belief.suspectAmt {\n\t\t\tbelief.suspectAmt = totalAmt\n\t\t}\n\t} else {\n\t\tbelief.suspectAmt = totalAmt\n\t\tbelief.suspectVotes = 1\n\t}\n\n\trequiredVotes := uint8(2)\n\tif belief.lowerOK > 0 && totalAmt <= belief.lowerOK {\n\t\trequiredVotes = 3\n\t}\n\n\tif belief.suspectVotes >= requiredVotes {\n\t\tif totalAmt <= belief.lowerOK {\n\t\t\tbelief.lowerOK = 0\n\t\t}\n\n\t\tif belief.upperFail == 0 ||\n\t\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\t\tbelief.upperFail = belief.suspectAmt\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) addPenalty(key candidateEdgeKey,\n\tincrement, maximum float64) {\n\n\tr.edgePenalty[key] = math.Min(\n\t\tmaximum, r.edgePenalty[key]+increment,\n\t)\n}\n\n// ReportAttempt preserves truthful success bounds, quarantines liquidity\n// blame until corroborated, and uses only reversible per-payment penalties\n// for unreadable or potentially shifted failures.\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tr.attempts++\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, totalAmt)\n\t\t\tr.reserved[key] += amtOver\n\n\t\t\t// A successful corridor is known-good, but atomic siblings\n\t\t\t// should still seek independent capacity where available.\n\t\t\tr.addPenalty(key, 0.48, 4)\n\t\t}\n\n\t\tr.retryLimit = 0\n\t\treturn nil\n\t}\n\n\t// The route-level fact that something failed is reliable even when its\n\t// source is not. This moderate penalty quickly avoids exact retry loops\n\t// but does not persist beyond the payment.\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\tr.addPenalty(key, 0.22, 8)\n\t}\n\n\tfailIndex := candidateFailureEdge(\n\t\trt, result.FailureSource,\n\t)\n\tattributed := failIndex >= 0 && failIndex < len(rt.Hops)\n\n\tdelivered := rt.Hops[len(rt.Hops)-1].AmtToForward\n\n\tif candidateIsLiquidityFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tamtOver := candidateRouteAmount(rt, failIndex)\n\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\tr.recordFailure(key, totalAmt)\n\t\tr.addPenalty(key, 1.15, 10)\n\n\t\t// Shifted blame is normally adjacent to the real failure. Small\n\t\t// neighbor penalties hedge against it without writing false bounds.\n\t\tif failIndex > 0 {\n\t\t\tneighbor := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex-1,\n\t\t\t)\n\t\t\tr.addPenalty(neighbor, 0.34, 6)\n\t\t}\n\t\tif failIndex+1 < len(rt.Hops) {\n\t\t\tneighbor := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex+1,\n\t\t\t)\n\t\t\tr.addPenalty(neighbor, 0.34, 6)\n\t\t}\n\n\t\tlowerRetry := delivered * 72 / 100\n\t\tif lowerRetry < candidateMinimumShard {\n\t\t\tlowerRetry = candidateMinimumShard\n\t\t}\n\t\tr.retryLimit = lowerRetry\n\n\t\treturn nil\n\t}\n\n\tif candidateIsPolicyFailure(result.Failure) && attributed {\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tr.addPenalty(key, 2.0, 10)\n\n\t\tif failIndex > 0 {\n\t\t\tneighbor := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex-1,\n\t\t\t)\n\t\t\tr.addPenalty(neighbor, 0.25, 5)\n\t\t}\n\t\tif failIndex+1 < len(rt.Hops) {\n\t\t\tneighbor := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex+1,\n\t\t\t)\n\t\t\tr.addPenalty(neighbor, 0.25, 5)\n\t\t}\n\n\t\tr.retryLimit = delivered\n\t\treturn nil\n\t}\n\n\t// An unreadable failure supplies no channel-level evidence. Keep the\n\t// amount available to the route-set planner and rely on route diversity.\n\tr.retryLimit = delivered\n\treturn nil\n}"
}
},
{
"id": 28,
"parent": 6,
"score": 0.1714,
"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\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskWeight = 3_000_000.0\n\tmaxLabels = 10\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n\tcap lnwire.MilliSatoshi\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupper lnwire.MilliSatoshi\n\n\testimate lnwire.MilliSatoshi\n\tconfidence float64\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tfailVotes uint8\n\tlastFailPath uint64\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype edgeUse struct {\n\tedge *candidateEdge\n\tamount lnwire.MilliSatoshi\n}\n\ntype searchResult struct {\n\trt *route.Route\n\tpath []*candidateEdge\n\tuses []edgeUse\n\tlogProb float64\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\tresult *searchResult\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\tbeliefs map[edgeKey]liquidityBelief\n\n\t// reserved includes every issued shard that has not failed. held only\n\t// includes shards whose success has been confirmed.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\theld map[edgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route][]edgeUse\n\n\t// Payment-local penalties react quickly but never poison later\n\t// payments.\n\tpenalty map[edgeKey]float64\n\n\t// For an exact path, this is the smallest delivery amount known to\n\t// have failed. Lower amounts remain eligible.\n\ttried map[uint64]lnwire.MilliSatoshi\n\n\tplan []plannedRoute\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: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\theld: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route][]edgeUse),\n\t\tpenalty: make(map[edgeKey]float64),\n\t\ttried: make(map[uint64]lnwire.MilliSatoshi),\n\t}\n\n\tfor id, balance := range localBalances {\n\t\tr.localBalances[id] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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\tcapacity := lnwire.NewMSatFromSatoshis(ch.Capacity)\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcap: capacity,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: capacity,\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\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\n\n\t\t\t\tsharedBeliefs.Lock()\n\t\t\t\tr.beliefs[key] = sharedBeliefs.m[key]\n\t\t\t\tsharedBeliefs.Unlock()\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\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\tz := (x - 0.90) / 0.045\n\n\tvar highMode float64\n\tswitch {\n\tcase z > 40:\n\t\thighMode = 0\n\tcase z < -40:\n\t\thighMode = 1\n\tdefault:\n\t\thighMode = 1 / (1 + math.Exp(z))\n\t}\n\n\treturn clampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) totalReserved(key edgeKey,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\ttotal := r.reserved[key]\n\tif extra != nil {\n\t\ttotal += extra[key]\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) edgeAvailable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) bool {\n\n\tif !edge.usableHTLC(amt) {\n\t\treturn false\n\t}\n\n\tneeded := r.totalReserved(edge.key, extra) + amt\n\tif needed > edge.capacity {\n\t\treturn false\n\t}\n\tif edge.from == r.source &&\n\t\tneeded > r.localBalances[edge.chanID] {\n\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\tneeded := r.totalReserved(edge.key, extra) + amt\n\tif edge.from == r.source {\n\t\tif needed <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.995\n\t\t}\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(needed, edge.capacity)\n\tb := r.beliefs[edge.key]\n\n\tif b.lowerOK > edge.capacity {\n\t\tb.lowerOK = edge.capacity\n\t}\n\tif b.upper > edge.capacity {\n\t\tb.upper = edge.capacity\n\t}\n\tif b.estimate > edge.capacity {\n\t\tb.estimate = edge.capacity\n\t}\n\n\t// Settlements are always truthful, unlike failure attribution.\n\tif b.lowerOK > 0 && needed <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\tif b.upper > 0 && needed >= b.upper {\n\t\treturn 0.006\n\t}\n\n\tp := prior\n\tif b.estimate > 0 && b.confidence > 0 {\n\t\tscale := math.Max(float64(edge.capacity)*0.09, 1)\n\t\tz := (float64(needed) - float64(b.estimate)) / scale\n\n\t\tvar estimateP float64\n\t\tswitch {\n\t\tcase z > 40:\n\t\t\testimateP = 0\n\t\tcase z < -40:\n\t\t\testimateP = 1\n\t\tdefault:\n\t\t\testimateP = 1 / (1 + math.Exp(z))\n\t\t}\n\n\t\tweight := math.Min(0.72, b.confidence*0.72)\n\t\tp = (1-weight)*p + weight*estimateP\n\t}\n\n\t// One report is a suspicion, two reports are useful soft evidence,\n\t// and only three independent route contexts establish a hard bound.\n\tif b.suspectAmt > 0 && needed >= b.suspectAmt*3/4 {\n\t\tswitch b.failVotes {\n\t\tcase 1:\n\t\t\tp *= 0.72\n\t\tcase 2:\n\t\t\tp *= 0.42\n\t\t}\n\t}\n\n\treturn clampProbability(p)\n}\n\ntype pathLabel struct {\n\tnode route.Vertex\n\tscore float64\n\tamount lnwire.MilliSatoshi\n\tlogProb float64\n\n\tedge *candidateEdge\n\tnext *pathLabel\n\n\tactive bool\n\tindex int\n}\n\ntype labelQueue []*pathLabel\n\nfunc (q labelQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q labelQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q labelQueue) 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 *labelQueue) Push(x any) {\n\titem := x.(*pathLabel)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *labelQueue) 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 addLabel(labels map[route.Vertex][]*pathLabel,\n\tlabel *pathLabel) bool {\n\n\texisting := labels[label.node]\n\tfor _, old := range existing {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif old.score <= label.score+0.0001 &&\n\t\t\told.amount <= label.amount {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor _, old := range existing {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif label.score <= old.score+0.0001 &&\n\t\t\tlabel.amount <= old.amount {\n\n\t\t\told.active = false\n\t\t}\n\t}\n\n\tlabel.active = true\n\tlabels[label.node] = append(existing, label)\n\n\tactive := make([]*pathLabel, 0, len(labels[label.node]))\n\tfor _, item := range labels[label.node] {\n\t\tif item.active {\n\t\t\tactive = append(active, item)\n\t\t}\n\t}\n\tif len(active) <= maxLabels {\n\t\treturn true\n\t}\n\n\tbestScore := active[0]\n\tminAmount := active[0]\n\tfor _, item := range active[1:] {\n\t\tif item.score < bestScore.score {\n\t\t\tbestScore = item\n\t\t}\n\t\tif item.amount < minAmount.amount {\n\t\t\tminAmount = item\n\t\t}\n\t}\n\n\tvar victim *pathLabel\n\tfor _, item := range active {\n\t\tif item == bestScore || item == minAmount {\n\t\t\tcontinue\n\t\t}\n\t\tif victim == nil || item.score > victim.score {\n\t\t\tvictim = item\n\t\t}\n\t}\n\tif victim == nil {\n\t\tif bestScore != minAmount {\n\t\t\tvictim = label\n\t\t} else {\n\t\t\tvictim = active[len(active)-1]\n\t\t}\n\t}\n\n\tvictim.active = false\n\treturn victim != label\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int,\n\tbias map[edgeKey]float64) (*searchResult, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tlabels := make(map[route.Vertex][]*pathLabel)\n\tstart := &pathLabel{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tactive: true,\n\t\tlogProb: 0,\n\t}\n\tlabels[start.node] = []*pathLabel{start}\n\n\tpq := &labelQueue{}\n\theap.Push(pq, start)\n\n\tvar sourceLabel *pathLabel\n\tfor pq.Len() > 0 {\n\t\tcurrent := heap.Pop(pq).(*pathLabel)\n\t\tif !current.active {\n\t\t\tcontinue\n\t\t}\n\t\tif current.node == r.source {\n\t\t\tsourceLabel = current\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[current.node] {\n\t\t\tif !r.edgeAvailable(edge, current.amount, extra) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, current.amount, extra)\n\t\t\tedgeFee := lnwire.MilliSatoshi(0)\n\t\t\tsending := current.amount\n\t\t\tif edge.from != r.source {\n\t\t\t\tedgeFee = edge.fee(current.amount)\n\t\t\t\tif edgeFee < 0 ||\n\t\t\t\t\tsending > lnwire.MilliSatoshi(math.MaxInt64)-edgeFee {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += edgeFee\n\t\t\t}\n\n\t\t\tedgeCost := float64(edgeFee) -\n\t\t\t\triskWeight*math.Log(p) + 8_000\n\t\t\tedgeCost += r.penalty[edge.key]\n\t\t\tif bias != nil {\n\t\t\t\tedgeCost += bias[edge.key]\n\t\t\t}\n\t\t\tif reuse != nil && reuse[edge.key] > 0 {\n\t\t\t\tedgeCost += float64(reuse[edge.key]) * 3_200_000\n\t\t\t}\n\n\t\t\tnext := &pathLabel{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: current.score + edgeCost,\n\t\t\t\tamount: sending,\n\t\t\t\tlogProb: current.logProb + math.Log(p),\n\t\t\t\tedge: edge,\n\t\t\t\tnext: current,\n\t\t\t}\n\t\t\tif addLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tpath := make([]*candidateEdge, 0)\n\tfor label := sourceLabel; label.edge != nil; label = label.next {\n\t\tpath = append(path, label.edge)\n\t}\n\trt, err := r.buildRoute(amt, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &searchResult{\n\t\trt: rt,\n\t\tpath: path,\n\t\tuses: routeUses(rt, path),\n\t\tlogProb: sourceLabel.logProb,\n\t\tfee: rt.TotalAmount - amt,\n\t}, 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\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\tlast := len(path) - 1\n\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tnextEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tnextEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\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 routeUses(rt *route.Route, path []*candidateEdge) []edgeUse {\n\tuses := make([]edgeUse, len(path))\n\tfor i, edge := range path {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tuses[i] = edgeUse{edge: edge, amount: amount}\n\t}\n\treturn uses\n}\n\nfunc deliveredAmount(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 routeHash(rt *route.Route) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, hop := range rt.Hops {\n\t\th ^= hop.ChannelID\n\t\th *= 1099511628211\n\t\tfor _, b := range hop.PubKeyBytes {\n\t\t\th ^= uint64(b)\n\t\t\th *= 1099511628211\n\t\t}\n\t}\n\treturn h\n}\n\nfunc (r *candidateRouter) findFreshRoute(amt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int) (*searchResult, error) {\n\n\tbias := make(map[edgeKey]float64)\n\tvar lastErr error\n\n\tfor n := 0; n < 14; n++ {\n\t\tresult, err := r.findRoute(amt, extra, reuse, bias)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thash := routeHash(result.rt)\n\t\tfailedAt, failed := r.tried[hash]\n\t\tif !failed || amt < failedAt {\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = errors.New(\"candidate paths already failed\")\n\t\tif len(result.path) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tindex := int((hash + uint64(n)*7) %\n\t\t\tuint64(len(result.path)))\n\t\tbias[result.path[index].key] += 9_000_000\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no fresh route found\")\n\t}\n\treturn nil, lastErr\n}\n\nfunc addReservations(dst map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tdst[use.edge.key] += use.amount\n\t}\n}\n\nfunc copyReservations(src map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, value := range src {\n\t\tdst[key] = value\n\t}\n\treturn dst\n}\n\nfunc copyReuse(src map[edgeKey]int) map[edgeKey]int {\n\tdst := make(map[edgeKey]int, len(src))\n\tfor key, value := range src {\n\t\tdst[key] = value\n\t}\n\treturn dst\n}\n\nfunc planMetric(results []*searchResult) float64 {\n\tmetric := -0.06 * float64(len(results))\n\tfor _, result := range results {\n\t\tmetric += result.logProb\n\t\tmetric -= float64(result.fee) / 10_000_000\n\t}\n\treturn metric\n}\n\nfunc uniqueAmounts(remaining lnwire.MilliSatoshi,\n\tslots int) []lnwire.MilliSatoshi {\n\n\taverage := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\tlnwire.MilliSatoshi(slots)\n\n\tratios := [][2]int64{\n\t\t{1, 1},\n\t\t{2, 3},\n\t\t{4, 3},\n\t\t{1, 2},\n\t\t{3, 2},\n\t\t{2, 5},\n\t\t{5, 3},\n\t\t{1, 4},\n\t\t{2, 1},\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tamounts := make([]lnwire.MilliSatoshi, 0, len(ratios))\n\tfor _, ratio := range ratios {\n\t\tamount := average * lnwire.MilliSatoshi(ratio[0]) /\n\t\t\tlnwire.MilliSatoshi(ratio[1])\n\t\tif amount <= 0 {\n\t\t\tamount = 1\n\t\t}\n\t\tif amount >= remaining {\n\t\t\tcontinue\n\t\t}\n\t\tif remaining-amount <\n\t\t\tlnwire.MilliSatoshi(slots-1) {\n\n\t\t\tcontinue\n\t\t}\n\t\tif !seen[amount] {\n\t\t\tseen[amount] = true\n\t\t\tamounts = append(amounts, amount)\n\t\t}\n\t}\n\n\treturn amounts\n}\n\nfunc (r *candidateRouter) searchPlan(\n\tremaining lnwire.MilliSatoshi, slots int,\n\tvirtual map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int, budget *int) ([]*searchResult, bool) {\n\n\tif remaining <= 0 || slots <= 0 || *budget <= 0 {\n\t\treturn nil, false\n\t}\n\n\tif slots == 1 {\n\t\t*budget--\n\t\tresult, err := r.findFreshRoute(remaining, virtual, reuse)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\t\treturn []*searchResult{result}, true\n\t}\n\n\tvar best []*searchResult\n\tbestMetric := math.Inf(-1)\n\n\tfor _, amount := range uniqueAmounts(remaining, slots) {\n\t\tif *budget <= 0 {\n\t\t\tbreak\n\t\t}\n\t\t*budget--\n\n\t\tresult, err := r.findFreshRoute(amount, virtual, reuse)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tnextVirtual := copyReservations(virtual)\n\t\taddReservations(nextVirtual, result.uses)\n\n\t\tnextReuse := copyReuse(reuse)\n\t\tfor _, use := range result.uses {\n\t\t\tnextReuse[use.edge.key]++\n\t\t}\n\n\t\ttail, ok := r.searchPlan(\n\t\t\tremaining-amount, slots-1,\n\t\t\tnextVirtual, nextReuse, budget,\n\t\t)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tcandidate := make([]*searchResult, 1, len(tail)+1)\n\t\tcandidate[0] = result\n\t\tcandidate = append(candidate, tail...)\n\n\t\tmetric := planMetric(candidate)\n\t\tif metric > bestMetric {\n\t\t\tbestMetric = metric\n\t\t\tbest = candidate\n\t\t}\n\t}\n\n\treturn best, len(best) != 0\n}\n\nfunc (r *candidateRouter) buildPlanForParts(\n\tamt lnwire.MilliSatoshi, parts int) ([]*searchResult, bool) {\n\n\tbudget := 36 + parts*10\n\treturn r.searchPlan(\n\t\tamt, parts,\n\t\tmake(map[edgeKey]lnwire.MilliSatoshi),\n\t\tmake(map[edgeKey]int), &budget,\n\t)\n}\n\nfunc appendUniquePart(parts []int, value, limit int) []int {\n\tif value < 2 || value > limit {\n\t\treturn parts\n\t}\n\tfor _, existing := range parts {\n\t\tif existing == value {\n\t\t\treturn parts\n\t\t}\n\t}\n\treturn append(parts, value)\n}\n\nfunc (r *candidateRouter) preparePlan(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) error {\n\n\tr.plan = nil\n\tif partsLeft == 0 {\n\t\treturn errors.New(\"maximum in-flight parts reached\")\n\t}\n\n\tvar best []*searchResult\n\tbestMetric := math.Inf(-1)\n\tdirect, directErr := r.findFreshRoute(amt, nil, nil)\n\tif directErr == nil {\n\t\tbest = []*searchResult{direct}\n\t\tbestMetric = planMetric(best)\n\t}\n\n\tlimit := partsLeft\n\tif limit > 32 {\n\t\tlimit = 32\n\t}\n\tmaxParts := int(limit)\n\n\tpartCounts := make([]int, 0, 16)\n\tfor parts := 2; parts <= maxParts && parts <= 10; parts++ {\n\t\tpartCounts = append(partCounts, parts)\n\t}\n\tfor _, parts := range []int{12, 16, 20, 24, 32, maxParts} {\n\t\tpartCounts = appendUniquePart(partCounts, parts, maxParts)\n\t}\n\n\tfor _, parts := range partCounts {\n\t\tresults, ok := r.buildPlanForParts(amt, parts)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tmetric := planMetric(results)\n\t\tif metric > bestMetric+0.04 {\n\t\t\tbestMetric = metric\n\t\t\tbest = results\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\tif directErr != nil {\n\t\t\treturn directErr\n\t\t}\n\t\treturn errors.New(\"no route set found\")\n\t}\n\n\tr.plan = make([]plannedRoute, len(best))\n\tfor i, result := range best {\n\t\tr.plan[i] = plannedRoute{result: result}\n\t}\n\treturn nil\n}\n\nfunc (r *candidateRouter) issue(result *searchResult) *route.Route {\n\taddReservations(r.reserved, result.uses)\n\tr.issued[result.rt] = result.uses\n\treturn result.rt\n}\n\nfunc fallbackAmounts(amt, minimum lnwire.MilliSatoshi,\n\tallowBelow bool) []lnwire.MilliSatoshi {\n\n\tif minimum <= 0 {\n\t\tminimum = 1\n\t}\n\n\traw := []lnwire.MilliSatoshi{\n\t\tamt,\n\t\tamt * 4 / 5,\n\t\tamt * 2 / 3,\n\t\tamt * 3 / 5,\n\t\tamt / 2,\n\t\tminimum,\n\t}\n\tif allowBelow {\n\t\traw = append(raw, minimum*2/3, minimum/2)\n\t}\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tresult := make([]lnwire.MilliSatoshi, 0, len(raw))\n\tfor _, amount := range raw {\n\t\tif amount <= 0 || amount > amt {\n\t\t\tcontinue\n\t\t}\n\t\tif !allowBelow && amount < minimum {\n\t\t\tcontinue\n\t\t}\n\t\tif !seen[amount] {\n\t\t\tseen[amount] = true\n\t\t\tresult = append(result, amount)\n\t\t}\n\t}\n\treturn result\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum in-flight parts reached\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 {\n\t\tresult := r.plan[0].result\n\t\tif deliveredAmount(result.rt) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tif err := r.preparePlan(amt, partsLeft); err == nil &&\n\t\tlen(r.plan) > 0 {\n\n\t\tresult := r.plan[0].result\n\t\tr.plan = r.plan[1:]\n\t\treturn r.issue(result), nil\n\t}\n\n\tminimum := amt\n\tif partsLeft > 1 {\n\t\tminimum = (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\t\tlnwire.MilliSatoshi(partsLeft)\n\t}\n\n\tfor _, tryAmt := range fallbackAmounts(\n\t\tamt, minimum, partsLeft > 2,\n\t) {\n\t\tresult, err := r.findFreshRoute(tryAmt, nil, nil)\n\t\tif err == nil {\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tb liquidityBelief) {\n\n\tr.beliefs[key] = b\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = b\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(use edgeUse,\n\ttotalObserved lnwire.MilliSatoshi) {\n\n\tkey := use.edge.key\n\tb := r.beliefs[key]\n\n\tif totalObserved > b.lowerOK {\n\t\tb.lowerOK = totalObserved\n\t}\n\tif b.estimate == 0 || totalObserved > b.estimate {\n\t\tb.estimate = totalObserved\n\t} else {\n\t\tb.estimate = (b.estimate*3 + totalObserved) / 4\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.40)\n\n\tif b.upper > 0 && totalObserved >= b.upper {\n\t\tb.upper = 0\n\t}\n\tif b.suspectAmt > 0 && totalObserved >= b.suspectAmt {\n\t\tb.suspectAmt = 0\n\t\tb.failVotes = 0\n\t\tb.lastFailPath = 0\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) learnFailure(edge *candidateEdge,\n\ttotalObserved lnwire.MilliSatoshi, pathHash uint64) {\n\n\tif totalObserved <= 0 {\n\t\treturn\n\t}\n\n\tkey := edge.key\n\tb := r.beliefs[key]\n\tdistinct := false\n\n\tif b.suspectAmt == 0 ||\n\t\ttotalObserved < b.suspectAmt/2 ||\n\t\ttotalObserved > b.suspectAmt*2 {\n\n\t\tb.suspectAmt = totalObserved\n\t\tb.failVotes = 1\n\t\tb.lastFailPath = pathHash\n\t\tdistinct = true\n\t} else {\n\t\tif totalObserved < b.suspectAmt {\n\t\t\tb.suspectAmt = totalObserved\n\t\t}\n\t\tif pathHash != b.lastFailPath {\n\t\t\tdistinct = true\n\t\t\tif b.failVotes < 255 {\n\t\t\t\tb.failVotes++\n\t\t\t}\n\t\t\tb.lastFailPath = pathHash\n\t\t}\n\t}\n\n\tif distinct {\n\t\tfailedEstimate := totalObserved * 3 / 4\n\t\tif b.estimate == 0 {\n\t\t\tb.estimate = failedEstimate\n\t\t} else {\n\t\t\tb.estimate = (b.estimate*4 + failedEstimate) / 5\n\t\t}\n\t\tb.confidence = math.Min(1, b.confidence+0.10)\n\t}\n\n\tif b.failVotes >= 3 {\n\t\tif b.upper == 0 || b.suspectAmt < b.upper {\n\t\t\tb.upper = b.suspectAmt\n\t\t}\n\t\tif b.lowerOK >= b.upper {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc releaseReservations(reserved map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tcurrent := reserved[use.edge.key]\n\t\tif current <= use.amount {\n\t\t\tdelete(reserved, use.edge.key)\n\t\t} else {\n\t\t\treserved[use.edge.key] = current - use.amount\n\t\t}\n\t}\n}\n\nfunc findFailureNode(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) reconstructUses(\n\trt *route.Route) []edgeUse {\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor _, hop := range rt.Hops {\n\t\tvar found *candidateEdge\n\t\tfor _, edge := range r.incomingEdges[hop.PubKeyBytes] {\n\t\t\tif edge.from == from && edge.chanID == hop.ChannelID {\n\t\t\t\tfound = edge\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif found == nil {\n\t\t\treturn nil\n\t\t}\n\t\tpath = append(path, found)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn routeUses(rt, path)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tif rt == nil {\n\t\treturn errors.New(\"nil reported route\")\n\t}\n\n\tuses, ok := r.issued[rt]\n\tif !ok {\n\t\tuses = r.reconstructUses(rt)\n\t}\n\tdelete(r.issued, rt)\n\n\tif result.Failure == nil {\n\t\tfor _, use := range uses {\n\t\t\tobserved := r.held[use.edge.key] + use.amount\n\t\t\tr.learnSuccess(use, observed)\n\t\t\tr.held[use.edge.key] = observed\n\n\t\t\tif penalty := r.penalty[use.edge.key]; penalty > 0 {\n\t\t\t\tr.penalty[use.edge.key] = penalty * 0.15\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\thash := routeHash(rt)\n\tdelivered := deliveredAmount(rt)\n\tif old, exists := r.tried[hash]; !exists || delivered < old {\n\t\tr.tried[hash] = delivered\n\t}\n\n\ttotalDemand := make(map[edgeKey]lnwire.MilliSatoshi, len(uses))\n\tfor _, use := range uses {\n\t\ttotalDemand[use.edge.key] = r.reserved[use.edge.key]\n\t}\n\treleaseReservations(r.reserved, uses)\n\tr.plan = nil\n\n\tfailNode := findFailureNode(rt, result.FailureSource)\n\tif failNode < 0 || failNode >= len(uses) {\n\t\t// An unreadable onion failure only proves that this exact route\n\t\t// failed at this amount.\n\t\treturn nil\n\t}\n\n\tprimary := failNode\n\tcode := result.Failure.Code()\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tuse := uses[primary]\n\t\tr.penalty[use.edge.key] += 3_500_000\n\t\tr.learnFailure(\n\t\t\tuse.edge, totalDemand[use.edge.key], hash,\n\t\t)\n\n\t\t// Adjacent edges are plausible under shifted attribution, but\n\t\t// receive only weak, payment-local evidence.\n\t\tif primary > 0 {\n\t\t\tr.penalty[uses[primary-1].edge.key] += 450_000\n\t\t}\n\t\tif primary+1 < len(uses) {\n\t\t\tr.penalty[uses[primary+1].edge.key] += 450_000\n\t\t}\n\t\treturn nil\n\t}\n\n\t// A real policy failure will repeat, so avoid it strongly within this\n\t// payment. It never establishes a persistent liquidity bound.\n\tr.penalty[uses[primary].edge.key] += 10_000_000\n\tif primary > 0 {\n\t\tr.penalty[uses[primary-1].edge.key] += 600_000\n\t}\n\tif primary+1 < len(uses) {\n\t\tr.penalty[uses[primary+1].edge.key] += 600_000\n\t}\n\n\treturn nil\n}\n\nvar _ routing.SimRouter = (*candidateRouter)(nil)\n\nfunc (r *candidateRouter) String() string {\n\treturn fmt.Sprintf(\"candidate-router(%v->%v)\", r.source, r.spec.Target)\n}"
}
},
{
"id": 29,
"parent": 3,
"score": 0.4733,
"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\"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\ntype candidateEdgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey candidateEdgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\n\n\tcapacity lnwire.MilliSatoshi\n\tbaseFeeMsat lnwire.MilliSatoshi\n\tfeeRatePPM lnwire.MilliSatoshi\n\ttimeLockDelta uint16\n\tminHTLC lnwire.MilliSatoshi\n\tmaxHTLC lnwire.MilliSatoshi\n}\n\nfunc (e *candidateEdge) fee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) usable(amt lnwire.MilliSatoshi) bool {\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\n// Success observations are exact. Failure observations remain soft because\n// their reported source may be shifted to an adjacent hop.\ntype candidateBelief struct {\n\tcapacity lnwire.MilliSatoshi\n\n\tlowerOK lnwire.MilliSatoshi\n\tokStamp uint64\n\n\tfailAmt lnwire.MilliSatoshi\n\tfailWeight float64\n\tfailStamp uint64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tepoch uint64\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tepoch uint64\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[candidateEdgeKey]candidateBelief\n\n\t// Includes successful shards already settled or held by atomic MPP.\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\n\t// Per-payment penalties provide route diversity without poisoning the\n\t// persistent liquidity model.\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tretryCeiling lnwire.MilliSatoshi\n\n\tfailedAttempts uint32\n\tconsecutiveFailures uint32\n\tsuccessfulParts uint32\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\tcandidateMemory.Lock()\n\tcandidateMemory.epoch++\n\tepoch := candidateMemory.epoch\n\n\tsnapshot := make(map[candidateEdgeKey]candidateBelief,\n\t\tlen(candidateMemory.beliefs))\n\tfor key, belief := range candidateMemory.beliefs {\n\t\tsnapshot[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tepoch: epoch,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: snapshot,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// The prior represents a channel whose funds are normally concentrated on\n// one side. Amounts near capacity encounter an additional logistic cliff.\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\thighMode := 1 / (1 + math.Exp(18*(x-0.82)))\n\n\treturn candidateClampProbability(\n\t\t0.5*lowMode + 0.5*highMode,\n\t)\n}\n\nfunc (r *candidateRouter) evidenceConfidence(stamp uint64) float64 {\n\tif stamp == 0 {\n\t\treturn 0\n\t}\n\tif r.epoch <= stamp+3 {\n\t\treturn 1\n\t}\n\n\tage := float64(r.epoch - stamp - 3)\n\treturn math.Exp(-age / 11)\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tavailable := r.localBalances[edge.chanID]\n\t\tif totalAmt <= available {\n\t\t\treturn 0.999\n\t\t}\n\n\t\treturn 0.001\n\t}\n\n\tprior := candidatePrior(totalAmt, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key]\n\tif !ok || belief.capacity != edge.capacity {\n\t\treturn prior\n\t}\n\n\tp := prior\n\n\tif belief.lowerOK > 0 {\n\t\tconfidence := r.evidenceConfidence(belief.okStamp)\n\t\tlearned := prior\n\n\t\tif totalAmt <= belief.lowerOK {\n\t\t\tlearned = 0.995\n\t\t} else {\n\t\t\tratio := float64(belief.lowerOK) /\n\t\t\t\tfloat64(totalAmt)\n\t\t\tlearned = prior +\n\t\t\t\t(0.995-prior)*ratio*ratio\n\t\t}\n\n\t\tp = confidence*learned + (1-confidence)*p\n\t}\n\n\tif belief.failAmt > 0 && belief.failWeight > 0 {\n\t\tconfidence := r.evidenceConfidence(\n\t\t\tbelief.failStamp,\n\t\t)\n\t\tratio := float64(totalAmt) /\n\t\t\tfloat64(belief.failAmt)\n\t\tratio = math.Max(0, math.Min(1, ratio))\n\n\t\t// A single readable attribution is useful but not a hard\n\t\t// ceiling. Repeated compatible blame progressively strengthens\n\t\t// the observation.\n\t\tseverity := belief.failWeight *\n\t\t\tratio * ratio * ratio\n\t\tmultiplier := math.Exp(-1.12 * severity)\n\t\tmultiplier = (1-confidence) +\n\t\t\tconfidence*multiplier\n\t\tp *= multiplier\n\t}\n\n\treturn candidateClampProbability(p)\n}\n\ntype candidateItem struct {\n\tnode route.Vertex\n\tscore float64\n}\n\ntype candidateQueue []*candidateItem\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateQueue) Push(value any) {\n\t*q = append(*q, value.(*candidateItem))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\tlast := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn last\n}\n\ntype candidatePath struct {\n\troute *route.Route\n\tedges []*candidateEdge\n\tprobability float64\n\tpressure float64\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*candidatePath, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid payment amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tscores := make(map[route.Vertex]float64)\n\tamounts := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscores[r.spec.Target] = 0\n\tamounts[r.spec.Target] = amt\n\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateItem)\n\t\tbestScore, ok := scores[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amounts[item.node]\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usable(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalLiquidity := arriving +\n\t\t\t\tr.reserved[edge.key]\n\t\t\tif totalLiquidity < arriving ||\n\t\t\t\ttotalLiquidity > edge.capacity {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.from == r.source &&\n\t\t\t\ttotalLiquidity >\n\t\t\t\t\tr.localBalances[edge.chanID] {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprobability := r.probability(\n\t\t\t\tedge, totalLiquidity,\n\t\t\t)\n\t\t\tedgeCost := -math.Log(probability) + 0.07\n\t\t\tif arriving > 0 {\n\t\t\t\tedgeCost += 4 * float64(fee) /\n\t\t\t\t\tfloat64(arriving)\n\t\t\t}\n\t\t\tedgeCost += r.edgePenalty[edge.key]\n\n\t\t\tnewScore := item.score + edgeCost\n\t\t\toldScore, visited := scores[edge.from]\n\t\t\tif visited && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscores[edge.from] = newScore\n\t\t\tamounts[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(pq, &candidateItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: newScore,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := next[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprobability := 1.0\n\tpressure := 0.0\n\n\tfor i, edge := range path {\n\t\tamtOver := candidateRouteAmount(rt, i)\n\t\ttotalLiquidity := amtOver +\n\t\t\tr.reserved[edge.key]\n\n\t\tprobability *= r.probability(\n\t\t\tedge, totalLiquidity,\n\t\t)\n\n\t\tdenominator := edge.capacity\n\t\tif edge.from == r.source {\n\t\t\tdenominator = r.localBalances[edge.chanID]\n\t\t}\n\n\t\tif denominator > 0 {\n\t\t\tratio := float64(totalLiquidity) /\n\t\t\t\tfloat64(denominator)\n\t\t\tif ratio > pressure {\n\t\t\t\tpressure = ratio\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &candidatePath{\n\t\troute: rt,\n\t\tedges: path,\n\t\tprobability: probability,\n\t\tpressure: pressure,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tvar path []*candidateEdge\n\tseen := make(map[route.Vertex]bool)\n\n\tfor node := r.source; node != r.spec.Target; {\n\t\tif seen[node] {\n\t\t\treturn nil, nil, errors.New(\"cycle in route\")\n\t\t}\n\t\tseen[node] = true\n\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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 := 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.to,\n\t\t\tChannelID: edge.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}, path, nil\n}\n\nfunc candidateCeilDiv(a lnwire.MilliSatoshi,\n\tb uint32) lnwire.MilliSatoshi {\n\n\tif b <= 1 {\n\t\treturn a\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(b)\n\treturn (a + divisor - 1) / divisor\n}\n\nfunc candidateRouteAmount(rt *route.Route,\n\tedgeIndex int) lnwire.MilliSatoshi {\n\n\tif edgeIndex == 0 {\n\t\treturn rt.TotalAmount\n\t}\n\n\treturn rt.Hops[edgeIndex-1].AmtToForward\n}\n\nfunc candidateRouteEdgeKey(rt *route.Route,\n\tedgeIndex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif edgeIndex > 0 {\n\t\tfrom = rt.Hops[edgeIndex-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[edgeIndex].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[edgeIndex].PubKeyBytes,\n\t}\n}\n\nfunc (r *candidateRouter) reservePath(path *candidatePath,\n\tadd bool) {\n\n\tfor i, edge := range path.edges {\n\t\tamt := candidateRouteAmount(path.route, i)\n\t\tif add {\n\t\t\tr.reserved[edge.key] += amt\n\t\t\tcontinue\n\t\t}\n\n\t\tcurrent := r.reserved[edge.key]\n\t\tif current <= amt {\n\t\t\tdelete(r.reserved, edge.key)\n\t\t} else {\n\t\t\tr.reserved[edge.key] = current - amt\n\t\t}\n\t}\n}\n\ntype candidatePlan struct {\n\tpaths []*candidatePath\n\tdelivered []lnwire.MilliSatoshi\n\tscore float64\n}\n\nfunc candidatePathCost(path *candidatePath,\n\tdelivered lnwire.MilliSatoshi) float64 {\n\n\tprobability := math.Max(path.probability, 1e-12)\n\tscore := -math.Log(probability) + 0.04\n\n\tfee := path.route.TotalAmount - delivered\n\tif fee > 0 && delivered > 0 {\n\t\tscore += 3 * float64(fee) /\n\t\t\tfloat64(delivered)\n\t}\n\n\treturn score\n}\n\nfunc candidateShardAmounts(total lnwire.MilliSatoshi,\n\tparts uint32) []lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn []lnwire.MilliSatoshi{total}\n\t}\n\n\tbase := candidateCeilDiv(total, parts)\n\tfactors := [...]int64{100, 120, 80, 145, 65, 170}\n\tmaximum := total - lnwire.MilliSatoshi(parts-1)\n\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tresult := make([]lnwire.MilliSatoshi, 0, len(factors))\n\n\tfor _, factor := range factors {\n\t\tamt := base * lnwire.MilliSatoshi(factor) / 100\n\t\tif amt < 1 {\n\t\t\tamt = 1\n\t\t}\n\t\tif amt > maximum {\n\t\t\tamt = maximum\n\t\t}\n\t\tif amt <= 0 || amt >= total || seen[amt] {\n\t\t\tcontinue\n\t\t}\n\n\t\tseen[amt] = true\n\t\tresult = append(result, amt)\n\t}\n\n\treturn result\n}\n\n// searchPlan jointly chooses shard amounts and routes. Virtual reservations\n// make later shards account for liquidity already allocated to earlier\n// members of the prospective atomic route set.\nfunc (r *candidateRouter) searchPlan(remaining lnwire.MilliSatoshi,\n\tparts uint32, root bool, budget *int) (*candidatePlan, bool) {\n\n\tif remaining <= 0 || parts == 0 || *budget <= 0 {\n\t\treturn nil, false\n\t}\n\n\tif parts == 1 {\n\t\t*budget--\n\t\tpath, err := r.findRoute(remaining)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tscore := candidatePathCost(path, remaining)\n\t\tif root && r.retryCeiling > 0 &&\n\t\t\tremaining > r.retryCeiling {\n\n\t\t\tratio := float64(remaining) /\n\t\t\t\tfloat64(r.retryCeiling)\n\t\t\tscore += 1.8 + 0.6*(ratio-1)\n\t\t}\n\n\t\treturn &candidatePlan{\n\t\t\tpaths: []*candidatePath{path},\n\t\t\tdelivered: []lnwire.MilliSatoshi{remaining},\n\t\t\tscore: score,\n\t\t}, true\n\t}\n\n\tvar best *candidatePlan\n\n\tfor _, shard := range candidateShardAmounts(\n\t\tremaining, parts,\n\t) {\n\t\tif *budget <= 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t*budget--\n\t\tpath, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfirstCost := candidatePathCost(path, shard)\n\t\tif root && r.retryCeiling > 0 &&\n\t\t\tshard > r.retryCeiling {\n\n\t\t\tratio := float64(shard) /\n\t\t\t\tfloat64(r.retryCeiling)\n\t\t\tfirstCost += 1.8 + 0.6*(ratio-1)\n\t\t}\n\n\t\tr.reservePath(path, true)\n\t\ttail, ok := r.searchPlan(\n\t\t\tremaining-shard, parts-1, false, budget,\n\t\t)\n\t\tr.reservePath(path, false)\n\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tplan := &candidatePlan{\n\t\t\tpaths: append(\n\t\t\t\t[]*candidatePath{path},\n\t\t\t\ttail.paths...,\n\t\t\t),\n\t\t\tdelivered: append(\n\t\t\t\t[]lnwire.MilliSatoshi{shard},\n\t\t\t\ttail.delivered...,\n\t\t\t),\n\t\t\tscore: firstCost + tail.score,\n\t\t}\n\n\t\tif best == nil || plan.score < best.score {\n\t\t\tbest = plan\n\t\t}\n\t}\n\n\treturn best, best != nil\n}\n\n// RequestRoute first constructs a feasible route set in memory. This avoids\n// consuming attempts on a halving probe ladder and permits unequal atomic\n// MPP splits when parallel corridors have unequal carrying capacity.\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid remaining amount\")\n\t}\n\n\tif r.consecutiveFailures >= 64 ||\n\t\tr.failedAttempts >= 96 {\n\n\t\treturn nil, errors.New(\"routing attempts exhausted\")\n\t}\n\n\tpartsLeft := uint32(1)\n\tif r.spec.MaxParts > 0 {\n\t\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\t\treturn nil, errors.New(\"maximum parts reached\")\n\t\t}\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\n\tif partsLeft <= 1 {\n\t\tpath, err := r.findRoute(amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn path.route, nil\n\t}\n\n\tfull, fullErr := r.findRoute(amt)\n\n\t// Below the bimodal cliff, splitting multiplies the number of uncertain\n\t// channels without materially improving per-channel probability.\n\tif fullErr == nil && r.retryCeiling == 0 &&\n\t\t(amt < 20_000_000 ||\n\t\t\tfull.pressure < 0.55 ||\n\t\t\tfull.probability >= 0.68) {\n\n\t\treturn full.route, nil\n\t}\n\n\tvar best *candidatePlan\n\tif fullErr == nil {\n\t\tscore := candidatePathCost(full, amt)\n\t\tif r.retryCeiling > 0 && amt > r.retryCeiling {\n\t\t\tratio := float64(amt) /\n\t\t\t\tfloat64(r.retryCeiling)\n\t\t\tscore += 1.8 + 0.6*(ratio-1)\n\t\t}\n\n\t\tbest = &candidatePlan{\n\t\t\tpaths: []*candidatePath{full},\n\t\t\tdelivered: []lnwire.MilliSatoshi{amt},\n\t\t\tscore: score,\n\t\t}\n\t}\n\n\tmaxPlannedParts := partsLeft\n\tif maxPlannedParts > 10 {\n\t\tmaxPlannedParts = 10\n\t}\n\n\tfor parts := uint32(2); parts <= maxPlannedParts; parts++ {\n\t\tbudget := 110\n\t\tplan, ok := r.searchPlan(\n\t\t\tamt, parts, true, &budget,\n\t\t)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif best == nil || plan.score < best.score {\n\t\t\tbest = plan\n\t\t}\n\t}\n\n\tif best != nil && len(best.paths) > 0 {\n\t\treturn best.paths[0].route, nil\n\t}\n\n\t// A complete set may evade the bounded planner. Retain a conservative\n\t// lower-amount fallback instead of abandoning a routable payment.\n\tdesired := candidateCeilDiv(amt, partsLeft)\n\tif r.retryCeiling > 0 &&\n\t\tr.retryCeiling < desired {\n\n\t\tdesired = r.retryCeiling\n\t}\n\tif desired <= 0 {\n\t\tdesired = 1\n\t}\n\n\tvar lastErr error\n\tfor desired > 0 {\n\t\tpath, err := r.findRoute(desired)\n\t\tif err == nil {\n\t\t\treturn path.route, nil\n\t\t}\n\t\tlastErr = err\n\n\t\tif desired == 1 {\n\t\t\tbreak\n\t\t}\n\n\t\tsmaller := desired * 72 / 100\n\t\tif smaller < 1 {\n\t\t\tsmaller = 1\n\t\t}\n\t\tif smaller >= desired {\n\t\t\tbreak\n\t\t}\n\t\tdesired = smaller\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\n\treturn nil, lastErr\n}\n\nfunc candidateFailureEdge(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc candidateIsLiquidityFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailTemporaryChannelFailure:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc candidateIsPolicyFailure(failure any) bool {\n\tswitch failure.(type) {\n\tcase *lnwire.FailFeeInsufficient,\n\t\t*lnwire.FailIncorrectCltvExpiry:\n\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (r *candidateRouter) saveBelief(key candidateEdgeKey,\n\tbelief candidateBelief) {\n\n\tr.beliefs[key] = belief\n\n\tcandidateMemory.Lock()\n\tcandidateMemory.beliefs[key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc (r *candidateRouter) recordSuccess(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tedge, ok := r.edges[key]\n\tif !ok || edge.from == r.source {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.capacity != 0 &&\n\t\tbelief.capacity != edge.capacity {\n\n\t\tbelief = candidateBelief{}\n\t}\n\tbelief.capacity = edge.capacity\n\n\toldConfidence := r.evidenceConfidence(belief.okStamp)\n\tif belief.lowerOK == 0 || oldConfidence < 0.45 {\n\t\tbelief.lowerOK = totalAmt\n\t} else if totalAmt > belief.lowerOK {\n\t\tbelief.lowerOK = totalAmt\n\t}\n\tbelief.okStamp = r.epoch\n\n\t// A truthful success directly contradicts failures at or below the\n\t// demonstrated amount.\n\tif belief.failAmt > 0 && totalAmt >= belief.failAmt {\n\t\tbelief.failWeight *= 0.12\n\t\tif belief.failWeight < 0.08 {\n\t\t\tbelief.failAmt = 0\n\t\t\tbelief.failWeight = 0\n\t\t\tbelief.failStamp = 0\n\t\t}\n\t}\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailure(key candidateEdgeKey,\n\ttotalAmt lnwire.MilliSatoshi, weight float64) {\n\n\tedge, ok := r.edges[key]\n\tif !ok || edge.from == r.source || weight <= 0 {\n\t\treturn\n\t}\n\n\tbelief := r.beliefs[key]\n\tif belief.capacity != 0 &&\n\t\tbelief.capacity != edge.capacity {\n\n\t\tbelief = candidateBelief{}\n\t}\n\tbelief.capacity = edge.capacity\n\n\tif belief.failWeight > 0 {\n\t\tconfidence := r.evidenceConfidence(\n\t\t\tbelief.failStamp,\n\t\t)\n\t\tbelief.failWeight *= confidence\n\t\tif belief.failWeight < 0.08 {\n\t\t\tbelief.failWeight = 0\n\t\t\tbelief.failAmt = 0\n\t\t}\n\t}\n\n\tcombined := belief.failWeight + weight\n\tif belief.failAmt == 0 || belief.failWeight == 0 {\n\t\tbelief.failAmt = totalAmt\n\t} else {\n\t\taverage := (float64(belief.failAmt)*\n\t\t\tbelief.failWeight +\n\t\t\tfloat64(totalAmt)*weight) / combined\n\t\tbelief.failAmt = lnwire.MilliSatoshi(average)\n\t}\n\n\tbelief.failWeight = math.Min(5, combined)\n\tbelief.failStamp = r.epoch\n\n\tr.saveBelief(key, belief)\n}\n\nfunc (r *candidateRouter) recordFailureIndex(\n\trt *route.Route, index int, weight float64) {\n\n\tif index < 0 || index >= len(rt.Hops) {\n\t\treturn\n\t}\n\n\tkey := candidateRouteEdgeKey(rt, index)\n\tamtOver := candidateRouteAmount(rt, index)\n\ttotalAmt := amtOver + r.reserved[key]\n\n\tr.recordFailure(key, totalAmt, weight)\n}\n\nfunc (r *candidateRouter) addPenalty(key candidateEdgeKey,\n\tincrement, maximum float64) {\n\n\tr.edgePenalty[key] = math.Min(\n\t\tmaximum, r.edgePenalty[key]+increment,\n\t)\n}\n\n// ReportAttempt treats settled attempts as exact observations. Readable\n// liquidity blame is spread across the reported edge and its neighbors,\n// matching the known one-hop attribution noise. Unreadable failures affect\n// only temporary route diversity and never persistent liquidity beliefs.\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\t\tamtOver := candidateRouteAmount(rt, i)\n\t\t\ttotalAmt := amtOver + r.reserved[key]\n\n\t\t\tr.recordSuccess(key, totalAmt)\n\t\t\tr.reserved[key] += amtOver\n\t\t\tr.edgePenalty[key] *= 0.22\n\t\t}\n\n\t\tr.successfulParts++\n\t\tr.consecutiveFailures = 0\n\t\tr.retryCeiling = 0\n\t\treturn nil\n\t}\n\n\tr.failedAttempts++\n\tr.consecutiveFailures++\n\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdgeKey(rt, i)\n\t\tr.addPenalty(key, 0.035, 1.1)\n\t}\n\n\tfailIndex := candidateFailureEdge(\n\t\trt, result.FailureSource,\n\t)\n\tattributed := failIndex >= 0 &&\n\t\tfailIndex < len(rt.Hops)\n\n\tif candidateIsLiquidityFailure(result.Failure) &&\n\t\tattributed {\n\n\t\t// Most readable reports are correct, but adjacent edges retain\n\t\t// some probability because shifted attribution is common.\n\t\tr.recordFailureIndex(rt, failIndex, 0.72)\n\t\tr.recordFailureIndex(rt, failIndex-1, 0.14)\n\t\tr.recordFailureIndex(rt, failIndex+1, 0.14)\n\n\t\tcenter := candidateRouteEdgeKey(rt, failIndex)\n\t\tr.addPenalty(center, 0.72, 3.5)\n\n\t\tif failIndex > 0 {\n\t\t\tkey := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex-1,\n\t\t\t)\n\t\t\tr.addPenalty(key, 0.16, 2)\n\t\t}\n\t\tif failIndex+1 < len(rt.Hops) {\n\t\t\tkey := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex+1,\n\t\t\t)\n\t\t\tr.addPenalty(key, 0.16, 2)\n\t\t}\n\n\t\tdelivered := rt.Hops[len(rt.Hops)-1].\n\t\t\tAmtToForward\n\t\tlowerRetry := delivered * 70 / 100\n\t\tif lowerRetry < 1 {\n\t\t\tlowerRetry = 1\n\t\t}\n\n\t\tif r.retryCeiling == 0 ||\n\t\t\tlowerRetry < r.retryCeiling {\n\n\t\t\tr.retryCeiling = lowerRetry\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif candidateIsPolicyFailure(result.Failure) &&\n\t\tattributed {\n\n\t\tkey := candidateRouteEdgeKey(rt, failIndex)\n\t\tr.addPenalty(key, 1.35, 4)\n\n\t\tif failIndex > 0 {\n\t\t\tneighbor := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex-1,\n\t\t\t)\n\t\t\tr.addPenalty(neighbor, 0.18, 1.5)\n\t\t}\n\t\tif failIndex+1 < len(rt.Hops) {\n\t\t\tneighbor := candidateRouteEdgeKey(\n\t\t\t\trt, failIndex+1,\n\t\t\t)\n\t\t\tr.addPenalty(neighbor, 0.18, 1.5)\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 30,
"parent": 1,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\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\nfunc (e *candidateEdge) upperAllows(amt lnwire.MilliSatoshi) bool {\n\tif 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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tatomic bool\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\tfailedRoute map[uint64]uint8\n\n\tplan []*route.Route\n\n\tfailures uint32\n\tunknownFails uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\n}\n\nfunc candidateAtomicMPP(spec *routing.SimPaymentSpec) bool {\n\tif spec == nil {\n\t\treturn false\n\t}\n\n\tv := reflect.ValueOf(spec)\n\tif v.Kind() == reflect.Pointer {\n\t\tif v.IsNil() {\n\t\t\treturn false\n\t\t}\n\t\tv = v.Elem()\n\t}\n\tif v.Kind() != reflect.Struct {\n\t\treturn false\n\t}\n\n\tfor _, name := range []string{\"AtomicMPP\", \"AtomicMpp\", \"Atomic\"} {\n\t\tfield := v.FieldByName(name)\n\t\tif field.IsValid() && field.Kind() == reflect.Bool {\n\t\t\treturn field.Bool()\n\t\t}\n\t}\n\n\treturn false\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\tatomic: candidateAtomicMPP(spec),\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedges: make(map[candidateEdgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tfailedRoute: make(map[uint64]uint8),\n\t}\n\n\tctx := context.Background()\n\tseen := make(map[route.Vertex]bool)\n\tqueue := []route.Vertex{source}\n\tseen[source] = true\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.002\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := 0.48 * math.Exp(-x/0.024)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.026))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.065 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tif z >= 30 {\n\t\treturn 0.005\n\t}\n\tif z <= -30 {\n\t\treturn 0.995\n\t}\n\n\treturn 1 / (1 + math.Exp(z))\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif extra != nil {\n\t\trequired += extra[e.key]\n\t}\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.13 * float64(belief.conf)\n\t\t\tif weight > 0.72 {\n\t\t\t\tweight = 0.72\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.007)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tif pos > 0 {\n\t\t\t\t\tbounded := 0.995*(1-pos) + 0.007*pos\n\t\t\t\t\tp = 0.30*p + 0.70*bounded\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, ok := r.localFails[e.key]; ok &&\n\t\trequired >= local.upper {\n\n\t\tif local.count >= 2 {\n\t\t\tp = math.Min(p, 0.003)\n\t\t} else {\n\t\t\tp *= 0.10\n\t\t}\n\t}\n\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\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]float64) float64 {\n\n\tp := r.edgeProbability(e, amt, extra)\n\tcost := -math.Log(p) + 0.018 + r.edgePenalty[e.key]\n\n\tif diversity != nil {\n\t\tcost += diversity[e.key]\n\t}\n\n\treserved := r.reserved[e.key]\n\tif extra != nil {\n\t\treserved += extra[e.key]\n\t}\n\tif reserved > 0 && e.capacity > 0 {\n\t\tcost += 0.05 +\n\t\t\t0.45*float64(reserved)/float64(e.capacity)\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tdiversity 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\n\tscore := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscore[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\t\tbestScore, ok := score[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.amt != arriving[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif extra != nil {\n\t\t\t\trequired += extra[edge.key]\n\t\t\t}\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif local, ok := r.localFails[edge.key]; ok &&\n\t\t\t\tlocal.count >= 2 &&\n\t\t\t\trequired >= local.upper {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 22 * float64(fee) / denom\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(\n\t\t\t\t\tedge, amtOver, extra, diversity,\n\t\t\t\t) + feeCost\n\n\t\t\toldScore, exists := score[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\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: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := score[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\tpathProbability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tpathProbability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt, extra,\n\t\t)\n\t}\n\n\treturn rt, pathProbability, 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\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\n\t\tpath = append(path, edge)\n\t\tnode = edge.key.to\n\t}\n\n\treturn r.buildRouteFromPath(amt, path)\n}\n\nfunc (r *candidateRouter) buildRouteFromPath(\n\tamt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\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\tif !edge.policyAllows(amtOver[i]) {\n\t\t\treturn nil, errors.New(\"path violates channel policy\")\n\t\t}\n\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc candidateDeliveredAmount(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 (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\tif rt == nil {\n\t\treturn nil\n\t}\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc (r *candidateRouter) pathFromRoute(\n\trt *route.Route) ([]*candidateEdge, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn nil, false\n\t}\n\n\tpath := make([]*candidateEdge, len(traversals))\n\tfor i := range traversals {\n\t\tpath[i] = traversals[i].edge\n\t}\n\treturn path, true\n}\n\nfunc candidateRouteHash(rt *route.Route, withAmount bool) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, hop := range rt.Hops {\n\t\th ^= hop.ChannelID\n\t\th *= 1099511628211\n\t}\n\tif withAmount {\n\t\th ^= uint64(candidateDeliveredAmount(rt))\n\t\th *= 1099511628211\n\t}\n\treturn h\n}\n\nfunc (r *candidateRouter) credibleLimit(\n\te *candidateEdge) lnwire.MilliSatoshi {\n\n\tif e.key.from == r.source {\n\t\tlimit := r.localBalances[e.key.chanID]\n\t\tif limit > e.capacity {\n\t\t\tlimit = e.capacity\n\t\t}\n\t\treturn limit\n\t}\n\n\tlimit := e.capacity * 88 / 100\n\tif e.maxHTLC > 0 && e.maxHTLC < limit {\n\t\tlimit = e.maxHTLC\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tstored := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif stored != nil {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif stored != nil {\n\t\tif belief.lowerOK > limit {\n\t\t\tlimit = belief.lowerOK\n\t\t}\n\n\t\tif belief.estimate > 0 && belief.conf >= 2 {\n\t\t\testimateLimit := belief.estimate * 94 / 100\n\t\t\tif estimateLimit > belief.lowerOK &&\n\t\t\t\testimateLimit < limit {\n\n\t\t\t\tlimit = estimateLimit\n\t\t\t}\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tfailedLimit := belief.upperFail * 68 / 100\n\t\t\tif failedLimit < limit {\n\t\t\t\tlimit = failedLimit\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, ok := r.localFails[e.key]; ok && local.upper > 0 {\n\t\tfactor := lnwire.MilliSatoshi(72)\n\t\tif local.count >= 2 {\n\t\t\tfactor = 58\n\t\t}\n\t\tlocalLimit := local.upper * factor / 100\n\t\tif localLimit < limit {\n\t\t\tlimit = localLimit\n\t\t}\n\t}\n\n\tif limit > e.capacity {\n\t\tlimit = e.capacity\n\t}\n\treturn limit\n}\n\nfunc (r *candidateRouter) pathUpperFeasible(\n\tamt lnwire.MilliSatoshi, path []*candidateEdge,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi) bool {\n\n\tif amt <= 0 || len(path) == 0 {\n\t\treturn false\n\t}\n\n\trt, err := r.buildRouteIgnoringMinimums(amt, path)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tif !traversal.edge.upperAllows(traversal.amt) {\n\t\t\treturn false\n\t\t}\n\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tif extra != nil {\n\t\t\trequired += extra[traversal.key]\n\t\t}\n\t\tif required > r.credibleLimit(traversal.edge) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) buildRouteIgnoringMinimums(\n\tamt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif amt <= 0 || len(path) == 0 {\n\t\treturn nil, errors.New(\"invalid path\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\tlast := len(path) - 1\n\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\tif !edge.upperAllows(amtOver[i]) {\n\t\t\treturn nil, errors.New(\"amount exceeds path limit\")\n\t\t}\n\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = 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: forward,\n\t\t\tOutgoingTimeLock: expiry,\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 (r *candidateRouter) fixedPathMax(\n\tupper lnwire.MilliSatoshi, path []*candidateEdge,\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif upper <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := upper\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathUpperFeasible(mid, path, extra) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\treturn low\n}\n\nfunc (r *candidateRouter) addExtraReservation(\n\textra map[candidateEdgeKey]lnwire.MilliSatoshi,\n\trt *route.Route) {\n\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\textra[traversal.key] += traversal.amt\n\t}\n}\n\nfunc (r *candidateRouter) targetPartCount(\n\tamt lnwire.MilliSatoshi, available uint32) uint32 {\n\n\tif available <= 1 {\n\t\treturn 1\n\t}\n\n\tvar target uint32\n\tswitch {\n\tcase amt <= 25_000_000:\n\t\ttarget = 1\n\tcase amt <= 100_000_000:\n\t\ttarget = 4\n\tcase amt <= 500_000_000:\n\t\ttarget = 8\n\tcase amt <= 1_500_000_000:\n\t\ttarget = 12\n\tdefault:\n\t\ttarget = 16\n\t}\n\n\tif target > available {\n\t\ttarget = available\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\treturn target\n}\n\ntype candidatePathCapacity struct {\n\tpath []*candidateEdge\n\tmax lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) buildJointPlan(\n\tamt lnwire.MilliSatoshi,\n\tslots uint32) []*route.Route {\n\n\tcount := r.targetPartCount(amt, slots)\n\tif count <= 1 {\n\t\trt, _, err := r.findRoute(amt, nil, nil)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn []*route.Route{rt}\n\t}\n\n\tprobe := candidateCeilDiv(amt, count)\n\tprobe = probe * 2 / 3\n\tif probe < 1 {\n\t\tprobe = 1\n\t}\n\n\tdiscoveryReserve := make(\n\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi,\n\t)\n\tdiversity := make(map[candidateEdgeKey]float64)\n\tseenPaths := make(map[uint64]bool)\n\tpaths := make([]candidatePathCapacity, 0, count)\n\n\tfor uint32(len(paths)) < count {\n\t\tvar rt *route.Route\n\t\tsearchAmt := probe\n\t\tfor tries := 0; tries < 5; tries++ {\n\t\t\tfound, _, err := r.findRoute(\n\t\t\t\tsearchAmt, discoveryReserve, diversity,\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\trt = found\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tsearchAmt = searchAmt * 2 / 3\n\t\t\tif searchAmt < 1 {\n\t\t\t\tsearchAmt = 1\n\t\t\t}\n\t\t}\n\t\tif rt == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tpathHash := candidateRouteHash(rt, false)\n\t\tif seenPaths[pathHash] {\n\t\t\tfor _, traversal := range r.routeTraversals(rt) {\n\t\t\t\tdiversity[traversal.key] += 2.5\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tseenPaths[pathHash] = true\n\n\t\tpath, ok := r.pathFromRoute(rt)\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\n\t\tpathMax := r.fixedPathMax(amt, path, nil)\n\t\tif pathMax <= 0 {\n\t\t\tfor _, traversal := range r.routeTraversals(rt) {\n\t\t\t\tdiversity[traversal.key] += 2.5\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tpaths = append(paths, candidatePathCapacity{\n\t\t\tpath: path,\n\t\t\tmax: pathMax,\n\t\t})\n\n\t\tnominal := searchAmt\n\t\tif nominal > pathMax {\n\t\t\tnominal = pathMax\n\t\t}\n\t\tnominalRoute, err := r.buildRouteFromPath(nominal, path)\n\t\tif err == nil {\n\t\t\tr.addExtraReservation(discoveryReserve, nominalRoute)\n\t\t}\n\t\tfor _, edge := range path {\n\t\t\tdiversity[edge.key] += 1.35\n\t\t}\n\t}\n\n\tif len(paths) == 0 {\n\t\treturn nil\n\t}\n\n\tsort.Slice(paths, func(i, j int) bool {\n\t\treturn paths[i].max < paths[j].max\n\t})\n\n\textra := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tplan := make([]*route.Route, 0, len(paths))\n\tremaining := amt\n\n\tfor i := 0; i < len(paths) && remaining > 0; i++ {\n\t\tmaxNow := r.fixedPathMax(\n\t\t\tremaining, paths[i].path, extra,\n\t\t)\n\t\tif maxNow <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tpathsLeft := uint32(len(paths) - i)\n\t\tdesired := candidateCeilDiv(remaining, pathsLeft)\n\n\t\tfutureCapacity := lnwire.MilliSatoshi(0)\n\t\tfor j := i + 1; j < len(paths); j++ {\n\t\t\tfutureCapacity += r.fixedPathMax(\n\t\t\t\tremaining, paths[j].path, extra,\n\t\t\t)\n\t\t\tif futureCapacity >= remaining {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\trequired := lnwire.MilliSatoshi(0)\n\t\tif futureCapacity < remaining {\n\t\t\trequired = remaining - futureCapacity\n\t\t}\n\n\t\ttake := desired\n\t\tif take < required {\n\t\t\ttake = required\n\t\t}\n\t\tif take > maxNow {\n\t\t\ttake = maxNow\n\t\t}\n\t\tif take > remaining {\n\t\t\ttake = remaining\n\t\t}\n\t\tif take <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\trt, err := r.buildRouteFromPath(take, paths[i].path)\n\t\tif err != nil {\n\t\t\tif maxNow != take {\n\t\t\t\trt, err = r.buildRouteFromPath(\n\t\t\t\t\tmaxNow, paths[i].path,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttake = candidateDeliveredAmount(rt)\n\t\t}\n\n\t\tif r.failedRoute[candidateRouteHash(rt, true)] >= 2 {\n\t\t\tcontinue\n\t\t}\n\n\t\tplan = append(plan, rt)\n\t\tr.addExtraReservation(extra, rt)\n\t\tremaining -= take\n\t}\n\n\tif remaining != 0 || len(plan) == 0 ||\n\t\tuint32(len(plan)) > slots {\n\n\t\treturn nil\n\t}\n\n\treturn plan\n}\n\nfunc (r *candidateRouter) buildGreedyPlan(\n\tamt lnwire.MilliSatoshi,\n\tslots uint32) []*route.Route {\n\n\tcount := r.targetPartCount(amt, slots)\n\textra := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tplan := make([]*route.Route, 0, count)\n\tremaining := amt\n\n\tfor i := uint32(0); i < count && remaining > 0; i++ {\n\t\tleft := count - i\n\t\tshard := candidateCeilDiv(remaining, left)\n\n\t\trt, _, err := r.findRoute(shard, extra, nil)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tif r.failedRoute[candidateRouteHash(rt, true)] >= 2 {\n\t\t\treturn nil\n\t\t}\n\n\t\tplan = append(plan, rt)\n\t\tr.addExtraReservation(extra, rt)\n\t\tremaining -= candidateDeliveredAmount(rt)\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil\n\t}\n\treturn plan\n}\n\nfunc candidatePlanAmount(plan []*route.Route) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, rt := range plan {\n\t\ttotal += candidateDeliveredAmount(rt)\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) freshRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tvar last *route.Route\n\tfor tries := 0; tries < 7; tries++ {\n\t\trt, _, err := r.findRoute(amt, nil, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlast = rt\n\n\t\tif r.failedRoute[candidateRouteHash(rt, true)] < 2 {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\tfor _, traversal := range r.routeTraversals(rt) {\n\t\t\tr.edgePenalty[traversal.key] += 0.55\n\t\t}\n\t}\n\n\tif last != nil {\n\t\treturn last, nil\n\t}\n\treturn nil, errors.New(\"no fresh route found\")\n}\n\nfunc (r *candidateRouter) failureBudget() uint32 {\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tbudget := 16 + 4*maxParts\n\tif budget < 40 {\n\t\tbudget = 40\n\t}\n\tif budget > 96 {\n\t\tbudget = 96\n\t}\n\treturn budget\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\tif r.failures >= r.failureBudget() {\n\t\treturn nil, errors.New(\"routing failure budget exhausted\")\n\t}\n\n\tif len(r.plan) != 0 {\n\t\tif candidatePlanAmount(r.plan) == amt {\n\t\t\trt := r.plan[0]\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn rt, nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tslots := maxParts - inFlightHtlcs\n\n\tr.plan = r.buildJointPlan(amt, slots)\n\tif len(r.plan) == 0 {\n\t\tr.plan = r.buildGreedyPlan(amt, slots)\n\t}\n\tif len(r.plan) != 0 {\n\t\trt := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\t\treturn rt, nil\n\t}\n\n\tif slots == 1 {\n\t\treturn r.freshRoute(amt)\n\t}\n\n\tdesired := candidateCeilDiv(amt, slots)\n\tif r.retryCap > 0 && r.retryCap < desired {\n\t\tdesired = r.retryCap\n\t}\n\tif desired < 1 {\n\t\tdesired = 1\n\t}\n\tif desired > amt {\n\t\tdesired = amt\n\t}\n\n\tfor tries := 0; tries < 7; tries++ {\n\t\trt, err := r.freshRoute(desired)\n\t\tif err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\n\t\tif desired >= amt {\n\t\t\tbreak\n\t\t}\n\n\t\tnext := desired * 3 / 2\n\t\tif next <= desired || next > amt {\n\t\t\tnext = amt\n\t\t}\n\t\tdesired = next\n\t}\n\n\treturn r.freshRoute(amt)\n}\n\nfunc (r *candidateRouter) recordProof(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tb := candidateBeliefStore.beliefs[traversal.key]\n\t\tif b == nil {\n\t\t\tb = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t\t}\n\n\t\tif required > b.lowerOK {\n\t\t\tb.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > b.estimate {\n\t\t\tb.estimate = highEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\n\t\tif b.upperFail > 0 && required >= b.upperFail {\n\t\t\tb.upperFail = 0\n\t\t}\n\t\tif b.suspectAmt > 0 && required >= b.suspectAmt {\n\t\t\tb.suspectAmt = 0\n\t\t\tb.suspectVotes = 0\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) applySettlement(\n\tused map[candidateEdgeKey]lnwire.MilliSatoshi) {\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, amount := range used {\n\t\tif amount <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif b := candidateBeliefStore.beliefs[key]; b != nil {\n\t\t\tif b.lowerOK > amount {\n\t\t\t\tb.lowerOK -= amount\n\t\t\t} else {\n\t\t\t\tb.lowerOK = 0\n\t\t\t}\n\t\t\tif b.estimate > amount {\n\t\t\t\tb.estimate -= amount\n\t\t\t} else {\n\t\t\t\tb.estimate = 0\n\t\t\t}\n\t\t\tif b.upperFail > amount {\n\t\t\t\tb.upperFail -= amount\n\t\t\t} else {\n\t\t\t\tb.upperFail = 0\n\t\t\t}\n\t\t\tif b.suspectAmt > amount {\n\t\t\t\tb.suspectAmt -= amount\n\t\t\t} else {\n\t\t\t\tb.suspectAmt = 0\n\t\t\t\tb.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += amount\n\t\treverseBelief.estimate += amount\n\t\tif reverseBelief.conf < 8 {\n\t\t\treverseBelief.conf++\n\t\t}\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += amount\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\tr.recordProof(rt)\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\t}\n\n\tif r.atomic {\n\t\tfor _, traversal := range traversals {\n\t\t\tr.reserved[traversal.key] += traversal.amt\n\t\t\tr.usedTotals[traversal.key] += traversal.amt\n\t\t}\n\t} else {\n\t\tsettled := make(\n\t\t\tmap[candidateEdgeKey]lnwire.MilliSatoshi,\n\t\t\tlen(traversals),\n\t\t)\n\t\tfor _, traversal := range traversals {\n\t\t\tsettled[traversal.key] += traversal.amt\n\n\t\t\tif traversal.key.from == r.source {\n\t\t\t\tr.reserved[traversal.key] += traversal.amt\n\t\t\t}\n\t\t}\n\t\tr.applySettlement(settled)\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered < r.spec.Amount || r.settled {\n\t\treturn\n\t}\n\n\tif r.atomic {\n\t\tr.applySettlement(r.usedTotals)\n\t}\n\tr.settled = true\n}\n\nfunc (r *candidateRouter) failureIndex(rt *route.Route,\n\tsource route.Vertex) (int, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn -1, false\n\t}\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0, true\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\tindex := i + 1\n\t\tif index >= len(traversals) {\n\t\t\treturn -1, false\n\t\t}\n\t\treturn index, true\n\t}\n\n\treturn -1, false\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversal candidateTraversal) {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tif local.count < 255 {\n\t\tlocal.count++\n\t}\n\tr.localFails[traversal.key] = local\n\tr.edgePenalty[traversal.key] += 1.55\n\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tb := candidateBeliefStore.beliefs[traversal.key]\n\tif b == nil {\n\t\tb = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t}\n\n\tif b.suspectVotes == 0 {\n\t\tb.suspectAmt = required\n\t\tb.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif b.suspectAmt == 0 || required < b.suspectAmt {\n\t\tb.suspectAmt = required\n\t}\n\tif b.suspectVotes < 255 {\n\t\tb.suspectVotes++\n\t}\n\n\tif b.suspectVotes < 3 {\n\t\treturn\n\t}\n\n\tif b.upperFail == 0 || b.suspectAmt < b.upperFail {\n\t\tb.upperFail = b.suspectAmt\n\t}\n\n\tfailedEstimate := b.suspectAmt * 68 / 100\n\tif b.estimate == 0 || failedEstimate < b.estimate {\n\t\tb.estimate = failedEstimate\n\t}\n\tif b.conf < 8 {\n\t\tb.conf++\n\t}\n}\n\nfunc (r *candidateRouter) penalizeFailureNeighborhood(\n\trt *route.Route, index int, policyFailure bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif index < 0 || index >= len(traversals) {\n\t\treturn\n\t}\n\n\tcenter := 1.0\n\tneighbor := 0.35\n\tif policyFailure {\n\t\tcenter = 1.8\n\t\tneighbor = 0.65\n\t}\n\n\tr.edgePenalty[traversals[index].key] += center\n\tif index > 0 {\n\t\tr.edgePenalty[traversals[index-1].key] += neighbor\n\t}\n\tif index+1 < len(traversals) {\n\t\tr.edgePenalty[traversals[index+1].key] += neighbor\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.failures++\n\n\thash := candidateRouteHash(rt, true)\n\tif r.failedRoute[hash] < 255 {\n\t\tr.failedRoute[hash]++\n\t}\n\n\ttraversals := r.routeTraversals(rt)\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.06\n\t}\n\n\tcode := result.Failure.Code()\n\tindex, attributed := r.failureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tif code == lnwire.CodeTemporaryChannelFailure && attributed {\n\t\tr.penalizeFailureNeighborhood(rt, index, false)\n\t\tr.recordAttributedLiquidityFailure(traversals[index])\n\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tif delivered > 1 {\n\t\t\tfactor := lnwire.MilliSatoshi(68)\n\t\t\tif r.localFails[traversals[index].key].count >= 2 {\n\t\t\t\tfactor = 56\n\t\t\t}\n\t\t\tr.retryCap = delivered * factor / 100\n\t\t}\n\t\treturn nil\n\t}\n\n\tif (code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry) && attributed {\n\n\t\tr.penalizeFailureNeighborhood(rt, index, true)\n\t\treturn nil\n\t}\n\n\tr.unknownFails++\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.27\n\t}\n\n\tif r.unknownFails%2 == 0 {\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tif delivered > 1 {\n\t\t\tr.retryCap = delivered * 72 / 100\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 31,
"parent": 6,
"score": 0.3944,
"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\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskWeight = 2_500_000.0\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n\tcap lnwire.MilliSatoshi\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupper lnwire.MilliSatoshi\n\n\testimate lnwire.MilliSatoshi\n\tconfidence float64\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tfailVotes uint8\n\tlastFailPath uint64\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype edgeUse struct {\n\tedge *candidateEdge\n\tamount lnwire.MilliSatoshi\n}\n\ntype searchResult struct {\n\trt *route.Route\n\tpath []*candidateEdge\n\tuses []edgeUse\n\tlogProb float64\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\tresult *searchResult\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\tbeliefs map[edgeKey]liquidityBelief\n\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route][]edgeUse\n\n\tliquidityPenalty map[edgeKey]float64\n\tpenaltyAmount map[edgeKey]lnwire.MilliSatoshi\n\tfixedPenalty map[edgeKey]float64\n\n\ttried map[uint64]lnwire.MilliSatoshi\n\n\tplan []plannedRoute\n\tretryCeiling lnwire.MilliSatoshi\n\tfailedAttempts uint32\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: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route][]edgeUse),\n\t\tliquidityPenalty: make(map[edgeKey]float64),\n\t\tpenaltyAmount: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tfixedPenalty: make(map[edgeKey]float64),\n\t\ttried: make(map[uint64]lnwire.MilliSatoshi),\n\t}\n\n\tfor id, balance := range localBalances {\n\t\tr.localBalances[id] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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\tcapacity := lnwire.NewMSatFromSatoshis(ch.Capacity)\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcap: capacity,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: capacity,\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\n\t\t\t\tsharedBeliefs.Lock()\n\t\t\t\tr.beliefs[key] = sharedBeliefs.m[key]\n\t\t\t\tsharedBeliefs.Unlock()\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\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\tz := (x - 0.90) / 0.045\n\n\tvar highMode float64\n\tswitch {\n\tcase z > 40:\n\t\thighMode = 0\n\tcase z < -40:\n\t\thighMode = 1\n\tdefault:\n\t\thighMode = 1 / (1 + math.Exp(z))\n\t}\n\n\treturn clampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) totalReserved(key edgeKey,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\ttotal := r.reserved[key]\n\tif extra != nil {\n\t\ttotal += extra[key]\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\tneeded := r.totalReserved(edge.key, extra) + amt\n\n\tif edge.from == r.source {\n\t\tif needed <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.995\n\t\t}\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(needed, edge.capacity)\n\tb := r.beliefs[edge.key]\n\n\tif b.lowerOK > edge.capacity {\n\t\tb.lowerOK = edge.capacity\n\t}\n\tif b.upper > edge.capacity {\n\t\tb.upper = edge.capacity\n\t}\n\tif b.estimate > edge.capacity {\n\t\tb.estimate = edge.capacity\n\t}\n\n\tif b.lowerOK > 0 && needed <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\tif b.upper > 0 && needed >= b.upper {\n\t\treturn 0.008\n\t}\n\n\tp := prior\n\tif b.confidence > 0 && b.estimate > 0 {\n\t\tscale := math.Max(float64(edge.capacity)*0.10, 1)\n\t\tz := (float64(needed) - float64(b.estimate)) / scale\n\n\t\tvar estimated float64\n\t\tswitch {\n\t\tcase z > 40:\n\t\t\testimated = 0\n\t\tcase z < -40:\n\t\t\testimated = 1\n\t\tdefault:\n\t\t\testimated = 1 / (1 + math.Exp(z))\n\t\t}\n\n\t\tweight := math.Min(0.70, b.confidence*0.70)\n\t\tp = (1-weight)*p + weight*estimated\n\t}\n\n\tif b.failVotes == 1 && b.suspectAmt > 0 &&\n\t\tneeded >= b.suspectAmt*3/4 {\n\n\t\tp *= 0.62\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) edgeAvailable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) bool {\n\n\tif !edge.usableHTLC(amt) {\n\t\treturn false\n\t}\n\n\tneeded := r.totalReserved(edge.key, extra) + amt\n\tif needed > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.from == r.source &&\n\t\tneeded > r.localBalances[edge.chanID] {\n\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) localPenalty(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.fixedPenalty[edge.key]\n\tliquidity := r.liquidityPenalty[edge.key]\n\tfailedAt := r.penaltyAmount[edge.key]\n\n\tif liquidity == 0 {\n\t\treturn p\n\t}\n\tif failedAt > 0 && amt < failedAt {\n\t\tratio := float64(amt) / float64(failedAt)\n\t\tliquidity *= ratio * ratio\n\t}\n\n\treturn p + liquidity\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tindex int\n}\n\ntype dijkstraQueue []*dijkstraItem\n\nfunc (q dijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q dijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q dijkstraQueue) 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 *dijkstraQueue) Push(x any) {\n\titem := x.(*dijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *dijkstraQueue) 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\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int,\n\tbias map[edgeKey]float64) (*searchResult, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\tbestScore := make(map[route.Vertex]float64)\n\trequired := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tlogProbability := make(map[route.Vertex]float64)\n\n\tbestScore[r.spec.Target] = 0\n\trequired[r.spec.Target] = amt\n\tlogProbability[r.spec.Target] = 0\n\n\tpq := &dijkstraQueue{}\n\theap.Push(pq, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*dijkstraItem)\n\t\tnode := item.node\n\n\t\tcurrent, ok := bestScore[node]\n\t\tif !ok || item.score > current+0.0001 {\n\t\t\tcontinue\n\t\t}\n\t\tif node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := required[node]\n\t\tfor _, edge := range r.incomingEdges[node] {\n\t\t\tif !r.edgeAvailable(edge, arriving, extra) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, arriving, extra)\n\t\t\tsending := arriving\n\t\t\tedgeFee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tedgeFee = edge.fee(arriving)\n\t\t\t\tsending += edgeFee\n\t\t\t}\n\n\t\t\tedgeCost := float64(edgeFee) -\n\t\t\t\triskWeight*math.Log(p) + 25_000\n\t\t\tedgeCost += r.localPenalty(edge, arriving)\n\n\t\t\tif bias != nil {\n\t\t\t\tedgeCost += bias[edge.key]\n\t\t\t}\n\t\t\tif reuse != nil && reuse[edge.key] > 0 {\n\t\t\t\tedgeCost += float64(reuse[edge.key]) * 4_500_000\n\t\t\t}\n\n\t\t\tscore := item.score + edgeCost\n\t\t\told, exists := bestScore[edge.from]\n\t\t\tif exists && score >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbestScore[edge.from] = score\n\t\t\trequired[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\tlogProbability[edge.from] =\n\t\t\t\tlogProbability[node] + math.Log(p)\n\n\t\t\theap.Push(pq, &dijkstraItem{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: score,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := bestScore[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, path, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &searchResult{\n\t\trt: rt,\n\t\tpath: path,\n\t\tuses: routeUses(rt, path),\n\t\tlogProb: logProbability[r.source],\n\t\tfee: rt.TotalAmount - amt,\n\t}, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tnext map[route.Vertex]*candidateEdge) (*route.Route,\n\t[]*candidateEdge, error) {\n\n\tvar path []*candidateEdge\n\tfor node := r.source; node != r.spec.Target; {\n\t\tedge, ok := next[node]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"broken path at %v\", node)\n\t\t}\n\t\tpath = append(path, edge)\n\t\tnode = edge.to\n\t}\n\n\tif len(path) == 0 {\n\t\treturn nil, 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\tnextEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tnextEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\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}, path, nil\n}\n\nfunc routeUses(rt *route.Route, path []*candidateEdge) []edgeUse {\n\tuses := make([]edgeUse, len(path))\n\tfor i, edge := range path {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tuses[i] = edgeUse{\n\t\t\tedge: edge,\n\t\t\tamount: amount,\n\t\t}\n\t}\n\treturn uses\n}\n\nfunc deliveredAmount(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 routeHash(rt *route.Route) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, hop := range rt.Hops {\n\t\th ^= hop.ChannelID\n\t\th *= 1099511628211\n\t\tfor _, b := range hop.PubKeyBytes {\n\t\t\th ^= uint64(b)\n\t\t\th *= 1099511628211\n\t\t}\n\t}\n\treturn h\n}\n\nfunc (r *candidateRouter) findFreshRoute(amt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int) (*searchResult, error) {\n\n\tbias := make(map[edgeKey]float64)\n\tvar lastErr error\n\n\tfor n := 0; n < 14; n++ {\n\t\tresult, err := r.findRoute(amt, extra, reuse, bias)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thash := routeHash(result.rt)\n\t\tfailedAt, failed := r.tried[hash]\n\t\tif !failed || amt < failedAt {\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = errors.New(\"all candidate routes already failed\")\n\t\tif len(result.path) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tindex := int((hash + uint64(n)) % uint64(len(result.path)))\n\t\tbias[result.path[index].key] += 12_000_000\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no fresh route found\")\n\t}\n\treturn nil, lastErr\n}\n\nfunc (r *candidateRouter) reliableDeliveryLimit(result *searchResult,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tdelivery := deliveredAmount(result.rt)\n\tlimit := delivery * 8\n\tif limit < delivery {\n\t\tlimit = delivery\n\t}\n\n\tfor _, use := range result.uses {\n\t\tedge := use.edge\n\t\treserved := r.totalReserved(edge.key, extra)\n\n\t\tvar safeTotal lnwire.MilliSatoshi\n\t\tif edge.from == r.source {\n\t\t\tsafeTotal = r.localBalances[edge.chanID]\n\t\t} else {\n\t\t\tb := r.beliefs[edge.key]\n\t\t\tsafeTotal = edge.capacity * 78 / 100\n\n\t\t\tif b.estimate > 0 && b.confidence > 0 {\n\t\t\t\tw := math.Min(0.85, b.confidence)\n\t\t\t\tsafeTotal = lnwire.MilliSatoshi(\n\t\t\t\t\t(1-w)*float64(safeTotal) +\n\t\t\t\t\t\tw*float64(b.estimate),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif b.lowerOK > safeTotal {\n\t\t\t\tsafeTotal = b.lowerOK\n\t\t\t}\n\t\t\tif b.upper > 0 && safeTotal >= b.upper {\n\t\t\t\tsafeTotal = b.upper * 9 / 10\n\t\t\t}\n\t\t}\n\n\t\tif safeTotal > edge.capacity {\n\t\t\tsafeTotal = edge.capacity\n\t\t}\n\t\tif safeTotal <= reserved || use.amount <= 0 {\n\t\t\treturn 0\n\t\t}\n\n\t\tavailable := safeTotal - reserved\n\t\tedgeLimit := delivery * available / use.amount\n\n\t\tif edge.maxHTLC != 0 {\n\t\t\tpolicyLimit := delivery * edge.maxHTLC / use.amount\n\t\t\tif policyLimit < edgeLimit {\n\t\t\t\tedgeLimit = policyLimit\n\t\t\t}\n\t\t}\n\t\tif edgeLimit < limit {\n\t\t\tlimit = edgeLimit\n\t\t}\n\t}\n\n\treturn limit\n}\n\nfunc addReservations(dst map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tdst[use.edge.key] += use.amount\n\t}\n}\n\nfunc (r *candidateRouter) planMetric(results []*searchResult) float64 {\n\ttotals := make(map[edgeKey]lnwire.MilliSatoshi)\n\tedges := make(map[edgeKey]*candidateEdge)\n\tvar totalFee lnwire.MilliSatoshi\n\n\tfor _, result := range results {\n\t\ttotalFee += result.fee\n\t\tfor _, use := range result.uses {\n\t\t\ttotals[use.edge.key] += use.amount\n\t\t\tedges[use.edge.key] = use.edge\n\t\t}\n\t}\n\n\tmetric := -0.06 * float64(len(results)-1)\n\tfor key, amount := range totals {\n\t\tp := r.edgeProbability(edges[key], amount, nil)\n\t\tmetric += math.Log(p)\n\t}\n\tmetric -= float64(totalFee) / 5_000_000\n\n\treturn metric\n}\n\nfunc (r *candidateRouter) buildPlanForParts(amt,\n\tmaxShard lnwire.MilliSatoshi, parts int,\n\tfrontload bool) ([]*searchResult, bool) {\n\n\tremaining := amt\n\tvirtual := make(map[edgeKey]lnwire.MilliSatoshi)\n\treuse := make(map[edgeKey]int)\n\tresults := make([]*searchResult, 0, parts)\n\n\tfor i := 0; i < parts; i++ {\n\t\tslots := parts - i\n\t\tif remaining <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tshare := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\t\tif share > maxShard {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tresult, err := r.findFreshRoute(share, virtual, reuse)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tif frontload && slots > 1 {\n\t\t\tsafe := r.reliableDeliveryLimit(result, virtual)\n\t\t\ttailFloor := (share + 1) / 2\n\t\t\tmaxNow := remaining -\n\t\t\t\tlnwire.MilliSatoshi(slots-1)*tailFloor\n\t\t\tif maxNow > maxShard {\n\t\t\t\tmaxNow = maxShard\n\t\t\t}\n\n\t\t\tdesired := safe\n\t\t\tif desired > maxNow {\n\t\t\t\tdesired = maxNow\n\t\t\t}\n\t\t\tif desired > share {\n\t\t\t\tlarger, largerErr := r.findFreshRoute(\n\t\t\t\t\tdesired, virtual, reuse,\n\t\t\t\t)\n\t\t\t\tif largerErr == nil {\n\t\t\t\t\tshare = desired\n\t\t\t\t\tresult = larger\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif share <= 0 || share > remaining {\n\t\t\treturn nil, false\n\t\t}\n\t\tif slots > 1 &&\n\t\t\tremaining-share >\n\t\t\t\tlnwire.MilliSatoshi(slots-1)*maxShard {\n\n\t\t\treturn nil, false\n\t\t}\n\n\t\tresults = append(results, result)\n\t\taddReservations(virtual, result.uses)\n\t\tfor _, use := range result.uses {\n\t\t\treuse[use.edge.key]++\n\t\t}\n\t\tremaining -= share\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, false\n\t}\n\n\treturn results, true\n}\n\nfunc (r *candidateRouter) preparePlan(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) error {\n\n\tr.plan = nil\n\tif partsLeft == 0 {\n\t\treturn errors.New(\"maximum in-flight parts reached\")\n\t}\n\n\tmaxShard := amt\n\tif r.retryCeiling > 0 && r.retryCeiling < maxShard {\n\t\tmaxShard = r.retryCeiling\n\t}\n\n\tminShard := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\tif maxShard < minShard {\n\t\tmaxShard = minShard\n\t}\n\n\tminParts := int(\n\t\t(amt + maxShard - 1) / maxShard,\n\t)\n\tif minParts < 1 {\n\t\tminParts = 1\n\t}\n\n\tvar best []*searchResult\n\tbestMetric := math.Inf(-1)\n\tvar directErr error\n\n\tif minParts == 1 {\n\t\tdirect, err := r.findFreshRoute(amt, nil, nil)\n\t\tdirectErr = err\n\t\tif err == nil {\n\t\t\tbest = []*searchResult{direct}\n\t\t\tbestMetric = r.planMetric(best)\n\t\t}\n\t}\n\n\tmaxParts := int(partsLeft)\n\tfor parts := minParts; parts <= maxParts; parts++ {\n\t\tif parts == 1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, frontload := range []bool{false, true} {\n\t\t\tresults, ok := r.buildPlanForParts(\n\t\t\t\tamt, maxShard, parts, frontload,\n\t\t\t)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetric := r.planMetric(results)\n\t\t\tif metric > bestMetric+0.04 {\n\t\t\t\tbest = results\n\t\t\t\tbestMetric = metric\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\tif directErr != nil {\n\t\t\treturn directErr\n\t\t}\n\t\treturn errors.New(\"no route set found\")\n\t}\n\n\tr.plan = make([]plannedRoute, len(best))\n\tfor i, result := range best {\n\t\tr.plan[i] = plannedRoute{result: result}\n\t}\n\n\treturn nil\n}\n\nfunc (r *candidateRouter) issue(result *searchResult) *route.Route {\n\taddReservations(r.reserved, result.uses)\n\tr.issued[result.rt] = result.uses\n\treturn result.rt\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum in-flight parts reached\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 {\n\t\tresult := r.plan[0].result\n\t\tif deliveredAmount(result.rt) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tif err := r.preparePlan(amt, partsLeft); err == nil &&\n\t\tlen(r.plan) > 0 {\n\n\t\tresult := r.plan[0].result\n\t\tr.plan = r.plan[1:]\n\t\treturn r.issue(result), nil\n\t}\n\n\tif partsLeft <= 1 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tminShard := (amt + lnwire.MilliSatoshi(partsLeft) - 1) /\n\t\tlnwire.MilliSatoshi(partsLeft)\n\ttryAmt := amt\n\n\tif r.retryCeiling > 0 && r.retryCeiling < tryAmt {\n\t\ttryAmt = r.retryCeiling\n\t}\n\tif tryAmt < minShard {\n\t\ttryAmt = minShard\n\t}\n\n\tfor tryAmt >= minShard {\n\t\tresult, err := r.findFreshRoute(tryAmt, nil, nil)\n\t\tif err == nil {\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t\tif tryAmt == minShard {\n\t\t\tbreak\n\t\t}\n\n\t\tnext := tryAmt * 3 / 5\n\t\tif next < minShard {\n\t\t\tnext = minShard\n\t\t}\n\t\tif next == tryAmt {\n\t\t\tbreak\n\t\t}\n\t\ttryAmt = next\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tb liquidityBelief) {\n\n\tr.beliefs[key] = b\n\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = b\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(use edgeUse,\n\ttotalObserved lnwire.MilliSatoshi) {\n\n\tkey := use.edge.key\n\tb := r.beliefs[key]\n\n\tif totalObserved > b.lowerOK {\n\t\tb.lowerOK = totalObserved\n\t}\n\tif totalObserved > b.estimate {\n\t\tb.estimate = totalObserved\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.35)\n\n\tif b.upper > 0 && totalObserved >= b.upper {\n\t\tb.upper = 0\n\t}\n\tif b.suspectAmt > 0 && totalObserved >= b.suspectAmt {\n\t\tb.suspectAmt = 0\n\t\tb.failVotes = 0\n\t\tb.lastFailPath = 0\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) learnFailure(edge *candidateEdge,\n\ttotalObserved lnwire.MilliSatoshi, pathHash uint64) {\n\n\tkey := edge.key\n\tb := r.beliefs[key]\n\n\tif b.suspectAmt == 0 ||\n\t\ttotalObserved < b.suspectAmt/2 ||\n\t\ttotalObserved > b.suspectAmt*2 {\n\n\t\tb.suspectAmt = totalObserved\n\t\tb.failVotes = 1\n\t\tb.lastFailPath = pathHash\n\t} else {\n\t\tif totalObserved < b.suspectAmt {\n\t\t\tb.suspectAmt = totalObserved\n\t\t}\n\t\tif pathHash != b.lastFailPath && b.failVotes < 255 {\n\t\t\tb.failVotes++\n\t\t\tb.lastFailPath = pathHash\n\t\t}\n\t}\n\n\tfailedEstimate := totalObserved * 3 / 4\n\tif b.estimate == 0 {\n\t\tb.estimate = failedEstimate\n\t} else {\n\t\tb.estimate = (b.estimate*3 + failedEstimate) / 4\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.15)\n\n\tif b.failVotes >= 2 {\n\t\tif b.upper == 0 || b.suspectAmt < b.upper {\n\t\t\tb.upper = b.suspectAmt\n\t\t}\n\t\tif b.lowerOK >= b.upper {\n\t\t\tb.lowerOK = 0\n\t\t}\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) addLiquidityPenalty(key edgeKey,\n\tamount lnwire.MilliSatoshi, penalty float64) {\n\n\tr.liquidityPenalty[key] += penalty\n\tif amount > 0 &&\n\t\t(r.penaltyAmount[key] == 0 ||\n\t\t\tamount < r.penaltyAmount[key]) {\n\n\t\tr.penaltyAmount[key] = amount\n\t}\n}\n\nfunc releaseReservations(reserved map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tcurrent := reserved[use.edge.key]\n\t\tif current <= use.amount {\n\t\t\tdelete(reserved, use.edge.key)\n\t\t} else {\n\t\t\treserved[use.edge.key] = current - use.amount\n\t\t}\n\t}\n}\n\nfunc findFailureNode(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) rebuildUses(rt *route.Route) []edgeUse {\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor _, hop := range rt.Hops {\n\t\tvar found *candidateEdge\n\t\tfor _, edge := range r.incomingEdges[hop.PubKeyBytes] {\n\t\t\tif edge.from == from && edge.chanID == hop.ChannelID {\n\t\t\t\tfound = edge\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif found == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tpath = append(path, found)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn routeUses(rt, path)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tuses, ok := r.issued[rt]\n\tif !ok {\n\t\tuses = r.rebuildUses(rt)\n\t}\n\tdelete(r.issued, rt)\n\n\tif result.Failure == nil {\n\t\tfor _, use := range uses {\n\t\t\tr.learnSuccess(\n\t\t\t\tuse, r.reserved[use.edge.key],\n\t\t\t)\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.failedAttempts++\n\n\thash := routeHash(rt)\n\tdelivered := deliveredAmount(rt)\n\tif old, exists := r.tried[hash]; !exists || delivered < old {\n\t\tr.tried[hash] = delivered\n\t}\n\n\ttotalDemand := make(map[edgeKey]lnwire.MilliSatoshi, len(uses))\n\tfor _, use := range uses {\n\t\ttotalDemand[use.edge.key] = r.reserved[use.edge.key]\n\t}\n\n\treleaseReservations(r.reserved, uses)\n\tr.plan = nil\n\n\tnextCeiling := delivered * 3 / 5\n\tif nextCeiling > 0 &&\n\t\t(r.retryCeiling == 0 || nextCeiling < r.retryCeiling) {\n\n\t\tr.retryCeiling = nextCeiling\n\t}\n\n\tfailNode := findFailureNode(rt, result.FailureSource)\n\tif failNode < 0 {\n\t\treturn nil\n\t}\n\n\tif failNode >= len(uses) {\n\t\treturn nil\n\t}\n\n\tprimary := failNode\n\tcode := result.Failure.Code()\n\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tuse := uses[primary]\n\t\tdemand := totalDemand[use.edge.key]\n\n\t\tr.addLiquidityPenalty(\n\t\t\tuse.edge.key, demand, 5_000_000,\n\t\t)\n\t\tr.learnFailure(use.edge, demand, hash)\n\n\t\tif primary > 0 {\n\t\t\tadjacent := uses[primary-1]\n\t\t\tr.addLiquidityPenalty(\n\t\t\t\tadjacent.edge.key,\n\t\t\t\ttotalDemand[adjacent.edge.key],\n\t\t\t\t750_000,\n\t\t\t)\n\t\t}\n\t\tif primary+1 < len(uses) {\n\t\t\tadjacent := uses[primary+1]\n\t\t\tr.addLiquidityPenalty(\n\t\t\t\tadjacent.edge.key,\n\t\t\t\ttotalDemand[adjacent.edge.key],\n\t\t\t\t750_000,\n\t\t\t)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tr.fixedPenalty[uses[primary].edge.key] += 8_000_000\n\tif primary > 0 {\n\t\tr.fixedPenalty[uses[primary-1].edge.key] += 900_000\n\t}\n\tif primary+1 < len(uses) {\n\t\tr.fixedPenalty[uses[primary+1].edge.key] += 900_000\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 32,
"parent": 1,
"score": 0.4525,
"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\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectVotes uint8\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\n\tbaseParts uint32\n\tfailures uint32\n\tunknownFails uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t}\n\n\tr.baseParts = r.initialPartCount(spec.Amount)\n\tmaxParts := spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif r.baseParts > maxParts {\n\t\tr.baseParts = maxParts\n\t}\n\tif r.baseParts == 0 {\n\t\tr.baseParts = 1\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\tswitch {\n\tcase amt <= 25_000_000:\n\t\treturn 1\n\n\tcase amt <= 250_000_000:\n\t\tif maxParts < 3 {\n\t\t\treturn maxParts\n\t\t}\n\t\treturn 3\n\n\tcase amt <= 1_000_000_000:\n\t\tif maxParts < 6 {\n\t\t\treturn maxParts\n\t\t}\n\t\treturn 6\n\n\tdefault:\n\t\t// Large atomic payments benefit from committing the smallest\n\t\t// feasible shards immediately instead of spending attempts on a\n\t\t// halving ladder.\n\t\treturn maxParts\n\t}\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + 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 candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.07 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tif z > 30 {\n\t\treturn 0.005\n\t}\n\tif z < -30 {\n\t\treturn 0.995\n\t}\n\n\treturn 1 / (1 + math.Exp(z))\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\t// The sender's balance is exact. A temporary failure attributed to\n\t// this edge is necessarily stale or shifted information.\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored, ok := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif ok {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif ok {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\tq := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\t\t\tweight := 0.13 * float64(belief.conf)\n\t\t\tif weight > 0.72 {\n\t\t\t\tweight = 0.72\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*q\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\t// Corroborated failures are strong evidence, but never\n\t\t\t\t// make a channel permanently impossible.\n\t\t\t\tp = math.Min(p, 0.012)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tpos := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tif pos > 0 {\n\t\t\t\t\tbounded := 0.995*(1-pos) + 0.012*pos\n\t\t\t\t\tp = 0.32*p + 0.68*bounded\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, found := r.localFails[e.key]; found &&\n\t\trequired >= local.upper {\n\n\t\tif local.count >= 2 {\n\t\t\tp = math.Min(p, 0.005)\n\t\t} else {\n\t\t\tp *= 0.15\n\t\t}\n\t}\n\n\tif p < 0.003 {\n\t\treturn 0.003\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\n\treturn p\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\trequired := amt + r.reserved[e.key]\n\n\tpenalty := r.edgePenalty[e.key]\n\tif local, ok := r.localFails[e.key]; ok &&\n\t\trequired < local.upper {\n\n\t\t// A lower-amount retry remains attractive after a high-amount\n\t\t// liquidity miss.\n\t\tpenalty *= 0.18\n\t}\n\n\tcost := -math.Log(p) + 0.025 + penalty\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\t// Prefer a route-set spread across independent corridors, while\n\t\t// still permitting reuse of a route proven to be liquid.\n\t\tfraction := float64(reserved) / float64(e.capacity)\n\t\tcost += 0.16 + 0.85*fraction\n\t}\n\n\treturn cost\n}\n\ntype candidateDijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateDijkstraQueue []*candidateDijkstraItem\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidateDijkstraItem))\n}\n\nfunc (q *candidateDijkstraQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\tscore := make(map[route.Vertex]float64)\n\tarriving := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tscore[r.spec.Target] = 0\n\tarriving[r.spec.Target] = amt\n\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, &candidateDijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tamt: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateDijkstraItem)\n\n\t\tbestScore, ok := score[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.amt != arriving[item.node] {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOver := item.amt\n\t\t\tif !edge.policyAllows(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOver + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOver\n\t\t\tfeeCost := 0.0\n\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOver)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenom := float64(amt)\n\t\t\t\tif denom < 1 {\n\t\t\t\t\tdenom = 1\n\t\t\t\t}\n\n\t\t\t\t// Reliability dominates, with fees breaking ties among\n\t\t\t\t// paths of comparable likelihood.\n\t\t\t\tfeeCost = 24 * float64(fee) / denom\n\t\t\t}\n\n\t\t\tnewScore := item.score +\n\t\t\t\tr.edgeCost(edge, amtOver) + feeCost\n\n\t\t\toldScore, exists := score[edge.key.from]\n\t\t\tif exists && newScore >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore[edge.key.from] = newScore\n\t\t\tarriving[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\n\t\t\theap.Push(pq, &candidateDijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: newScore,\n\t\t\t\tamt: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := score[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\tpathProbability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tpathProbability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t\tif pathProbability < 1e-15 {\n\t\t\tpathProbability = 1e-15\n\t\t}\n\t}\n\n\treturn rt, pathProbability, 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\tfor node := r.source; node != r.spec.Target; {\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\n\ttarget := r.baseParts\n\tif r.failures > 0 {\n\t\ttarget += 1 + r.failures/2\n\t}\n\tif r.unknownFails > 1 {\n\t\ttarget++\n\t}\n\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\n\treturn target\n}\n\nfunc (r *candidateRouter) decayPenalties() {\n\tfor key, penalty := range r.edgePenalty {\n\t\tpenalty *= 0.84\n\t\tif penalty < 0.01 {\n\t\t\tdelete(r.edgePenalty, key)\n\t\t\tcontinue\n\t\t}\n\t\tr.edgePenalty[key] = penalty\n\t}\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tseen map[lnwire.MilliSatoshi]bool, value, limit,\n\tminimum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif value < minimum {\n\t\tvalue = minimum\n\t}\n\tif value > limit {\n\t\tvalue = limit\n\t}\n\tif value <= 0 || seen[value] {\n\t\treturn amounts\n\t}\n\n\tseen[value] = true\n\treturn append(amounts, value)\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tr.decayPenalties()\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\tminimumShard := candidateCeilDiv(amt, partsLeft)\n\n\ttargetParts := r.targetPartCount()\n\tvar desiredSlots uint32\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t} else {\n\t\tdesiredSlots = 1\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\tif desiredSlots == 0 {\n\t\tdesiredSlots = 1\n\t}\n\n\tnominalShard := candidateCeilDiv(amt, desiredSlots)\n\n\t// Preserve efficient single-path behavior for small payments and for\n\t// routes whose liquidity has already been strongly established.\n\tif r.baseParts == 1 {\n\t\tif rt, _, err := r.findRoute(amt); err == nil {\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\tif r.failures == 0 && amt != minimumShard {\n\t\tif rt, probability, err := r.findRoute(amt); err == nil &&\n\t\t\tprobability >= 0.82 {\n\n\t\t\treturn rt, nil\n\t\t}\n\t}\n\n\t// Jointly choose a shard amount and route. The minimum candidate\n\t// guarantees that the remaining payment still fits in the available\n\t// part slots. Larger candidates allow strong, high-capacity corridors\n\t// to take deliberately unequal shares.\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\tamounts := make([]lnwire.MilliSatoshi, 0, 8)\n\n\tamounts = candidateAppendAmount(\n\t\tamounts, seen, minimumShard, amt, minimumShard,\n\t)\n\tif r.retryCap > 0 {\n\t\tamounts = candidateAppendAmount(\n\t\t\tamounts, seen, r.retryCap, amt, minimumShard,\n\t\t)\n\t}\n\tamounts = candidateAppendAmount(\n\t\tamounts, seen, minimumShard+minimumShard/4,\n\t\tamt, minimumShard,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, seen, minimumShard+minimumShard/2,\n\t\tamt, minimumShard,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, seen, minimumShard*2,\n\t\tamt, minimumShard,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, seen, nominalShard, amt, minimumShard,\n\t)\n\tamounts = candidateAppendAmount(\n\t\tamounts, seen, amt, amt, minimumShard,\n\t)\n\n\tvar bestRoute *route.Route\n\tbestUtility := math.Inf(-1)\n\tbestAmount := lnwire.MilliSatoshi(0)\n\tvar lastErr error\n\n\tfor _, shard := range amounts {\n\t\trt, probability, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tif probability < 1e-15 {\n\t\t\tprobability = 1e-15\n\t\t}\n\n\t\tprogress := float64(shard) / float64(minimumShard)\n\t\tutility := math.Log(probability) + 0.42*math.Log(progress)\n\n\t\t// Prefer fewer attempts only when reliability is effectively tied.\n\t\tif utility > bestUtility+1e-12 ||\n\t\t\t(math.Abs(utility-bestUtility) <= 1e-12 &&\n\t\t\t\tshard > bestAmount) {\n\n\t\t\tbestRoute = rt\n\t\t\tbestUtility = utility\n\t\t\tbestAmount = shard\n\t\t}\n\t}\n\n\tif bestRoute != nil {\n\t\treturn bestRoute, nil\n\t}\n\tif lastErr != nil {\n\t\treturn nil, lastErr\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make([]candidateTraversal, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamt := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamt = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amt,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(rt *route.Route) lnwire.MilliSatoshi {\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tb := candidateBeliefStore.beliefs[traversal.key]\n\t\tif b == nil {\n\t\t\tb = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t\t}\n\n\t\tif required > b.lowerOK {\n\t\t\tb.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > b.estimate {\n\t\t\tb.estimate = highEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\n\t\t// Successes are truthful and override conflicting failure blame.\n\t\tif b.upperFail > 0 && required >= b.upperFail {\n\t\t\tb.upperFail = 0\n\t\t}\n\t\tif b.suspectAmt > 0 && required >= b.suspectAmt {\n\t\t\tb.suspectAmt = 0\n\t\t\tb.suspectVotes = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tif penalty := r.edgePenalty[traversal.key]; penalty > 0 {\n\t\t\tpenalty *= 0.15\n\t\t\tif penalty < 0.01 {\n\t\t\t\tdelete(r.edgePenalty, traversal.key)\n\t\t\t} else {\n\t\t\t\tr.edgePenalty[traversal.key] = penalty\n\t\t\t}\n\t\t}\n\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t}\n\n\tr.delivered += candidateDeliveredAmount(rt)\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif b := candidateBeliefStore.beliefs[key]; b != nil {\n\t\t\tif b.lowerOK > used {\n\t\t\t\tb.lowerOK -= used\n\t\t\t} else {\n\t\t\t\tb.lowerOK = 0\n\t\t\t}\n\t\t\tif b.estimate > used {\n\t\t\t\tb.estimate -= used\n\t\t\t} else {\n\t\t\t\tb.estimate = 0\n\t\t\t}\n\t\t\tif b.upperFail > used {\n\t\t\t\tb.upperFail -= used\n\t\t\t} else {\n\t\t\t\tb.upperFail = 0\n\t\t\t}\n\t\t\tif b.suspectAmt > used {\n\t\t\t\tb.suspectAmt -= used\n\t\t\t} else {\n\t\t\t\tb.suspectAmt = 0\n\t\t\t\tb.suspectVotes = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureEdge(rt *route.Route,\n\tsource route.Vertex) (candidateTraversal, bool) {\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\tindex := -1\n\tif source == rt.SourcePubKey {\n\t\tindex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == source {\n\t\t\t\tindex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif index < 0 || index >= len(traversals) {\n\t\treturn candidateTraversal{}, false\n\t}\n\n\treturn traversals[index], true\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversal candidateTraversal) {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t// Local balances are exact, so remote attribution cannot teach us\n\t// anything about an edge originating at the sender.\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tif local.count < 255 {\n\t\tlocal.count++\n\t}\n\tr.localFails[traversal.key] = local\n\tr.edgePenalty[traversal.key] += 1.0\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tb := candidateBeliefStore.beliefs[traversal.key]\n\tif b == nil {\n\t\tb = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = b\n\t}\n\n\tif b.suspectVotes == 0 {\n\t\tb.suspectAmt = required\n\t\tb.suspectVotes = 1\n\t\treturn\n\t}\n\n\tif b.suspectAmt == 0 || required < b.suspectAmt {\n\t\tb.suspectAmt = required\n\t}\n\tif b.suspectVotes < 255 {\n\t\tb.suspectVotes++\n\t}\n\n\t// A single readable report is quarantined. Corroboration promotes it\n\t// to a soft bound, limiting damage from shifted attribution.\n\tif b.suspectVotes >= 2 {\n\t\tif b.upperFail == 0 || b.suspectAmt < b.upperFail {\n\t\t\tb.upperFail = b.suspectAmt\n\t\t}\n\n\t\tfailedEstimate := b.suspectAmt * 70 / 100\n\t\tif b.estimate == 0 || failedEstimate < b.estimate {\n\t\t\tb.estimate = failedEstimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf++\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.retryCap = 0\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\ttraversals := r.routeTraversals(rt)\n\n\t// A small, fading route-wide penalty prevents immediate repetition\n\t// without driving the router permanently toward long detours.\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.025\n\t}\n\n\tcode := result.Failure.Code()\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tr.recordAttributedLiquidityFailure(traversal)\n\n\t\t\tdelivered := candidateDeliveredAmount(rt)\n\t\t\tif delivered > 1 {\n\t\t\t\tr.retryCap = delivered * 64 / 100\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// An off-route source explicitly carries no channel information.\n\tr.unknownFails++\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.16\n\t}\n\n\tdelivered := candidateDeliveredAmount(rt)\n\tif delivered > 1 {\n\t\tfactor := lnwire.MilliSatoshi(76)\n\t\tif r.unknownFails > 1 {\n\t\t\tfactor = 68\n\t\t}\n\t\tr.retryCap = delivered * factor / 100\n\t}\n\n\tif code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry {\n\n\t\tif traversal, ok := r.failureEdge(\n\t\t\trt, result.FailureSource,\n\t\t); ok {\n\t\t\tr.edgePenalty[traversal.key] += 1.5\n\t\t}\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 33,
"parent": 6,
"score": 0.0,
"accepted": false,
"frontier": false,
"params": {
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tfinalCltvDelta = 40\n\triskWeight = 2_200_000.0\n\tmaxLabels = 8\n\tmaxPathLength = 32\n)\n\ntype edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n\tcap lnwire.MilliSatoshi\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupper lnwire.MilliSatoshi\n\n\testimate lnwire.MilliSatoshi\n\tconfidence float64\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tevidence float64\n\tlastPath uint64\n}\n\nvar sharedBeliefs = struct {\n\tsync.Mutex\n\tm map[edgeKey]liquidityBelief\n}{\n\tm: make(map[edgeKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\tchanID uint64\n\tfrom, to route.Vertex\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) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype edgeUse struct {\n\tedge *candidateEdge\n\tamount lnwire.MilliSatoshi\n}\n\ntype searchResult struct {\n\trt *route.Route\n\tpath []*candidateEdge\n\tuses []edgeUse\n\tlogProb float64\n\tfee lnwire.MilliSatoshi\n}\n\ntype plannedRoute struct {\n\tresult *searchResult\n}\n\ntype localMiss struct {\n\tamount lnwire.MilliSatoshi\n\tweight float64\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\tbeliefs map[edgeKey]liquidityBelief\n\n\t// reserved contains held successful shards and attempts that have been\n\t// returned but not reported. held contains truthful successful shards\n\t// only, so pending siblings never inflate a success observation.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\theld map[edgeKey]lnwire.MilliSatoshi\n\tissued map[*route.Route][]edgeUse\n\n\tmisses map[edgeKey]localMiss\n\tpolicyPenalty map[edgeKey]float64\n\ttried map[uint64]lnwire.MilliSatoshi\n\n\tplan []plannedRoute\n\tretryCeiling lnwire.MilliSatoshi\n\tfailures int\n}\n\nfunc newCandidateRouter(view routing.SimNetworkView, source route.Vertex,\n\tlocalBalances map[uint64]lnwire.MilliSatoshi,\n\tspec *routing.SimPaymentSpec) (routing.SimRouter, error) {\n\n\tif spec == nil {\n\t\treturn nil, errors.New(\"nil payment specification\")\n\t}\n\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: make(map[uint64]lnwire.MilliSatoshi),\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\theld: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tissued: make(map[*route.Route][]edgeUse),\n\t\tmisses: make(map[edgeKey]localMiss),\n\t\tpolicyPenalty: make(map[edgeKey]float64),\n\t\ttried: make(map[uint64]lnwire.MilliSatoshi),\n\t}\n\n\tfor id, balance := range localBalances {\n\t\tr.localBalances[id] = balance\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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\tcapacity := lnwire.NewMSatFromSatoshis(ch.Capacity)\n\t\t\t\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcap: capacity,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t\tcapacity: capacity,\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\n\t\t\t\tsharedBeliefs.Lock()\n\t\t\t\tr.beliefs[key] = sharedBeliefs.m[key]\n\t\t\t\tsharedBeliefs.Unlock()\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\treturn r, nil\n}\n\nfunc clampProbability(p float64) float64 {\n\tif p < 0.005 {\n\t\treturn 0.005\n\t}\n\tif p > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn p\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.025)\n\n\tz := (x - 0.90) / 0.045\n\tvar highMode float64\n\tswitch {\n\tcase z > 40:\n\t\thighMode = 0\n\tcase z < -40:\n\t\thighMode = 1\n\tdefault:\n\t\thighMode = 1 / (1 + math.Exp(z))\n\t}\n\n\treturn clampProbability(0.5*lowMode + 0.5*highMode)\n}\n\nfunc (r *candidateRouter) totalReserved(key edgeKey,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\ttotal := r.reserved[key]\n\tif extra != nil {\n\t\ttotal += extra[key]\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\tneeded := r.totalReserved(edge.key, extra) + amt\n\tif edge.from == r.source {\n\t\tif needed <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.995\n\t\t}\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(needed, edge.capacity)\n\tb := r.beliefs[edge.key]\n\n\tif b.lowerOK > edge.capacity {\n\t\tb.lowerOK = edge.capacity\n\t}\n\tif b.upper > edge.capacity {\n\t\tb.upper = edge.capacity\n\t}\n\tif b.estimate > edge.capacity {\n\t\tb.estimate = edge.capacity\n\t}\n\n\tif b.lowerOK > 0 && needed <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\tif b.upper > b.lowerOK && needed >= b.upper {\n\t\treturn 0.008\n\t}\n\n\tp := prior\n\tif b.estimate > 0 && b.confidence > 0 {\n\t\tscale := math.Max(float64(edge.capacity)*0.10, 1)\n\t\tz := (float64(needed) - float64(b.estimate)) / scale\n\n\t\tvar estimated float64\n\t\tswitch {\n\t\tcase z > 40:\n\t\t\testimated = 0\n\t\tcase z < -40:\n\t\t\testimated = 1\n\t\tdefault:\n\t\t\testimated = 1 / (1 + math.Exp(z))\n\t\t}\n\n\t\tweight := math.Min(0.72, b.confidence*0.72)\n\t\tp = (1-weight)*p + weight*estimated\n\t}\n\n\tif b.evidence > 0 && b.suspectAmt > 0 &&\n\t\tneeded*4 >= b.suspectAmt*3 {\n\n\t\tp *= math.Exp(-0.38 * math.Min(b.evidence, 2.5))\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) edgeAvailable(edge *candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi) bool {\n\n\tif !edge.usableHTLC(amt) {\n\t\treturn false\n\t}\n\n\tneeded := r.totalReserved(edge.key, extra) + amt\n\tif needed > edge.capacity {\n\t\treturn false\n\t}\n\tif edge.from == r.source &&\n\t\tneeded > r.localBalances[edge.chanID] {\n\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) missPenalty(edge *candidateEdge,\n\tneeded lnwire.MilliSatoshi) float64 {\n\n\tmiss := r.misses[edge.key]\n\tif miss.amount <= 0 || miss.weight <= 0 {\n\t\treturn 0\n\t}\n\n\tratio := float64(needed) / float64(miss.amount)\n\tif ratio > 1 {\n\t\tratio = 1\n\t}\n\tif ratio < 0 {\n\t\tratio = 0\n\t}\n\n\t// Blame matters strongly near the failed amount, but fades quickly\n\t// enough that the same channel remains useful for a smaller shard.\n\treturn 14_000_000 * miss.weight * ratio * ratio * ratio\n}\n\ntype searchState struct {\n\tnode route.Vertex\n\tscore float64\n\tamount lnwire.MilliSatoshi\n\tlogProb float64\n\tedge *candidateEdge\n\tnext *searchState\n\tdepth int\n\tdead bool\n\theapIndex int\n}\n\ntype stateQueue []*searchState\n\nfunc (q stateQueue) Len() int { return len(q) }\nfunc (q stateQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\nfunc (q stateQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n\tq[i].heapIndex = i\n\tq[j].heapIndex = j\n}\nfunc (q *stateQueue) Push(x any) {\n\titem := x.(*searchState)\n\titem.heapIndex = len(*q)\n\t*q = append(*q, item)\n}\nfunc (q *stateQueue) 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 stateContains(s *searchState, node route.Vertex) bool {\n\tfor current := s; current != nil; current = current.next {\n\t\tif current.node == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc acceptLabel(labels map[route.Vertex][]*searchState,\n\tcandidate *searchState) bool {\n\n\told := labels[candidate.node]\n\tfor _, label := range old {\n\t\tif !label.dead &&\n\t\t\tlabel.score <= candidate.score &&\n\t\t\tlabel.amount <= candidate.amount {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tkept := old[:0]\n\tfor _, label := range old {\n\t\tif label.dead {\n\t\t\tcontinue\n\t\t}\n\t\tif candidate.score <= label.score &&\n\t\t\tcandidate.amount <= label.amount {\n\n\t\t\tlabel.dead = true\n\t\t\tcontinue\n\t\t}\n\t\tkept = append(kept, label)\n\t}\n\tkept = append(kept, candidate)\n\n\tif len(kept) > maxLabels {\n\t\tminAmountIndex := 0\n\t\tfor i := 1; i < len(kept); i++ {\n\t\t\tif kept[i].amount < kept[minAmountIndex].amount {\n\t\t\t\tminAmountIndex = i\n\t\t\t}\n\t\t}\n\n\t\tremove := -1\n\t\tfor i := range kept {\n\t\t\tif i == minAmountIndex {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif remove < 0 || kept[i].score > kept[remove].score {\n\t\t\t\tremove = i\n\t\t\t}\n\t\t}\n\t\tif remove >= 0 {\n\t\t\tkept[remove].dead = true\n\t\t\tremovedCandidate := kept[remove] == candidate\n\t\t\tkept = append(kept[:remove], kept[remove+1:]...)\n\t\t\tlabels[candidate.node] = kept\n\t\t\treturn !removedCandidate\n\t\t}\n\t}\n\n\tlabels[candidate.node] = kept\n\treturn true\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int,\n\tbias map[edgeKey]float64) (*searchResult, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is payment target\")\n\t}\n\n\troot := &searchState{\n\t\tnode: r.spec.Target,\n\t\tamount: amt,\n\t\tdepth: 0,\n\t\tlogProb: 0,\n\t}\n\tlabels := map[route.Vertex][]*searchState{\n\t\tr.spec.Target: {root},\n\t}\n\tpq := &stateQueue{}\n\theap.Push(pq, root)\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*searchState)\n\t\tif item.dead {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tpath := make([]*candidateEdge, 0, item.depth)\n\t\t\tfor current := item; current != nil && current.edge != nil;\n\t\t\t\tcurrent = current.next {\n\n\t\t\t\tpath = append(path, current.edge)\n\t\t\t}\n\n\t\t\trt, err := r.buildRoute(amt, path)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn &searchResult{\n\t\t\t\trt: rt,\n\t\t\t\tpath: path,\n\t\t\t\tuses: routeUses(rt, path),\n\t\t\t\tlogProb: item.logProb,\n\t\t\t\tfee: rt.TotalAmount - amt,\n\t\t\t}, nil\n\t\t}\n\t\tif item.depth >= maxPathLength {\n\t\t\tcontinue\n\t\t}\n\n\t\tarriving := item.amount\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif stateContains(item, edge.from) ||\n\t\t\t\t!r.edgeAvailable(edge, arriving, extra) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tp := r.edgeProbability(edge, arriving, extra)\n\t\t\tsending := arriving\n\t\t\tedgeFee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.from != r.source {\n\t\t\t\tedgeFee = edge.fee(arriving)\n\t\t\t\tsending += edgeFee\n\t\t\t}\n\n\t\t\tneeded := r.totalReserved(edge.key, extra) + arriving\n\t\t\tedgeCost := float64(edgeFee) -\n\t\t\t\triskWeight*math.Log(p) + 25_000\n\t\t\tedgeCost += r.missPenalty(edge, needed)\n\t\t\tedgeCost += r.policyPenalty[edge.key]\n\n\t\t\tif bias != nil {\n\t\t\t\tedgeCost += bias[edge.key]\n\t\t\t}\n\t\t\tif reuse != nil && reuse[edge.key] > 0 {\n\t\t\t\tedgeCost += 5_000_000 *\n\t\t\t\t\tfloat64(reuse[edge.key])\n\t\t\t}\n\n\t\t\tnext := &searchState{\n\t\t\t\tnode: edge.from,\n\t\t\t\tscore: item.score + edgeCost,\n\t\t\t\tamount: sending,\n\t\t\t\tlogProb: item.logProb + math.Log(p),\n\t\t\t\tedge: edge,\n\t\t\t\tnext: item,\n\t\t\t\tdepth: item.depth + 1,\n\t\t\t}\n\t\t\tif acceptLabel(labels, next) {\n\t\t\t\theap.Push(pq, next)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\tif path[0].from != r.source ||\n\t\tpath[len(path)-1].to != r.spec.Target {\n\n\t\treturn nil, errors.New(\"invalid path endpoints\")\n\t}\n\n\tamtOver := make([]lnwire.MilliSatoshi, len(path))\n\texpiryOver := make([]uint32, len(path))\n\tlast := len(path) - 1\n\tamtOver[last] = amt\n\texpiryOver[last] = finalCltvDelta\n\n\tfor i := last - 1; i >= 0; i-- {\n\t\tnextEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tnextEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = expiryOver[i+1]\n\t\t}\n\n\t\thops[i] = &route.Hop{\n\t\t\tPubKeyBytes: edge.to,\n\t\t\tChannelID: edge.chanID,\n\t\t\tAmtToForward: forward,\n\t\t\tOutgoingTimeLock: expiry,\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 routeUses(rt *route.Route, path []*candidateEdge) []edgeUse {\n\tuses := make([]edgeUse, len(path))\n\tfor i, edge := range path {\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\t\tuses[i] = edgeUse{edge: edge, amount: amount}\n\t}\n\treturn uses\n}\n\nfunc deliveredAmount(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 routeHash(rt *route.Route) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, hop := range rt.Hops {\n\t\th ^= hop.ChannelID\n\t\th *= 1099511628211\n\t\tfor _, b := range hop.PubKeyBytes {\n\t\t\th ^= uint64(b)\n\t\t\th *= 1099511628211\n\t\t}\n\t}\n\treturn h\n}\n\nfunc (r *candidateRouter) findFreshRoute(amt lnwire.MilliSatoshi,\n\textra map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]int) (*searchResult, error) {\n\n\tbias := make(map[edgeKey]float64)\n\tvar lastErr error\n\n\tfor n := 0; n < 14; n++ {\n\t\tresult, err := r.findRoute(amt, extra, reuse, bias)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\thash := routeHash(result.rt)\n\t\tfailedAt, failed := r.tried[hash]\n\t\tif !failed || amt*100 <= failedAt*72 {\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = errors.New(\"candidate route already failed\")\n\t\tif len(result.path) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tindex := int((hash + uint64(n*7)) %\n\t\t\tuint64(len(result.path)))\n\t\tbias[result.path[index].key] += 18_000_000\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no fresh route found\")\n\t}\n\treturn nil, lastErr\n}\n\nfunc (r *candidateRouter) reliableDeliveryLimit(result *searchResult,\n\textra map[edgeKey]lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tdelivery := deliveredAmount(result.rt)\n\tlimit := delivery * 8\n\tif limit < delivery {\n\t\tlimit = delivery\n\t}\n\n\tfor _, use := range result.uses {\n\t\tedge := use.edge\n\t\treserved := r.totalReserved(edge.key, extra)\n\n\t\tvar safeTotal lnwire.MilliSatoshi\n\t\tif edge.from == r.source {\n\t\t\tsafeTotal = r.localBalances[edge.chanID]\n\t\t} else {\n\t\t\tb := r.beliefs[edge.key]\n\t\t\tsafeTotal = edge.capacity * 68 / 100\n\n\t\t\tif b.estimate > 0 && b.confidence > 0 {\n\t\t\t\tw := math.Min(0.82, b.confidence)\n\t\t\t\tsafeTotal = lnwire.MilliSatoshi(\n\t\t\t\t\t(1-w)*float64(safeTotal) +\n\t\t\t\t\t\tw*float64(b.estimate),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif b.lowerOK > safeTotal {\n\t\t\t\tsafeTotal = b.lowerOK\n\t\t\t}\n\t\t\tif b.upper > b.lowerOK && safeTotal >= b.upper {\n\t\t\t\tsafeTotal = b.upper * 17 / 20\n\t\t\t}\n\t\t}\n\n\t\tif safeTotal > edge.capacity {\n\t\t\tsafeTotal = edge.capacity\n\t\t}\n\t\tif safeTotal <= reserved {\n\t\t\treturn 0\n\t\t}\n\n\t\tavailable := safeTotal - reserved\n\t\tedgeLimit := delivery * available / use.amount\n\n\t\tif edge.maxHTLC != 0 {\n\t\t\tpolicyLimit := delivery * edge.maxHTLC / use.amount\n\t\t\tif policyLimit < edgeLimit {\n\t\t\t\tedgeLimit = policyLimit\n\t\t\t}\n\t\t}\n\t\tif edgeLimit < limit {\n\t\t\tlimit = edgeLimit\n\t\t}\n\t}\n\n\treturn limit\n}\n\nfunc addReservations(dst map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tdst[use.edge.key] += use.amount\n\t}\n}\n\nfunc releaseReservations(dst map[edgeKey]lnwire.MilliSatoshi,\n\tuses []edgeUse) {\n\n\tfor _, use := range uses {\n\t\tcurrent := dst[use.edge.key]\n\t\tif current <= use.amount {\n\t\t\tdelete(dst, use.edge.key)\n\t\t} else {\n\t\t\tdst[use.edge.key] = current - use.amount\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) planMetric(results []*searchResult) float64 {\n\ttotals := make(map[edgeKey]lnwire.MilliSatoshi)\n\tedges := make(map[edgeKey]*candidateEdge)\n\tfees := lnwire.MilliSatoshi(0)\n\n\tfor _, result := range results {\n\t\tfees += result.fee\n\t\tfor _, use := range result.uses {\n\t\t\ttotals[use.edge.key] += use.amount\n\t\t\tedges[use.edge.key] = use.edge\n\t\t}\n\t}\n\n\tmetric := -0.035 * float64(len(results))\n\tfor key, amount := range totals {\n\t\tedge := edges[key]\n\t\tp := r.edgeProbability(edge, amount, nil)\n\t\tmetric += math.Log(p)\n\n\t\tneeded := r.reserved[key] + amount\n\t\tmetric -= r.missPenalty(edge, needed) / riskWeight\n\t\tmetric -= r.policyPenalty[key] / riskWeight\n\t}\n\tmetric -= float64(fees) / 20_000_000\n\n\treturn metric\n}\n\nfunc (r *candidateRouter) buildPlanForParts(amt lnwire.MilliSatoshi,\n\tparts int, maxShard lnwire.MilliSatoshi) ([]*searchResult, bool) {\n\n\tremaining := amt\n\tvirtual := make(map[edgeKey]lnwire.MilliSatoshi)\n\treuse := make(map[edgeKey]int)\n\tresults := make([]*searchResult, 0, parts)\n\n\tfor i := 0; i < parts && remaining > 0; i++ {\n\t\tslots := parts - i\n\t\tgoal := (remaining + lnwire.MilliSatoshi(slots) - 1) /\n\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\tprobe := goal\n\t\tif maxShard > 0 && probe > maxShard {\n\t\t\tprobe = maxShard\n\t\t}\n\t\tif slots == 1 {\n\t\t\tprobe = remaining\n\t\t\tif maxShard > 0 && probe > maxShard {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t}\n\n\t\tvar result *searchResult\n\t\tvar err error\n\t\tfor tries := 0; tries < 5 && probe > 0; tries++ {\n\t\t\tresult, err = r.findFreshRoute(probe, virtual, reuse)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tprobe = probe * 2 / 3\n\t\t}\n\t\tif err != nil || result == nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tsafe := r.reliableDeliveryLimit(result, virtual)\n\t\tif safe <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tshare := goal\n\t\tif safe < share {\n\t\t\tshare = safe * 9 / 10\n\t\t} else if slots > 1 {\n\t\t\tpreferred := safe * 17 / 20\n\t\t\tunequalCap := goal * 8 / 5\n\t\t\tif preferred > share {\n\t\t\t\tshare = preferred\n\t\t\t}\n\t\t\tif share > unequalCap {\n\t\t\t\tshare = unequalCap\n\t\t\t}\n\t\t} else {\n\t\t\tshare = remaining\n\t\t}\n\n\t\tif maxShard > 0 && share > maxShard {\n\t\t\tshare = maxShard\n\t\t}\n\t\tif share > remaining {\n\t\t\tshare = remaining\n\t\t}\n\t\tif share <= 0 {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tresult, err = r.findFreshRoute(share, virtual, reuse)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\n\t\tsafe = r.reliableDeliveryLimit(result, virtual)\n\t\tif safe < share {\n\t\t\tif slots == 1 || safe <= 0 {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tshare = safe * 9 / 10\n\t\t\tif share <= 0 {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tresult, err = r.findFreshRoute(share, virtual, reuse)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, result)\n\t\taddReservations(virtual, result.uses)\n\t\tfor _, use := range result.uses {\n\t\t\treuse[use.edge.key]++\n\t\t}\n\t\tremaining -= share\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil, false\n\t}\n\treturn results, true\n}\n\nfunc appendPartOption(options []int, value, maximum int) []int {\n\tif value < 2 || value > maximum {\n\t\treturn options\n\t}\n\tfor _, existing := range options {\n\t\tif existing == value {\n\t\t\treturn options\n\t\t}\n\t}\n\treturn append(options, value)\n}\n\nfunc (r *candidateRouter) preparePlan(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) error {\n\n\tr.plan = nil\n\tif partsLeft == 0 {\n\t\treturn errors.New(\"maximum in-flight parts reached\")\n\t}\n\n\tvar best []*searchResult\n\tbestMetric := math.Inf(-1)\n\n\tallowDirect := partsLeft == 1 || r.failures < 3 ||\n\t\tr.retryCeiling == 0 || amt <= r.retryCeiling\n\n\tdirect, directErr := r.findFreshRoute(amt, nil, nil)\n\tif allowDirect && directErr == nil {\n\t\tbest = []*searchResult{direct}\n\t\tbestMetric = r.planMetric(best)\n\t}\n\n\tmaxParts := int(partsLeft)\n\tif maxParts > 16 {\n\t\tmaxParts = 16\n\t}\n\n\tmaxShard := lnwire.MilliSatoshi(0)\n\tif r.failures >= 3 {\n\t\tmaxShard = r.retryCeiling\n\t}\n\n\toptions := make([]int, 0, 7)\n\toptions = appendPartOption(options, 2, maxParts)\n\toptions = appendPartOption(options, 3, maxParts)\n\toptions = appendPartOption(options, 4, maxParts)\n\toptions = appendPartOption(options, 6, maxParts)\n\toptions = appendPartOption(options, 8, maxParts)\n\toptions = appendPartOption(options, 12, maxParts)\n\toptions = appendPartOption(options, maxParts, maxParts)\n\n\tfor _, parts := range options {\n\t\tresults, ok := r.buildPlanForParts(amt, parts, maxShard)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tmetric := r.planMetric(results)\n\t\tif metric > bestMetric+0.02 {\n\t\t\tbest = results\n\t\t\tbestMetric = metric\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\tif directErr != nil {\n\t\t\treturn directErr\n\t\t}\n\t\treturn errors.New(\"no route set found\")\n\t}\n\n\tr.plan = make([]plannedRoute, len(best))\n\tfor i, result := range best {\n\t\tr.plan[i] = plannedRoute{result: result}\n\t}\n\treturn nil\n}\n\nfunc (r *candidateRouter) issue(result *searchResult) *route.Route {\n\taddReservations(r.reserved, result.uses)\n\tr.issued[result.rt] = result.uses\n\treturn result.rt\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum in-flight parts reached\")\n\t}\n\tpartsLeft := maxParts - inFlightHtlcs\n\n\tif len(r.plan) > 0 {\n\t\tresult := r.plan[0].result\n\t\tif deliveredAmount(result.rt) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.issue(result), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tif err := r.preparePlan(amt, partsLeft); err == nil &&\n\t\tlen(r.plan) > 0 {\n\n\t\tresult := r.plan[0].result\n\t\tr.plan = r.plan[1:]\n\t\treturn r.issue(result), nil\n\t}\n\n\tif partsLeft <= 1 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\ttryAmt := amt\n\tif r.failures >= 3 && r.retryCeiling > 0 &&\n\t\tr.retryCeiling < tryAmt {\n\n\t\ttryAmt = r.retryCeiling\n\t}\n\n\tminTry := amt / lnwire.MilliSatoshi(partsLeft*6)\n\tif minTry <= 0 {\n\t\tminTry = 1\n\t}\n\n\tfor tries := 0; tries < 10 && tryAmt >= minTry; tries++ {\n\t\tresult, err := r.findFreshRoute(tryAmt, nil, nil)\n\t\tif err == nil {\n\t\t\tsafe := r.reliableDeliveryLimit(result, nil)\n\t\t\tif safe > 0 && safe < tryAmt {\n\t\t\t\ttryAmt = safe * 9 / 10\n\t\t\t\tif tryAmt < minTry {\n\t\t\t\t\ttryAmt = minTry\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn r.issue(result), nil\n\t\t}\n\n\t\tnext := tryAmt * 2 / 3\n\t\tif next < minTry {\n\t\t\tnext = minTry\n\t\t}\n\t\tif next == tryAmt {\n\t\t\tbreak\n\t\t}\n\t\ttryAmt = next\n\t}\n\n\treturn nil, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) saveBelief(key edgeKey,\n\tb liquidityBelief) {\n\n\tr.beliefs[key] = b\n\tsharedBeliefs.Lock()\n\tsharedBeliefs.m[key] = b\n\tsharedBeliefs.Unlock()\n}\n\nfunc (r *candidateRouter) learnSuccess(use edgeUse,\n\ttotalObserved lnwire.MilliSatoshi) {\n\n\tkey := use.edge.key\n\tb := r.beliefs[key]\n\n\tif totalObserved > b.lowerOK {\n\t\tb.lowerOK = totalObserved\n\t}\n\tif totalObserved > b.estimate {\n\t\tb.estimate = totalObserved\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.35)\n\n\tif b.upper > 0 && totalObserved >= b.upper {\n\t\tb.upper = 0\n\t}\n\tif b.suspectAmt > 0 && totalObserved >= b.suspectAmt {\n\t\tb.suspectAmt = 0\n\t\tb.evidence = 0\n\t\tb.lastPath = 0\n\t}\n\n\tr.saveBelief(key, b)\n\n\tif miss := r.misses[key]; miss.amount > 0 &&\n\t\ttotalObserved >= miss.amount {\n\n\t\tdelete(r.misses, key)\n\t}\n}\n\nfunc (r *candidateRouter) learnFailure(edge *candidateEdge,\n\ttotalObserved lnwire.MilliSatoshi, pathHash uint64,\n\tweight float64) {\n\n\tif edge.from == r.source || totalObserved <= 0 || weight <= 0 {\n\t\treturn\n\t}\n\n\tkey := edge.key\n\tb := r.beliefs[key]\n\n\tif b.suspectAmt == 0 ||\n\t\ttotalObserved*2 < b.suspectAmt ||\n\t\ttotalObserved > b.suspectAmt*2 {\n\n\t\tb.suspectAmt = totalObserved\n\t\tb.evidence = weight\n\t\tb.lastPath = pathHash\n\t} else {\n\t\tif totalObserved < b.suspectAmt {\n\t\t\tb.suspectAmt = totalObserved\n\t\t}\n\t\tif b.lastPath == pathHash {\n\t\t\tb.evidence += weight * 0.35\n\t\t} else {\n\t\t\tb.evidence += weight\n\t\t\tb.lastPath = pathHash\n\t\t}\n\t}\n\n\tfailedEstimate := totalObserved * 2 / 3\n\tif b.estimate == 0 {\n\t\tb.estimate = failedEstimate\n\t} else {\n\t\tw := math.Min(0.35, weight*0.28)\n\t\tb.estimate = lnwire.MilliSatoshi(\n\t\t\t(1-w)*float64(b.estimate) +\n\t\t\t\tw*float64(failedEstimate),\n\t\t)\n\t}\n\tb.confidence = math.Min(1, b.confidence+0.12*weight)\n\n\t// Two independently attributed primary observations are enough to\n\t// establish a ceiling. Adjacent-hop evidence needs many more samples.\n\tif b.evidence >= 1.35 &&\n\t\t(b.lowerOK == 0 || b.suspectAmt > b.lowerOK) {\n\n\t\tif b.upper == 0 || b.suspectAmt < b.upper {\n\t\t\tb.upper = b.suspectAmt\n\t\t}\n\t}\n\n\tr.saveBelief(key, b)\n}\n\nfunc (r *candidateRouter) addLocalMiss(edge *candidateEdge,\n\tamount lnwire.MilliSatoshi, weight float64) {\n\n\tif amount <= 0 || weight <= 0 {\n\t\treturn\n\t}\n\n\told := r.misses[edge.key]\n\tif old.amount == 0 ||\n\t\tamount*2 < old.amount ||\n\t\tamount > old.amount*2 {\n\n\t\tr.misses[edge.key] = localMiss{\n\t\t\tamount: amount,\n\t\t\tweight: weight,\n\t\t}\n\t\treturn\n\t}\n\n\tif amount < old.amount {\n\t\told.amount = amount\n\t}\n\told.weight = math.Min(4, old.weight+weight)\n\tr.misses[edge.key] = old\n}\n\nfunc findFailureNode(rt *route.Route, source route.Vertex) int {\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) applyLiquidityBlame(uses []edgeUse,\n\ttotalDemand map[edgeKey]lnwire.MilliSatoshi,\n\tprimary int, pathHash uint64) {\n\n\tif len(uses) == 0 {\n\t\treturn\n\t}\n\n\tapply := func(index int, weight float64) {\n\t\tif index < 0 || index >= len(uses) {\n\t\t\treturn\n\t\t}\n\t\tuse := uses[index]\n\t\tamount := totalDemand[use.edge.key]\n\t\tr.addLocalMiss(use.edge, amount, weight)\n\t\tr.learnFailure(use.edge, amount, pathHash, weight)\n\t}\n\n\t// The target has no outgoing edge. Blame placed there is most\n\t// plausibly a one-hop shift from the final forwarding node.\n\tif primary >= len(uses) {\n\t\tapply(len(uses)-1, 0.80)\n\t\treturn\n\t}\n\n\t// Local outbound liquidity is known exactly. A reported liquidity\n\t// failure there is therefore much more likely to be shifted blame.\n\tif primary == 0 && uses[0].edge.from == r.source {\n\t\tapply(0, 0.08)\n\t\tapply(1, 0.78)\n\t\treturn\n\t}\n\n\tapply(primary, 0.76)\n\tapply(primary-1, 0.12)\n\tapply(primary+1, 0.12)\n}\n\nfunc (r *candidateRouter) lookupIssued(rt *route.Route) []edgeUse {\n\tif uses, ok := r.issued[rt]; ok {\n\t\tdelete(r.issued, rt)\n\t\treturn uses\n\t}\n\n\thash := routeHash(rt)\n\tfor issuedRoute, uses := range r.issued {\n\t\tif routeHash(issuedRoute) == hash &&\n\t\t\tdeliveredAmount(issuedRoute) == deliveredAmount(rt) {\n\n\t\t\tdelete(r.issued, issuedRoute)\n\t\t\treturn uses\n\t\t}\n\t}\n\n\tpath := make([]*candidateEdge, 0, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\tfor _, hop := range rt.Hops {\n\t\tvar found *candidateEdge\n\t\tfor _, edge := range r.incomingEdges[hop.PubKeyBytes] {\n\t\t\tif edge.from == from && edge.chanID == hop.ChannelID {\n\t\t\t\tfound = edge\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif found == nil {\n\t\t\treturn nil\n\t\t}\n\t\tpath = append(path, found)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn routeUses(rt, path)\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\tif rt == nil {\n\t\treturn errors.New(\"nil reported route\")\n\t}\n\n\tuses := r.lookupIssued(rt)\n\n\tif result.Failure == nil {\n\t\tfor _, use := range uses {\n\t\t\tr.held[use.edge.key] += use.amount\n\t\t\tr.learnSuccess(use, r.held[use.edge.key])\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.failures++\n\thash := routeHash(rt)\n\tdelivered := deliveredAmount(rt)\n\tif old, exists := r.tried[hash]; !exists || delivered < old {\n\t\tr.tried[hash] = delivered\n\t}\n\n\ttotalDemand := make(map[edgeKey]lnwire.MilliSatoshi, len(uses))\n\tfor _, use := range uses {\n\t\t// Only successful held siblings and this attempt are known to\n\t\t// contribute to the failed demand. Other pending attempts are\n\t\t// excluded because they have not yet proven anything.\n\t\ttotalDemand[use.edge.key] =\n\t\t\tr.held[use.edge.key] + use.amount\n\t}\n\n\treleaseReservations(r.reserved, uses)\n\tr.plan = nil\n\n\tnextCeiling := delivered * 31 / 50\n\tif nextCeiling > 0 &&\n\t\t(r.retryCeiling == 0 || nextCeiling < r.retryCeiling) {\n\n\t\tr.retryCeiling = nextCeiling\n\t}\n\n\tfailNode := findFailureNode(rt, result.FailureSource)\n\tif failNode < 0 {\n\t\t// Unreadable onion failures provide route-level evidence only.\n\t\treturn nil\n\t}\n\n\tcode := result.Failure.Code()\n\tif code == lnwire.CodeTemporaryChannelFailure {\n\t\tr.applyLiquidityBlame(\n\t\t\tuses, totalDemand, failNode, hash,\n\t\t)\n\t\treturn nil\n\t}\n\n\tisPolicy := code == lnwire.CodeFeeInsufficient ||\n\t\tcode == lnwire.CodeIncorrectCltvExpiry\n\tif !isPolicy || len(uses) == 0 {\n\t\treturn nil\n\t}\n\n\tif failNode >= len(uses) {\n\t\tr.policyPenalty[uses[len(uses)-1].edge.key] += 8_000_000\n\t\treturn nil\n\t}\n\n\tr.policyPenalty[uses[failNode].edge.key] += 10_000_000\n\tif failNode > 0 {\n\t\tr.policyPenalty[uses[failNode-1].edge.key] += 1_500_000\n\t}\n\tif failNode+1 < len(uses) {\n\t\tr.policyPenalty[uses[failNode+1].edge.key] += 1_500_000\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 34,
"parent": 0,
"score": 0.5738,
"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 edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype beliefKey struct {\n\tnetwork uint64\n\tedge edgeKey\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupper lnwire.MilliSatoshi\n\n\testimate lnwire.MilliSatoshi\n\tconf float64\n\n\tprobeFail lnwire.MilliSatoshi\n\tfailVotes uint8\n}\n\nvar persistentBeliefs = struct {\n\tsync.RWMutex\n\tvalues map[beliefKey]liquidityBelief\n}{\n\tvalues: make(map[beliefKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\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) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC {\n\t\treturn false\n\t}\n\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn amt <= e.capacity\n}\n\ntype plannedShard struct {\n\troute *route.Route\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tnetworkID uint64\n\n\t// reserved includes successful shards from the current payment. This\n\t// models both atomic holds and liquidity already consumed by settled\n\t// non-atomic shards.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\t// Per-payment evidence affects exploration immediately, while persistent\n\t// hard bounds require corroboration.\n\tsuspicions map[edgeKey]uint8\n\tpenalty map[edgeKey]float64\n\ttried map[string]uint8\n\n\tplan []plannedShard\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[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tsuspicions: make(map[edgeKey]uint8),\n\t\tpenalty: make(map[edgeKey]float64),\n\t\ttried: make(map[string]uint8),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, exists := r.edges[key]; exists {\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: key,\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.\n\t\t\t\t\t\tFeeProportionalMillionths,\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.edges[key] = edge\n\t\t\t\tr.incomingEdges[node] = append(\n\t\t\t\t\tr.incomingEdges[node], edge,\n\t\t\t\t)\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.networkID = graphFingerprint(r.edges)\n\treturn r, nil\n}\n\nfunc graphFingerprint(edges map[edgeKey]*candidateEdge) uint64 {\n\tvar result uint64\n\n\tfor _, edge := range edges {\n\t\th := uint64(1469598103934665603)\n\t\tmix := func(v byte) {\n\t\t\th ^= uint64(v)\n\t\t\th *= 1099511628211\n\t\t}\n\n\t\tfor i := 0; i < 8; i++ {\n\t\t\tmix(byte(edge.key.chanID >> (8 * i)))\n\t\t}\n\t\tfor _, b := range edge.key.from {\n\t\t\tmix(b)\n\t\t}\n\t\tfor _, b := range edge.key.to {\n\t\t\tmix(b)\n\t\t}\n\n\t\tcapacity := uint64(edge.capacity)\n\t\tfor i := 0; i < 8; i++ {\n\t\t\tmix(byte(capacity >> (8 * i)))\n\t\t}\n\n\t\tresult ^= h\n\t}\n\n\tif result == 0 {\n\t\treturn 1\n\t}\n\treturn result\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\n// bimodalPrior models a channel as being either nearly depleted or nearly\n// full. Tiny sends can pass in either mode; medium sends mostly depend on\n// selecting the rich direction; amounts near capacity encounter a cliff.\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.035)\n\thighMode := 1 / (1 + math.Exp((x-0.88)*24))\n\treturn clampProbability(0.50*lowMode + 0.50*highMode)\n}\n\nfunc (r *candidateRouter) getBelief(key edgeKey) liquidityBelief {\n\tpersistentBeliefs.RLock()\n\tb := persistentBeliefs.values[beliefKey{\n\t\tnetwork: r.networkID,\n\t\tedge: key,\n\t}]\n\tpersistentBeliefs.RUnlock()\n\treturn b\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\trequired lnwire.MilliSatoshi) float64 {\n\n\tp := bimodalPrior(required, edge.capacity)\n\tb := r.getBelief(edge.key)\n\n\tif b.lowerOK > 0 && required <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\n\tif b.upper > 0 {\n\t\tif required >= b.upper {\n\t\t\treturn 0.012\n\t\t}\n\n\t\tlo := b.lowerOK\n\t\tif lo >= b.upper {\n\t\t\tlo = 0\n\t\t}\n\n\t\tif required > lo {\n\t\t\tspan := float64(b.upper - lo)\n\t\t\tpos := float64(required-lo) / span\n\t\t\tboundP := 0.995*(1-pos) + 0.012*pos\n\t\t\tweight := math.Min(0.85, 0.35+0.10*b.conf)\n\t\t\tp = (1-weight)*p + weight*boundP\n\t\t}\n\t}\n\n\tif b.estimate > 0 {\n\t\twidth := math.Max(float64(edge.capacity)*0.075, 1_000_000)\n\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t(float64(required)-float64(b.estimate))/width,\n\t\t))\n\t\tweight := math.Min(0.65, b.conf/(b.conf+3))\n\t\tp = (1-weight)*p + weight*estimateP\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) recordSuccess(key edgeKey,\n\trequired lnwire.MilliSatoshi) {\n\n\tpersistentBeliefs.Lock()\n\tbk := beliefKey{network: r.networkID, edge: key}\n\tb := persistentBeliefs.values[bk]\n\n\tif required > b.lowerOK {\n\t\tb.lowerOK = required\n\t}\n\n\testimate := required + required/4\n\tif estimate > b.estimate {\n\t\tb.estimate = estimate\n\t}\n\n\t// Success is truthful. It immediately invalidates any contradictory\n\t// failure ceiling, including one produced by stale or shifted blame.\n\tif b.upper > 0 && required >= b.upper {\n\t\tb.upper = 0\n\t\tb.probeFail = 0\n\t\tb.failVotes = 0\n\t}\n\n\tif b.conf < 8 {\n\t\tb.conf += 1\n\t}\n\tpersistentBeliefs.values[bk] = b\n\tpersistentBeliefs.Unlock()\n}\n\nfunc closeFailureAmounts(a, b lnwire.MilliSatoshi) bool {\n\tif a <= 0 || b <= 0 {\n\t\treturn false\n\t}\n\n\tdelta := a - b\n\tif delta < 0 {\n\t\tdelta = -delta\n\t}\n\n\ttolerance := b / 3\n\tif tolerance < 1_000_000 {\n\t\ttolerance = 1_000_000\n\t}\n\treturn delta <= tolerance\n}\n\n// recordLiquidityFailure quarantines a first report. A hard ceiling is only\n// installed after a second compatible attribution to the same directed edge.\nfunc (r *candidateRouter) recordLiquidityFailure(key edgeKey,\n\trequired lnwire.MilliSatoshi) {\n\n\tpersistentBeliefs.Lock()\n\tbk := beliefKey{network: r.networkID, edge: key}\n\tb := persistentBeliefs.values[bk]\n\n\t// A truthful success outweighs a contradictory, potentially shifted\n\t// failure attribution.\n\tif b.lowerOK > 0 && required <= b.lowerOK {\n\t\tb.probeFail = 0\n\t\tb.failVotes = 0\n\t\tpersistentBeliefs.values[bk] = b\n\t\tpersistentBeliefs.Unlock()\n\t\treturn\n\t}\n\n\tif closeFailureAmounts(required, b.probeFail) {\n\t\tif b.failVotes < 255 {\n\t\t\tb.failVotes++\n\t\t}\n\t\tif required < b.probeFail {\n\t\t\tb.probeFail = required\n\t\t}\n\t} else {\n\t\tb.probeFail = required\n\t\tb.failVotes = 1\n\t}\n\n\tif b.failVotes >= 2 {\n\t\tif b.upper == 0 || b.probeFail < b.upper {\n\t\t\tb.upper = b.probeFail\n\t\t}\n\n\t\testimate := b.probeFail * 2 / 3\n\t\tif b.estimate == 0 || estimate < b.estimate {\n\t\t\tb.estimate = estimate\n\t\t}\n\t\tif b.conf < 8 {\n\t\t\tb.conf += 1\n\t\t}\n\t}\n\n\tpersistentBeliefs.values[bk] = b\n\tpersistentBeliefs.Unlock()\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tscore float64\n\tamount lnwire.MilliSatoshi\n\tindex int\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) 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 *searchQueue) Push(x any) {\n\titem := x.(*searchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) Pop() any {\n\told := *q\n\tn := len(old)\n\titem := old[n-1]\n\t*q = old[:n-1]\n\treturn item\n}\n\ntype routeSearchResult struct {\n\troute *route.Route\n\tscore float64\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]uint8) (*routeSearchResult, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tdist := make(map[route.Vertex]float64)\n\tamountAt := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = 0\n\tamountAt[r.spec.Target] = amt\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamount: amt,\n\t})\n\n\tfeeScale := math.Max(float64(amt)*0.0025, 25_000)\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\t\tbestScore, ok := dist[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amountAt[item.node]\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usableHTLC(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\talready := reserved[edge.key]\n\t\t\tif already > edge.capacity ||\n\t\t\t\tarriving > edge.capacity-already {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := already + arriving\n\t\t\tprobability := r.edgeProbability(edge, required)\n\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tlocal := r.localBalances[edge.key.chanID]\n\t\t\t\tif already > local || arriving > local-already {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprobability = 0.995\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tif fee < 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tincrement := -math.Log(probability)\n\t\t\tincrement += float64(fee) / feeScale\n\t\t\tincrement += 0.012\n\t\t\tincrement += r.penalty[edge.key]\n\n\t\t\tif count := reuse[edge.key]; count > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tincrement += 0.70 * float64(count)\n\t\t\t}\n\n\t\t\tcandidate := item.score + increment\n\t\t\told, exists := dist[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = candidate\n\t\t\tamountAt[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t\tamount: sending,\n\t\t\t})\n\t\t}\n\t}\n\n\tscore, ok := dist[r.source]\n\tif !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, next)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &routeSearchResult{\n\t\troute: rt,\n\t\tscore: score,\n\t}, 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(\"cycle in route\")\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\tnextEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tnextEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = 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: forward,\n\t\t\tOutgoingTimeLock: expiry,\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 routeDeliveredAmount(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 routeEdge(rt *route.Route, index int) (edgeKey,\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn edgeKey{}, 0, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tamt := rt.TotalAmount\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t\tamt = rt.Hops[index-1].AmtToForward\n\t}\n\n\treturn edgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, amt, true\n}\n\nfunc routeSignature(rt *route.Route) string {\n\tif rt == nil {\n\t\treturn \"\"\n\t}\n\n\ts := fmt.Sprintf(\"%d\", routeDeliveredAmount(rt))\n\tfor i := range rt.Hops {\n\t\tkey, _, ok := routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ts += fmt.Sprintf(\"/%d:%x:%x\", key.chanID, key.from, key.to)\n\t}\n\treturn s\n}\n\nfunc copyReservations(src map[edgeKey]lnwire.MilliSatoshi,\n) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc shardAmounts(total lnwire.MilliSatoshi, parts int,\n\tunequal bool) []lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn []lnwire.MilliSatoshi{total}\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, parts)\n\tremaining := total\n\n\tif !unequal {\n\t\tfor i := 0; i < parts; i++ {\n\t\t\tleft := parts - i\n\t\t\tresult[i] = remaining / lnwire.MilliSatoshi(left)\n\t\t\tremaining -= result[i]\n\t\t}\n\t\treturn result\n\t}\n\n\t// A modest front-loaded split lets the best corridor carry more while\n\t// retaining enough parts for weaker parallel corridors.\n\tdenominator := int64(parts*2 + 1)\n\tfor i := 0; i < parts; i++ {\n\t\tif i == parts-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tweight := int64(2)\n\t\tif i == 0 {\n\t\t\tweight = 3\n\t\t}\n\n\t\tresult[i] = lnwire.MilliSatoshi(\n\t\t\tint64(total) * weight / denominator,\n\t\t)\n\t\tremaining -= result[i]\n\t}\n\n\treturn result\n}\n\nfunc (r *candidateRouter) constructPlan(total lnwire.MilliSatoshi,\n\tparts int, unequal bool) ([]plannedShard, float64, error) {\n\n\tamounts := shardAmounts(total, parts, unequal)\n\ttempReserved := copyReservations(r.reserved)\n\treuse := make(map[edgeKey]uint8)\n\tplan := make([]plannedShard, 0, len(amounts))\n\ttotalScore := 0.10 * float64(parts-1)\n\n\tfor _, shardAmt := range amounts {\n\t\tif shardAmt <= 0 {\n\t\t\treturn nil, 0, errors.New(\"invalid shard amount\")\n\t\t}\n\n\t\tfound, err := r.findRoute(shardAmt, tempReserved, reuse)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\tplan = append(plan, plannedShard{\n\t\t\troute: found.route,\n\t\t\tamt: shardAmt,\n\t\t})\n\t\ttotalScore += found.score\n\n\t\tfor i := range found.route.Hops {\n\t\t\tkey, amtOver, ok := routeEdge(found.route, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttempReserved[key] += amtOver\n\t\t\tif reuse[key] < 255 {\n\t\t\t\treuse[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\treturn plan, totalScore, nil\n}\n\nfunc (r *candidateRouter) bestPlan(total lnwire.MilliSatoshi,\n\tpartsLeft uint32) ([]plannedShard, error) {\n\n\tmaxParts := int(partsLeft)\n\tif maxParts < 1 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\tif maxParts > 16 {\n\t\tmaxParts = 16\n\t}\n\n\tvar best []plannedShard\n\tbestScore := math.Inf(1)\n\n\tfor parts := 1; parts <= maxParts; parts++ {\n\t\tfor variant := 0; variant < 2; variant++ {\n\t\t\tif parts == 1 && variant == 1 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tplan, score, err := r.constructPlan(\n\t\t\t\ttotal, parts, variant == 1,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc planTotal(plan []plannedShard) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, shard := range plan {\n\t\ttotal += shard.amt\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) diversifyRepeatedPlan(\n\tplan []plannedShard) bool {\n\n\tif len(plan) == 0 {\n\t\treturn false\n\t}\n\n\tfirst := plan[0].route\n\tsignature := routeSignature(first)\n\trepeats := r.tried[signature]\n\tif repeats < 2 {\n\t\treturn false\n\t}\n\n\tif len(first.Hops) == 0 {\n\t\treturn false\n\t}\n\n\tchoice := int(repeats) % len(first.Hops)\n\tkey, _, ok := routeEdge(first, choice)\n\tif !ok {\n\t\treturn false\n\t}\n\n\tr.penalty[key] += 2.5\n\treturn true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment amount exhausted\")\n\t}\n\n\tif len(r.plan) > 0 && planTotal(r.plan) == amt {\n\t\tnext := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\t\treturn next.route, nil\n\t}\n\tr.plan = nil\n\n\tvar partsLeft uint32\n\tif inFlightHtlcs < r.spec.MaxParts {\n\t\tpartsLeft = r.spec.MaxParts - inFlightHtlcs\n\t}\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"maximum payment parts reached\")\n\t}\n\n\tfor diversification := 0; diversification < 6; diversification++ {\n\t\tplan, err := r.bestPlan(amt, partsLeft)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif r.diversifyRepeatedPlan(plan) {\n\t\t\tcontinue\n\t\t}\n\n\t\tnext := plan[0]\n\t\tr.plan = plan[1:]\n\t\treturn next.route, nil\n\t}\n\n\treturn nil, errors.New(\"no fresh route found\")\n}\n\nfunc failureOutgoingIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\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 nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, amtOver, ok := routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := r.reserved[key] + amtOver\n\t\t\tr.recordSuccess(key, required)\n\t\t\tr.reserved[key] = required\n\n\t\t\tif r.penalty[key] > 0.25 {\n\t\t\t\tr.penalty[key] *= 0.5\n\t\t\t} else {\n\t\t\t\tdelete(r.penalty, key)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tsignature := routeSignature(rt)\n\tif r.tried[signature] < 255 {\n\t\tr.tried[signature]++\n\t}\n\n\t// An off-route source explicitly carries no usable attribution.\n\tfailIndex := failureOutgoingIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\treturn nil\n\t}\n\n\tkey, amtOver, ok := routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tif r.suspicions[key] < 255 {\n\t\t\tr.suspicions[key]++\n\t\t}\n\n\t\t// A single plausible report only nudges route selection. Repeated\n\t\t// compatible reports earn a stronger per-payment penalty and a\n\t\t// corroborated persistent liquidity ceiling.\n\t\tif r.suspicions[key] == 1 {\n\t\t\tr.penalty[key] += 0.45\n\t\t} else {\n\t\t\tr.penalty[key] += 1.10\n\t\t}\n\t\tr.recordLiquidityFailure(\n\t\t\tkey, r.reserved[key]+amtOver,\n\t\t)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// The route was built from current gossip, so a policy failure is\n\t\t// likely stale gossip or shifted blame. Avoid it for this payment\n\t\t// without poisoning persistent liquidity knowledge.\n\t\tr.penalty[key] += 2.0\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 35,
"parent": 5,
"score": 0.2446,
"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\"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(\n\tamt lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\treturn e.baseFeeMsat + amt*e.feeRatePPM/1_000_000\n}\n\nfunc (e *candidateEdge) policyAllows(\n\tamt lnwire.MilliSatoshi) bool {\n\n\tif amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf uint8\n\n\tsuspectAmt lnwire.MilliSatoshi\n\tsuspectWeight float64\n}\n\nvar candidateBeliefStore = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]*candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]*candidateBelief),\n}\n\ntype candidateLocalFailure struct {\n\tupper lnwire.MilliSatoshi\n\tweight float64\n}\n\ntype candidateTraversal struct {\n\tkey candidateEdgeKey\n\tedge *candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tusedTotals map[candidateEdgeKey]lnwire.MilliSatoshi\n\tlocalFails map[candidateEdgeKey]candidateLocalFailure\n\tedgePenalty map[candidateEdgeKey]float64\n\trouteFails map[uint64]uint16\n\n\tplannedParts uint32\n\tfailures uint32\n\tfailuresSinceProgress uint32\n\tunknownFails uint32\n\tsuccessfulParts uint32\n\tretryCap lnwire.MilliSatoshi\n\tdelivered lnwire.MilliSatoshi\n\tsettled bool\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\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tusedTotals: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tlocalFails: make(map[candidateEdgeKey]candidateLocalFailure),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\trouteFails: make(map[uint64]uint16),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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,\n\t\t\tfunc(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\tkey := candidateEdgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tedge := &candidateEdge{\n\t\t\t\t\tkey: key,\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[key] = edge\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tfunc() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tr.plannedParts = r.initialPartCount(spec.Amount)\n\tif spec.MaxParts != 0 && r.plannedParts > spec.MaxParts {\n\t\tr.plannedParts = spec.MaxParts\n\t}\n\tif r.plannedParts == 0 {\n\t\tr.plannedParts = 1\n\t}\n\n\treturn r, nil\n}\n\nfunc (r *candidateRouter) initialPartCount(\n\tamt lnwire.MilliSatoshi) uint32 {\n\n\tswitch {\n\tcase amt <= 15_000_000:\n\t\treturn 1\n\n\tcase amt <= 40_000_000:\n\t\treturn 2\n\n\tcase amt <= 90_000_000:\n\t\treturn 4\n\n\tcase amt <= 220_000_000:\n\t\treturn 8\n\n\tcase amt <= 500_000_000:\n\t\treturn 12\n\n\tdefault:\n\t\treturn 16\n\t}\n}\n\nfunc candidatePrior(amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.003\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\tlowMode := 0.48 * math.Exp(-x/0.025)\n\thighMode := 0.50 / (1 + math.Exp((x-0.90)/0.025))\n\tp := 0.005 + lowMode + highMode\n\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\n\tcase p > 0.985:\n\t\treturn 0.985\n\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc candidateLogisticProbability(amt, estimate,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 {\n\t\treturn 0.005\n\t}\n\n\tscale := 0.065 * float64(capacity)\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\n\tz := (float64(amt) - float64(estimate)) / scale\n\tswitch {\n\tcase z > 30:\n\t\treturn 0.005\n\n\tcase z < -30:\n\t\treturn 0.995\n\n\tdefault:\n\t\treturn 1 / (1 + math.Exp(z))\n\t}\n}\n\nfunc candidateClampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.003:\n\t\treturn 0.003\n\n\tcase p > 0.995:\n\t\treturn 0.995\n\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc (r *candidateRouter) edgeProbability(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\trequired := amt + r.reserved[e.key]\n\tif required > e.capacity {\n\t\treturn 0.001\n\t}\n\n\tif e.key.from == r.source {\n\t\tif r.localBalances[e.key.chanID] < required {\n\t\t\treturn 0.001\n\t\t}\n\n\t\treturn 0.999\n\t}\n\n\tp := candidatePrior(required, e.capacity)\n\n\tcandidateBeliefStore.Lock()\n\tstored := candidateBeliefStore.beliefs[e.key]\n\tvar belief candidateBelief\n\tif stored != nil {\n\t\tbelief = *stored\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tif stored != nil {\n\t\tif belief.estimate > 0 && belief.conf > 0 {\n\t\t\testimateP := candidateLogisticProbability(\n\t\t\t\trequired, belief.estimate, e.capacity,\n\t\t\t)\n\n\t\t\tweight := 0.13 * float64(belief.conf)\n\t\t\tif weight > 0.78 {\n\t\t\t\tweight = 0.78\n\t\t\t}\n\t\t\tp = (1-weight)*p + weight*estimateP\n\t\t}\n\n\t\tif belief.lowerOK > 0 && required <= belief.lowerOK {\n\t\t\tp = math.Max(p, 0.995)\n\t\t}\n\n\t\tif belief.upperFail > 0 {\n\t\t\tswitch {\n\t\t\tcase required >= belief.upperFail:\n\t\t\t\tp = math.Min(p, 0.012)\n\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperFail > belief.lowerOK &&\n\t\t\t\trequired > belief.lowerOK:\n\n\t\t\t\tspan := float64(\n\t\t\t\t\tbelief.upperFail - belief.lowerOK,\n\t\t\t\t)\n\t\t\t\tposition := float64(\n\t\t\t\t\trequired - belief.lowerOK,\n\t\t\t\t) / span\n\t\t\t\tbounded := 0.995*(1-position) +\n\t\t\t\t\t0.012*position\n\t\t\t\tp = 0.30*p + 0.70*bounded\n\t\t\t}\n\t\t}\n\t}\n\n\tif local, ok := r.localFails[e.key]; ok &&\n\t\tlocal.upper > 0 && required >= local.upper {\n\n\t\tp *= math.Exp(-1.75 * local.weight)\n\t}\n\n\treturn candidateClampProbability(p)\n}\n\nfunc (r *candidateRouter) edgeCost(e *candidateEdge,\n\tamt lnwire.MilliSatoshi) float64 {\n\n\tp := r.edgeProbability(e, amt)\n\tcost := -math.Log(p) + 0.016 + r.edgePenalty[e.key]\n\n\tif reserved := r.reserved[e.key]; reserved > 0 &&\n\t\te.capacity > 0 {\n\n\t\tratio := float64(reserved) / float64(e.capacity)\n\t\tcost += 0.12 + 1.10*ratio*ratio\n\t}\n\n\treturn cost\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tscore float64\n\tamt lnwire.MilliSatoshi\n\tnextEdge *candidateEdge\n\tnextLabel *candidatePathLabel\n\tactive bool\n}\n\ntype candidateDijkstraQueue []*candidatePathLabel\n\nfunc (q candidateDijkstraQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateDijkstraQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q candidateDijkstraQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\n\nfunc (q *candidateDijkstraQueue) Push(x any) {\n\t*q = append(*q, x.(*candidatePathLabel))\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\n\treturn item\n}\n\nfunc candidateLabelDominates(a *candidatePathLabel,\n\tscore float64, amt lnwire.MilliSatoshi) bool {\n\n\treturn a.active && a.score <= score+1e-12 && a.amt <= amt\n}\n\nfunc (r *candidateRouter) addPathLabel(\n\tlabels map[route.Vertex][]*candidatePathLabel,\n\tlabel *candidatePathLabel) bool {\n\n\tnodeLabels := labels[label.node]\n\tactive := 0\n\n\tfor _, old := range nodeLabels {\n\t\tif candidateLabelDominates(old, label.score, label.amt) {\n\t\t\treturn false\n\t\t}\n\t\tif old.active {\n\t\t\tactive++\n\t\t}\n\t}\n\n\tfor _, old := range nodeLabels {\n\t\tif old.active &&\n\t\t\tlabel.score <= old.score+1e-12 &&\n\t\t\tlabel.amt <= old.amt {\n\n\t\t\told.active = false\n\t\t\tactive--\n\t\t}\n\t}\n\n\tif active >= 8 {\n\t\tvar worst *candidatePathLabel\n\t\tworstRank := math.Inf(-1)\n\t\tdenominator := float64(r.spec.Amount)\n\t\tif denominator < 1 {\n\t\t\tdenominator = 1\n\t\t}\n\n\t\tfor _, old := range nodeLabels {\n\t\t\tif !old.active {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trank := old.score +\n\t\t\t\t0.20*float64(old.amt)/denominator\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 +\n\t\t\t0.20*float64(label.amt)/denominator\n\t\tif newRank >= worstRank {\n\t\t\treturn false\n\t\t}\n\t\tworst.active = false\n\t}\n\n\tlabels[label.node] = append(nodeLabels, label)\n\treturn true\n}\n\nfunc (r *candidateRouter) findRoute(\n\tamt lnwire.MilliSatoshi) (*route.Route, float64, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, 0, errors.New(\"invalid route amount\")\n\t}\n\n\ttarget := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t\tamt: amt,\n\t\tactive: true,\n\t}\n\n\tlabels := map[route.Vertex][]*candidatePathLabel{\n\t\tr.spec.Target: {target},\n\t}\n\tpq := &candidateDijkstraQueue{}\n\theap.Push(pq, target)\n\n\tvar sourceLabel *candidatePathLabel\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidatePathLabel)\n\t\tif !item.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tsourceLabel = item\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tamtOverEdge := item.amt\n\t\t\tif !edge.policyAllows(amtOverEdge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := amtOverEdge + r.reserved[edge.key]\n\t\t\tif required > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < required {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsending := amtOverEdge\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(amtOverEdge)\n\t\t\t\tsending += fee\n\n\t\t\t\tdenominator := float64(amt)\n\t\t\t\tif denominator < 1 {\n\t\t\t\t\tdenominator = 1\n\t\t\t\t}\n\t\t\t\tfeeCost = 34 * float64(fee) / denominator\n\t\t\t}\n\n\t\t\tlabel := &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: item.score +\n\t\t\t\t\tr.edgeCost(edge, amtOverEdge) +\n\t\t\t\t\tfeeCost,\n\t\t\t\tamt: sending,\n\t\t\t\tnextEdge: edge,\n\t\t\t\tnextLabel: item,\n\t\t\t\tactive: true,\n\t\t\t}\n\t\t\tif !r.addPathLabel(labels, label) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\theap.Push(pq, label)\n\t\t}\n\t}\n\n\tif sourceLabel == nil {\n\t\treturn nil, 0, errors.New(\"no route found\")\n\t}\n\n\trt, err := r.buildRoute(amt, sourceLabel)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tprobability := 1.0\n\tfor _, traversal := range r.routeTraversals(rt) {\n\t\tprobability *= r.edgeProbability(\n\t\t\ttraversal.edge, traversal.amt,\n\t\t)\n\t}\n\n\treturn rt, probability, nil\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tsourceLabel *candidatePathLabel) (*route.Route, error) {\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, fmt.Errorf(\n\t\t\t\t\"broken path at %v\", label.node,\n\t\t\t)\n\t\t}\n\n\t\tpath = append(path, label.nextEdge)\n\t\tlabel = label.nextLabel\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\tforwardingEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tforwardingEdge.fee(amounts[i+1])\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(forwardingEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := amt\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\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 candidateCeilDiv(amt lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn amt\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\treturn (amt + divisor - 1) / divisor\n}\n\nfunc (r *candidateRouter) targetPartCount() uint32 {\n\ttarget := r.plannedParts\n\ttarget += r.failures / 4\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif target > maxParts {\n\t\ttarget = maxParts\n\t}\n\tif target == 0 {\n\t\ttarget = 1\n\t}\n\n\treturn target\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamt, maximum lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amt <= 0 || amt > maximum {\n\t\treturn amounts\n\t}\n\n\tfor _, existing := range amounts {\n\t\tif existing == amt {\n\t\t\treturn amounts\n\t\t}\n\t}\n\n\treturn append(amounts, amt)\n}\n\nfunc candidateRouteFingerprint(rt *route.Route) uint64 {\n\th := uint64(1469598103934665603)\n\tfor _, hop := range rt.Hops {\n\t\th ^= hop.ChannelID\n\t\th *= 1099511628211\n\t}\n\th ^= uint64(candidateDeliveredAmount(rt))\n\th *= 1099511628211\n\n\treturn h\n}\n\ntype candidateRouteChoice struct {\n\troute *route.Route\n\tutility float64\n}\n\nfunc (r *candidateRouter) chooseRoute(\n\tamt lnwire.MilliSatoshi, inFlightHtlcs,\n\tmaxParts uint32) (*route.Route, error) {\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\ttargetParts := r.targetPartCount()\n\n\tdesiredSlots := uint32(1)\n\tif targetParts > inFlightHtlcs {\n\t\tdesiredSlots = targetParts - inFlightHtlcs\n\t}\n\tif desiredSlots > partsLeft {\n\t\tdesiredSlots = partsLeft\n\t}\n\tif desiredSlots == 0 {\n\t\tdesiredSlots = 1\n\t}\n\n\tplanBase := candidateCeilDiv(amt, desiredSlots)\n\n\tfloorDivisor := partsLeft * 3\n\tif floorDivisor < partsLeft {\n\t\tfloorDivisor = partsLeft\n\t}\n\tfloor := candidateCeilDiv(amt, floorDivisor)\n\n\tabsoluteDivisor := maxParts * 6\n\tif absoluteDivisor < maxParts {\n\t\tabsoluteDivisor = maxParts\n\t}\n\tabsoluteFloor := candidateCeilDiv(\n\t\tr.spec.Amount, absoluteDivisor,\n\t)\n\tif absoluteFloor > floor {\n\t\tfloor = absoluteFloor\n\t}\n\tif floor > amt {\n\t\tfloor = amt\n\t}\n\tif floor <= 0 {\n\t\tfloor = 1\n\t}\n\n\tbase := planBase\n\tretrying := r.retryCap > 0\n\tif retrying && r.retryCap < base {\n\t\tbase = r.retryCap\n\t}\n\tif base < floor {\n\t\tbase = floor\n\t}\n\tif base > amt {\n\t\tbase = amt\n\t}\n\n\tvar candidates []lnwire.MilliSatoshi\n\tcandidates = candidateAppendAmount(candidates, base, amt)\n\tcandidates = candidateAppendAmount(\n\t\tcandidates, base*3/4, amt,\n\t)\n\tcandidates = candidateAppendAmount(\n\t\tcandidates, base/2, amt,\n\t)\n\tcandidates = candidateAppendAmount(candidates, floor, amt)\n\n\tif retrying {\n\t\tcandidates = candidateAppendAmount(\n\t\t\tcandidates, r.retryCap, amt,\n\t\t)\n\t\tcandidates = candidateAppendAmount(\n\t\t\tcandidates, planBase, amt,\n\t\t)\n\t} else {\n\t\tcandidates = candidateAppendAmount(\n\t\t\tcandidates, base*5/4, amt,\n\t\t)\n\t\tcandidates = candidateAppendAmount(\n\t\t\tcandidates, base*3/2, amt,\n\t\t)\n\t\tcandidates = candidateAppendAmount(\n\t\t\tcandidates, base*2, amt,\n\t\t)\n\t\tcandidates = candidateAppendAmount(\n\t\t\tcandidates, amt, amt,\n\t\t)\n\t}\n\n\tvar best *candidateRouteChoice\n\tvar lastErr error\n\n\tfor _, candidate := range candidates {\n\t\trt, probability, err := r.findRoute(candidate)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tif probability < 1e-300 {\n\t\t\tprobability = 1e-300\n\t\t}\n\n\t\tprogressRatio := float64(candidate) / float64(planBase)\n\t\tif progressRatio < 1e-9 {\n\t\t\tprogressRatio = 1e-9\n\t\t}\n\n\t\tutility := math.Log(probability) +\n\t\t\t0.76*math.Log(progressRatio)\n\n\t\tif candidate < planBase {\n\t\t\tutility -= 0.16 * math.Log(\n\t\t\t\tfloat64(planBase) / float64(candidate),\n\t\t\t)\n\t\t}\n\n\t\tif retrying && candidate > r.retryCap {\n\t\t\tutility -= 0.34\n\t\t}\n\n\t\tdelivered := candidateDeliveredAmount(rt)\n\t\tif delivered > 0 && rt.TotalAmount > delivered {\n\t\t\tfeeRatio := float64(\n\t\t\t\trt.TotalAmount-delivered,\n\t\t\t) / float64(delivered)\n\t\t\tutility -= 7.0 * feeRatio\n\t\t}\n\n\t\tfingerprint := candidateRouteFingerprint(rt)\n\t\tutility -= 0.48 * float64(r.routeFails[fingerprint])\n\n\t\tif best == nil || utility > best.utility {\n\t\t\tbest = &candidateRouteChoice{\n\t\t\t\troute: rt,\n\t\t\t\tutility: utility,\n\t\t\t}\n\t\t}\n\t}\n\n\tif best != nil {\n\t\treturn best.route, nil\n\t}\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"no route found\")\n\t}\n\n\treturn nil, lastErr\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 is zero\")\n\t}\n\n\tmaxParts := r.spec.MaxParts\n\tif maxParts == 0 {\n\t\tmaxParts = 1\n\t}\n\tif inFlightHtlcs >= maxParts {\n\t\treturn nil, errors.New(\"maximum parts already in flight\")\n\t}\n\n\tif r.failuresSinceProgress >= 64 {\n\t\treturn nil, errors.New(\"route search exhausted\")\n\t}\n\tif r.failures >= 96 {\n\t\treturn nil, errors.New(\"attempt budget exhausted\")\n\t}\n\n\treturn r.chooseRoute(amt, inFlightHtlcs, maxParts)\n}\n\nfunc (r *candidateRouter) routeTraversals(\n\trt *route.Route) []candidateTraversal {\n\n\ttraversals := make(\n\t\t[]candidateTraversal, 0, len(rt.Hops),\n\t)\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkey := candidateEdgeKey{\n\t\t\tchanID: hop.ChannelID,\n\t\t\tfrom: from,\n\t\t\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tfrom = hop.PubKeyBytes\n\t\t\tcontinue\n\t\t}\n\n\t\tamount := rt.TotalAmount\n\t\tif i > 0 {\n\t\t\tamount = rt.Hops[i-1].AmtToForward\n\t\t}\n\n\t\ttraversals = append(traversals, candidateTraversal{\n\t\t\tkey: key,\n\t\t\tedge: edge,\n\t\t\tamt: amount,\n\t\t})\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn traversals\n}\n\nfunc candidateDeliveredAmount(\n\trt *route.Route) lnwire.MilliSatoshi {\n\n\tif len(rt.Hops) == 0 {\n\t\treturn 0\n\t}\n\n\treturn rt.Hops[len(rt.Hops)-1].AmtToForward\n}\n\nfunc (r *candidateRouter) recordSuccess(rt *route.Route) {\n\ttraversals := r.routeTraversals(rt)\n\n\tcandidateBeliefStore.Lock()\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\t\tif belief == nil {\n\t\t\tbelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t\t}\n\n\t\tif required > belief.lowerOK {\n\t\t\tbelief.lowerOK = required\n\t\t}\n\n\t\thighEstimate := traversal.edge.capacity * 88 / 100\n\t\tif required > highEstimate {\n\t\t\thighEstimate = required\n\t\t}\n\t\tif highEstimate > belief.estimate {\n\t\t\tbelief.estimate = highEstimate\n\t\t}\n\t\tif belief.conf < 8 {\n\t\t\tbelief.conf++\n\t\t}\n\n\t\tif belief.upperFail > 0 &&\n\t\t\trequired >= belief.upperFail {\n\n\t\t\tbelief.upperFail = 0\n\t\t}\n\t\tif belief.suspectAmt > 0 &&\n\t\t\trequired >= belief.suspectAmt {\n\n\t\t\tbelief.suspectAmt = 0\n\t\t\tbelief.suspectWeight = 0\n\t\t}\n\t}\n\tcandidateBeliefStore.Unlock()\n\n\tfor _, traversal := range traversals {\n\t\trequired := traversal.amt + r.reserved[traversal.key]\n\n\t\tif local, ok := r.localFails[traversal.key]; ok &&\n\t\t\trequired >= local.upper {\n\n\t\t\tdelete(r.localFails, traversal.key)\n\t\t}\n\n\t\tr.reserved[traversal.key] += traversal.amt\n\t\tr.usedTotals[traversal.key] += traversal.amt\n\t\tr.edgePenalty[traversal.key] *= 0.25\n\t}\n\n\tr.successfulParts++\n\tr.failuresSinceProgress = 0\n\tr.retryCap = 0\n\tr.delivered += candidateDeliveredAmount(rt)\n\n\tif r.delivered >= r.spec.Amount && !r.settled {\n\t\tr.recordSettlement()\n\t\tr.settled = true\n\t}\n}\n\nfunc candidateSubtractFloor(value,\n\tdelta lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value > delta {\n\t\treturn value - delta\n\t}\n\n\treturn 0\n}\n\nfunc candidateShiftUpperDown(value,\n\tdelta lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value == 0 {\n\t\treturn 0\n\t}\n\tif value > delta {\n\t\treturn value - delta\n\t}\n\n\treturn 1\n}\n\nfunc (r *candidateRouter) recordSettlement() {\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tfor key, used := range r.usedTotals {\n\t\tif used <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief := candidateBeliefStore.beliefs[key]; belief != nil {\n\t\t\tbelief.lowerOK = candidateSubtractFloor(\n\t\t\t\tbelief.lowerOK, used,\n\t\t\t)\n\t\t\tbelief.estimate = candidateSubtractFloor(\n\t\t\t\tbelief.estimate, used,\n\t\t\t)\n\t\t\tbelief.upperFail = candidateShiftUpperDown(\n\t\t\t\tbelief.upperFail, used,\n\t\t\t)\n\t\t\tbelief.suspectAmt = candidateSubtractFloor(\n\t\t\t\tbelief.suspectAmt, used,\n\t\t\t)\n\t\t\tif belief.suspectAmt == 0 {\n\t\t\t\tbelief.suspectWeight = 0\n\t\t\t}\n\t\t}\n\n\t\treverse := candidateEdgeKey{\n\t\t\tchanID: key.chanID,\n\t\t\tfrom: key.to,\n\t\t\tto: key.from,\n\t\t}\n\t\treverseBelief := candidateBeliefStore.beliefs[reverse]\n\t\tif reverseBelief == nil {\n\t\t\treverseBelief = &candidateBelief{}\n\t\t\tcandidateBeliefStore.beliefs[reverse] = reverseBelief\n\t\t}\n\n\t\treverseCapacity := lnwire.MilliSatoshi(0)\n\t\tif edge := r.edges[reverse]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t} else if edge := r.edges[key]; edge != nil {\n\t\t\treverseCapacity = edge.capacity\n\t\t}\n\n\t\treverseBelief.lowerOK += used\n\t\treverseBelief.estimate += used\n\n\t\tif reverseCapacity > 0 {\n\t\t\tif reverseBelief.lowerOK > reverseCapacity {\n\t\t\t\treverseBelief.lowerOK = reverseCapacity\n\t\t\t}\n\t\t\tif reverseBelief.estimate > reverseCapacity {\n\t\t\t\treverseBelief.estimate = reverseCapacity\n\t\t\t}\n\t\t}\n\n\t\tif reverseBelief.upperFail > 0 {\n\t\t\treverseBelief.upperFail += used\n\t\t\tif reverseCapacity > 0 &&\n\t\t\t\treverseBelief.upperFail > reverseCapacity {\n\n\t\t\t\treverseBelief.upperFail = reverseCapacity\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) failureTraversalIndex(\n\trt *route.Route, source route.Vertex) (int, bool) {\n\n\tif source == rt.SourcePubKey {\n\t\treturn 0, len(rt.Hops) > 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes != source {\n\t\t\tcontinue\n\t\t}\n\n\t\tindex := i + 1\n\t\tif index >= len(rt.Hops) {\n\t\t\treturn 0, false\n\t\t}\n\n\t\treturn index, true\n\t}\n\n\treturn 0, false\n}\n\nfunc (r *candidateRouter) addLocalFailure(\n\ttraversal candidateTraversal, weight float64) {\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tlocal := r.localFails[traversal.key]\n\tif local.upper == 0 || required < local.upper {\n\t\tlocal.upper = required\n\t}\n\tlocal.weight += weight\n\tif local.weight > 4 {\n\t\tlocal.weight = 4\n\t}\n\tr.localFails[traversal.key] = local\n}\n\nfunc (r *candidateRouter) recordPersistentFailure(\n\ttraversal candidateTraversal, weight float64) {\n\n\tif traversal.key.from == r.source {\n\t\treturn\n\t}\n\n\trequired := traversal.amt + r.reserved[traversal.key]\n\n\tcandidateBeliefStore.Lock()\n\tdefer candidateBeliefStore.Unlock()\n\n\tbelief := candidateBeliefStore.beliefs[traversal.key]\n\tif belief == nil {\n\t\tbelief = &candidateBelief{}\n\t\tcandidateBeliefStore.beliefs[traversal.key] = belief\n\t}\n\n\tif belief.suspectAmt == 0 || required < belief.suspectAmt {\n\t\tbelief.suspectAmt = required\n\t}\n\tbelief.suspectWeight += weight\n\n\tif belief.suspectWeight < 2.05 {\n\t\treturn\n\t}\n\n\tif belief.upperFail == 0 ||\n\t\tbelief.suspectAmt < belief.upperFail {\n\n\t\tbelief.upperFail = belief.suspectAmt\n\t}\n\n\tfailedEstimate := belief.suspectAmt * 68 / 100\n\tif belief.estimate == 0 ||\n\t\tfailedEstimate < belief.estimate {\n\n\t\tbelief.estimate = failedEstimate\n\t}\n\tif belief.conf < 8 {\n\t\tbelief.conf++\n\t}\n}\n\nfunc (r *candidateRouter) recordAttributedLiquidityFailure(\n\ttraversals []candidateTraversal, claimed int) {\n\n\tfor offset := -1; offset <= 1; offset++ {\n\t\tindex := claimed + offset\n\t\tif index < 0 || index >= len(traversals) {\n\t\t\tcontinue\n\t\t}\n\n\t\tweight := 0.17\n\t\tpenalty := 0.14\n\t\tif offset == 0 {\n\t\t\tweight = 0.72\n\t\t\tpenalty = 0.62\n\t\t}\n\n\t\ttraversal := traversals[index]\n\t\tr.addLocalFailure(traversal, weight)\n\t\tr.edgePenalty[traversal.key] += penalty\n\n\t\tif offset == 0 {\n\t\t\tr.recordPersistentFailure(traversal, weight)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) recordPolicyFailure(\n\ttraversals []candidateTraversal, claimed int) {\n\n\tfor offset := -1; offset <= 1; offset++ {\n\t\tindex := claimed + offset\n\t\tif index < 0 || index >= len(traversals) {\n\t\t\tcontinue\n\t\t}\n\n\t\tpenalty := 0.38\n\t\tif offset == 0 {\n\t\t\tpenalty = 2.40\n\t\t}\n\t\tr.edgePenalty[traversals[index].key] += penalty\n\t}\n}\n\nfunc (r *candidateRouter) lowerRetryCap(\n\tdelivered lnwire.MilliSatoshi, factor int64) {\n\n\tif delivered <= 1 {\n\t\treturn\n\t}\n\n\tnext := delivered * lnwire.MilliSatoshi(factor) / 100\n\tif next < 1 {\n\t\tnext = 1\n\t}\n\tif r.retryCap == 0 || next < r.retryCap {\n\t\tr.retryCap = next\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\tif result.Failure == nil {\n\t\tr.recordSuccess(rt)\n\t\treturn nil\n\t}\n\n\tr.failures++\n\tr.failuresSinceProgress++\n\tr.routeFails[candidateRouteFingerprint(rt)]++\n\n\ttraversals := r.routeTraversals(rt)\n\tif len(traversals) != len(rt.Hops) {\n\t\tr.unknownFails++\n\t\tr.lowerRetryCap(candidateDeliveredAmount(rt), 70)\n\t\treturn nil\n\t}\n\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += 0.012\n\t}\n\n\tcode := result.Failure.Code()\n\tclaimed, attributed := r.failureTraversalIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tswitch code {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tif attributed {\n\t\t\tr.recordAttributedLiquidityFailure(\n\t\t\t\ttraversals, claimed,\n\t\t\t)\n\t\t\tr.lowerRetryCap(\n\t\t\t\tcandidateDeliveredAmount(rt), 58,\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tif attributed {\n\t\t\tr.recordPolicyFailure(traversals, claimed)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tr.unknownFails++\n\tr.lowerRetryCap(candidateDeliveredAmount(rt), 70)\n\n\troutePenalty := 0.20\n\tif len(traversals) > 12 {\n\t\troutePenalty = 0.14\n\t}\n\tfor _, traversal := range traversals {\n\t\tr.edgePenalty[traversal.key] += routePenalty\n\t}\n\n\treturn nil\n}"
}
},
{
"id": 36,
"parent": 8,
"score": 0.3071,
"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 edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n\tto route.Vertex\n}\n\ntype beliefKey struct {\n\tnetwork uint64\n\tedge edgeKey\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupper lnwire.MilliSatoshi\n\n\testimate lnwire.MilliSatoshi\n\tconf float64\n\n\tprobeFail lnwire.MilliSatoshi\n\tfailVotes uint8\n}\n\nvar persistentBeliefs = struct {\n\tsync.RWMutex\n\tvalues map[beliefKey]liquidityBelief\n}{\n\tvalues: make(map[beliefKey]liquidityBelief),\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\n\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) usableHTLC(amt lnwire.MilliSatoshi) bool {\n\tif amt <= 0 || amt < e.minHTLC || amt > e.capacity {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype plannedShard struct {\n\troute *route.Route\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[edgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tnetworkID uint64\n\n\t// This is cumulative liquidity already consumed or held by successful\n\t// shards. Planning adds hypothetical sibling reservations on top.\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\n\t// Failure attribution is noisy. A plausible report gets only a soft,\n\t// payment-local penalty; persistent hard bounds require corroboration.\n\tsuspicions map[edgeKey]uint8\n\tpenalty map[edgeKey]float64\n\n\ttriedExact map[string]uint8\n\tpathFails map[string]uint8\n\tfailCount uint32\n\n\tplan []plannedShard\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[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tsuspicions: make(map[edgeKey]uint8),\n\t\tpenalty: make(map[edgeKey]float64),\n\t\ttriedExact: make(map[string]uint8),\n\t\tpathFails: make(map[string]uint8),\n\t}\n\n\tctx := context.Background()\n\tseen := map[route.Vertex]bool{source: true}\n\tqueue := []route.Vertex{source}\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\tkey := edgeKey{\n\t\t\t\t\tchanID: ch.ChannelID,\n\t\t\t\t\tfrom: ch.OtherNode,\n\t\t\t\t\tto: node,\n\t\t\t\t}\n\t\t\t\tif _, ok := r.edges[key]; ok {\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: key,\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.edges[key] = edge\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\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.networkID = graphFingerprint(r.edges)\n\treturn r, nil\n}\n\nfunc graphFingerprint(edges map[edgeKey]*candidateEdge) uint64 {\n\tvar result uint64\n\n\tfor _, edge := range edges {\n\t\th := uint64(1469598103934665603)\n\t\tmix := func(v byte) {\n\t\t\th ^= uint64(v)\n\t\t\th *= 1099511628211\n\t\t}\n\n\t\tfor i := 0; i < 8; i++ {\n\t\t\tmix(byte(edge.key.chanID >> (8 * i)))\n\t\t}\n\t\tfor _, b := range edge.key.from {\n\t\t\tmix(b)\n\t\t}\n\t\tfor _, b := range edge.key.to {\n\t\t\tmix(b)\n\t\t}\n\t\tcapacity := uint64(edge.capacity)\n\t\tfor i := 0; i < 8; i++ {\n\t\t\tmix(byte(capacity >> (8 * i)))\n\t\t}\n\n\t\tresult ^= h\n\t}\n\n\tif result == 0 {\n\t\treturn 1\n\t}\n\treturn result\n}\n\nfunc clampProbability(p float64) float64 {\n\tswitch {\n\tcase p < 0.005:\n\t\treturn 0.005\n\tcase p > 0.995:\n\t\treturn 0.995\n\tdefault:\n\t\treturn p\n\t}\n}\n\nfunc bimodalPrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amt) / float64(capacity)\n\n\t// Either direction is usually almost empty or almost full. Very small\n\t// shards can also pass through the depleted mode.\n\tlowMode := math.Exp(-x / 0.045)\n\thighMode := 1 / (1 + math.Exp((x-0.90)*25))\n\treturn clampProbability(0.48*lowMode + 0.52*highMode)\n}\n\nfunc (r *candidateRouter) getBelief(key edgeKey) liquidityBelief {\n\tpersistentBeliefs.RLock()\n\tb := persistentBeliefs.values[beliefKey{\n\t\tnetwork: r.networkID,\n\t\tedge: key,\n\t}]\n\tpersistentBeliefs.RUnlock()\n\treturn b\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\trequired lnwire.MilliSatoshi) float64 {\n\n\tp := bimodalPrior(required, edge.capacity)\n\tb := r.getBelief(edge.key)\n\n\tif b.lowerOK > 0 && required <= b.lowerOK {\n\t\treturn 0.995\n\t}\n\n\tif b.upper > 0 {\n\t\tif required >= b.upper {\n\t\t\treturn 0.010\n\t\t}\n\n\t\tlo := b.lowerOK\n\t\tif lo >= b.upper {\n\t\t\tlo = 0\n\t\t}\n\t\tif required > lo {\n\t\t\tposition := float64(required-lo) /\n\t\t\t\tfloat64(b.upper-lo)\n\t\t\tboundP := 0.995*(1-position) + 0.010*position\n\t\t\tweight := math.Min(0.84, 0.34+0.10*b.conf)\n\t\t\tp = (1-weight)*p + weight*boundP\n\t\t}\n\t}\n\n\tif b.estimate > 0 {\n\t\twidth := math.Max(\n\t\t\tfloat64(edge.capacity)*0.07, 1_000_000,\n\t\t)\n\t\testimateP := 1 / (1 + math.Exp(\n\t\t\t(float64(required)-float64(b.estimate))/width,\n\t\t))\n\t\tweight := math.Min(0.62, b.conf/(b.conf+3.5))\n\t\tp = (1-weight)*p + weight*estimateP\n\t}\n\n\treturn clampProbability(p)\n}\n\nfunc (r *candidateRouter) recordSuccess(key edgeKey,\n\trequired lnwire.MilliSatoshi) {\n\n\tpersistentBeliefs.Lock()\n\tdefer persistentBeliefs.Unlock()\n\n\tbk := beliefKey{network: r.networkID, edge: key}\n\tb := persistentBeliefs.values[bk]\n\n\tif required > b.lowerOK {\n\t\tb.lowerOK = required\n\t}\n\n\testimate := required + required/3\n\tif estimate > b.estimate {\n\t\tb.estimate = estimate\n\t}\n\n\t// Successful attempts are always trustworthy.\n\tif b.upper > 0 && required >= b.upper {\n\t\tb.upper = 0\n\t\tb.probeFail = 0\n\t\tb.failVotes = 0\n\t}\n\n\tif b.conf < 10 {\n\t\tb.conf++\n\t}\n\tpersistentBeliefs.values[bk] = b\n}\n\nfunc closeFailureAmounts(a, b lnwire.MilliSatoshi) bool {\n\tif a <= 0 || b <= 0 {\n\t\treturn false\n\t}\n\n\tdelta := a - b\n\tif delta < 0 {\n\t\tdelta = -delta\n\t}\n\n\ttolerance := b / 3\n\tif tolerance < 1_000_000 {\n\t\ttolerance = 1_000_000\n\t}\n\treturn delta <= tolerance\n}\n\nfunc (r *candidateRouter) recordLiquidityFailure(key edgeKey,\n\trequired lnwire.MilliSatoshi) {\n\n\tpersistentBeliefs.Lock()\n\tdefer persistentBeliefs.Unlock()\n\n\tbk := beliefKey{network: r.networkID, edge: key}\n\tb := persistentBeliefs.values[bk]\n\n\tif b.lowerOK > 0 && required <= b.lowerOK {\n\t\tb.probeFail = 0\n\t\tb.failVotes = 0\n\t\tpersistentBeliefs.values[bk] = b\n\t\treturn\n\t}\n\n\tif closeFailureAmounts(required, b.probeFail) {\n\t\tif b.failVotes < 255 {\n\t\t\tb.failVotes++\n\t\t}\n\t\tif required < b.probeFail {\n\t\t\tb.probeFail = required\n\t\t}\n\t} else {\n\t\tb.probeFail = required\n\t\tb.failVotes = 1\n\t}\n\n\t// A shifted attribution should not create a hard bound by itself.\n\tif b.failVotes >= 2 {\n\t\tif b.upper == 0 || b.probeFail < b.upper {\n\t\t\tb.upper = b.probeFail\n\t\t}\n\n\t\testimate := b.probeFail * 2 / 3\n\t\tif b.estimate == 0 || estimate < b.estimate {\n\t\t\tb.estimate = estimate\n\t\t}\n\t\tif b.conf < 10 {\n\t\t\tb.conf++\n\t\t}\n\t}\n\n\tpersistentBeliefs.values[bk] = b\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tscore float64\n\tindex int\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q searchQueue) Less(i, j int) bool {\n\treturn q[i].score < q[j].score\n}\n\nfunc (q searchQueue) 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 *searchQueue) Push(x any) {\n\titem := x.(*searchItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *searchQueue) 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 edgeLaneBias(key edgeKey, lane int) float64 {\n\tif lane == 0 {\n\t\treturn 0\n\t}\n\n\th := key.chanID ^ uint64(lane)*0x9e3779b97f4a7c15\n\tfor i := 0; i < 4; i++ {\n\t\th ^= uint64(key.from[i]) << (8 * i)\n\t\th ^= uint64(key.to[i]) << (8 * (3 - i))\n\t\th *= 1099511628211\n\t}\n\n\treturn 0.035 * float64((h>>17)%17)\n}\n\nfunc (r *candidateRouter) findRoute(amt lnwire.MilliSatoshi,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]uint8, reusePenalty float64,\n\tlane int) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\tif r.source == r.spec.Target {\n\t\treturn nil, errors.New(\"source is target\")\n\t}\n\n\tdist := make(map[route.Vertex]float64)\n\tamountAt := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tdist[r.spec.Target] = 0\n\tamountAt[r.spec.Target] = amt\n\n\tpq := &searchQueue{}\n\theap.Push(pq, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tscore: 0,\n\t})\n\n\tfeeScale := math.Max(float64(amt)*0.003, 40_000)\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*searchItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.score > best+1e-12 {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\tbreak\n\t\t}\n\n\t\tarriving := amountAt[item.node]\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif !edge.usableHTLC(arriving) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\talready := reserved[edge.key]\n\t\t\tif already > edge.capacity ||\n\t\t\t\tarriving > edge.capacity-already {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := already + arriving\n\t\t\tprobability := r.edgeProbability(edge, required)\n\n\t\t\tif edge.key.from == r.source {\n\t\t\t\tlocal := r.localBalances[edge.key.chanID]\n\t\t\t\tif already > local || arriving > local-already {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprobability = 0.995\n\t\t\t}\n\n\t\t\tsending := arriving\n\t\t\tfee := lnwire.MilliSatoshi(0)\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee = edge.fee(arriving)\n\t\t\t\tif fee < 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsending += fee\n\t\t\t\tif sending < arriving {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tincrement := -math.Log(probability)\n\t\t\tincrement += float64(fee) / feeScale\n\t\t\tincrement += 0.015\n\t\t\tincrement += r.penalty[edge.key]\n\t\t\tincrement += edgeLaneBias(edge.key, lane)\n\n\t\t\tif count := reuse[edge.key]; count > 0 &&\n\t\t\t\tedge.key.from != r.source {\n\n\t\t\t\tincrement += reusePenalty * float64(count)\n\t\t\t}\n\n\t\t\tcandidate := item.score + increment\n\t\t\told, exists := dist[edge.key.from]\n\t\t\tif exists && candidate >= old {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = candidate\n\t\t\tamountAt[edge.key.from] = sending\n\t\t\tnext[edge.key.from] = edge\n\t\t\theap.Push(pq, &searchItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: candidate,\n\t\t\t})\n\t\t}\n\t}\n\n\tif _, ok := dist[r.source]; !ok {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\treturn r.buildRoute(amt, next)\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(\"cycle in route\")\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\tnextEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\tnextEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(nextEdge.timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tforward := amt\n\t\texpiry := uint32(finalCltvDelta)\n\n\t\tif i < last {\n\t\t\tforward = amtOver[i+1]\n\t\t\texpiry = 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: forward,\n\t\t\tOutgoingTimeLock: expiry,\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 routeDeliveredAmount(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 routeEdge(rt *route.Route, index int) (edgeKey,\n\tlnwire.MilliSatoshi, bool) {\n\n\tif rt == nil || index < 0 || index >= len(rt.Hops) {\n\t\treturn edgeKey{}, 0, false\n\t}\n\n\tfrom := rt.SourcePubKey\n\tamt := rt.TotalAmount\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t\tamt = rt.Hops[index-1].AmtToForward\n\t}\n\n\treturn edgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}, amt, true\n}\n\nfunc routeSignature(rt *route.Route) string {\n\tif rt == nil {\n\t\treturn \"\"\n\t}\n\n\ts := fmt.Sprintf(\"%d\", routeDeliveredAmount(rt))\n\tfor i := range rt.Hops {\n\t\tkey, _, ok := routeEdge(rt, i)\n\t\tif ok {\n\t\t\ts += fmt.Sprintf(\"/%d:%x:%x\",\n\t\t\t\tkey.chanID, key.from, key.to,\n\t\t\t)\n\t\t}\n\t}\n\treturn s\n}\n\nfunc routePathSignature(rt *route.Route) string {\n\tif rt == nil {\n\t\treturn \"\"\n\t}\n\n\ts := \"\"\n\tfor i := range rt.Hops {\n\t\tkey, _, ok := routeEdge(rt, i)\n\t\tif ok {\n\t\t\ts += fmt.Sprintf(\"/%d:%x:%x\",\n\t\t\t\tkey.chanID, key.from, key.to,\n\t\t\t)\n\t\t}\n\t}\n\treturn s\n}\n\nfunc copyReservations(\n\tsrc map[edgeKey]lnwire.MilliSatoshi,\n) map[edgeKey]lnwire.MilliSatoshi {\n\n\tdst := make(map[edgeKey]lnwire.MilliSatoshi, len(src))\n\tfor key, amt := range src {\n\t\tdst[key] = amt\n\t}\n\treturn dst\n}\n\nfunc shardAmounts(total lnwire.MilliSatoshi, parts, scheme int,\n\treverse bool) []lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn []lnwire.MilliSatoshi{total}\n\t}\n\n\tweights := make([]int64, parts)\n\tvar weightTotal int64\n\n\tfor i := 0; i < parts; i++ {\n\t\tswitch scheme {\n\t\tcase 1:\n\t\t\tweights[i] = 2\n\t\t\tif i == 0 {\n\t\t\t\tweights[i] = 3\n\t\t\t}\n\n\t\tcase 2:\n\t\t\t// A two-to-one gradient covers unequal corridors without\n\t\t\t// creating tiny probe shards.\n\t\t\tweights[i] = int64(parts*2 - i)\n\n\t\tdefault:\n\t\t\tweights[i] = 1\n\t\t}\n\t}\n\n\tif reverse {\n\t\tfor i, j := 0, parts-1; i < j; i, j = i+1, j-1 {\n\t\t\tweights[i], weights[j] = weights[j], weights[i]\n\t\t}\n\t}\n\n\tfor _, weight := range weights {\n\t\tweightTotal += weight\n\t}\n\n\tresult := make([]lnwire.MilliSatoshi, parts)\n\tremaining := total\n\tremainingWeight := weightTotal\n\n\tfor i := 0; i < parts; i++ {\n\t\tif i == parts-1 {\n\t\t\tresult[i] = remaining\n\t\t\tbreak\n\t\t}\n\n\t\tresult[i] = lnwire.MilliSatoshi(\n\t\t\tint64(remaining) * weights[i] / remainingWeight,\n\t\t)\n\t\tif result[i] <= 0 {\n\t\t\tresult[i] = 1\n\t\t}\n\n\t\tremaining -= result[i]\n\t\tremainingWeight -= weights[i]\n\t}\n\n\treturn result\n}\n\nfunc (r *candidateRouter) scoreRoute(rt *route.Route,\n\treserved map[edgeKey]lnwire.MilliSatoshi,\n\treuse map[edgeKey]uint8, reusePenalty float64) float64 {\n\n\tdelivered := routeDeliveredAmount(rt)\n\tfeeScale := math.Max(float64(delivered)*0.003, 40_000)\n\tscore := 0.0\n\n\tfor i := range rt.Hops {\n\t\tkey, amtOver, ok := routeEdge(rt, i)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\treturn math.Inf(1)\n\t\t}\n\n\t\trequired := reserved[key] + amtOver\n\t\tp := r.edgeProbability(edge, required)\n\t\tif key.from == r.source {\n\t\t\tp = 0.995\n\t\t}\n\n\t\tscore += -math.Log(p) + 0.015 + r.penalty[key]\n\n\t\tif key.from != r.source {\n\t\t\tscore += float64(edge.fee(amtOver)) / feeScale\n\t\t\tscore += reusePenalty * float64(reuse[key])\n\t\t}\n\t}\n\n\tscore += 0.70 * float64(r.triedExact[routeSignature(rt)])\n\tscore += 0.16 * float64(r.pathFails[routePathSignature(rt)])\n\treturn score\n}\n\nfunc (r *candidateRouter) constructPlan(total lnwire.MilliSatoshi,\n\tparts, scheme int, reverse bool, lane int,\n\treusePenalty float64) ([]plannedShard, float64, error) {\n\n\tamounts := shardAmounts(total, parts, scheme, reverse)\n\ttempReserved := copyReservations(r.reserved)\n\treuse := make(map[edgeKey]uint8)\n\tplan := make([]plannedShard, 0, len(amounts))\n\tscores := make([]float64, 0, len(amounts))\n\n\tfor _, shardAmt := range amounts {\n\t\tif shardAmt <= 0 {\n\t\t\treturn nil, 0, errors.New(\"invalid shard amount\")\n\t\t}\n\n\t\trt, err := r.findRoute(\n\t\t\tshardAmt, tempReserved, reuse,\n\t\t\treusePenalty, lane,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\tscore := r.scoreRoute(\n\t\t\trt, tempReserved, reuse, reusePenalty,\n\t\t)\n\t\tplan = append(plan, plannedShard{\n\t\t\troute: rt,\n\t\t\tamt: shardAmt,\n\t\t})\n\t\tscores = append(scores, score)\n\n\t\tfor i := range rt.Hops {\n\t\t\tkey, amtOver, ok := routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttempReserved[key] += amtOver\n\t\t\tif reuse[key] < 255 {\n\t\t\t\treuse[key]++\n\t\t\t}\n\t\t}\n\t}\n\n\tvar sum, maximum float64\n\tfor _, score := range scores {\n\t\tsum += score\n\t\tif score > maximum {\n\t\t\tmaximum = score\n\t\t}\n\t}\n\n\taverage := sum / float64(len(scores))\n\n\t// Summing all path risks alone strongly biases toward one oversized\n\t// shard. This mixed objective still charges for every added path, while\n\t// allowing a deliberately split route set to win when its shards are\n\t// materially safer or a single route cannot carry the payment.\n\ttotalScore := average + 0.12*maximum + 0.10*sum\n\ttotalScore += 0.07 * float64(parts-1)\n\n\treturn plan, totalScore, nil\n}\n\nfunc candidatePartCounts(minParts, maxParts int) []int {\n\tselected := make(map[int]bool)\n\tfor _, parts := range []int{1, 2, 3, 4, 6, 8, 12, 16} {\n\t\tif parts >= minParts && parts <= maxParts {\n\t\t\tselected[parts] = true\n\t\t}\n\t}\n\tselected[minParts] = true\n\tselected[maxParts] = true\n\n\tresult := make([]int, 0, len(selected))\n\tfor parts := minParts; parts <= maxParts; parts++ {\n\t\tif selected[parts] {\n\t\t\tresult = append(result, parts)\n\t\t}\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) minimumParts(maxParts int) int {\n\tminParts := 1\n\tswitch {\n\tcase r.failCount >= 6:\n\t\tminParts = 8\n\tcase r.failCount >= 3:\n\t\tminParts = 4\n\tcase r.failCount >= 1:\n\t\tminParts = 2\n\t}\n\n\tif minParts > maxParts {\n\t\tminParts = maxParts\n\t}\n\treturn minParts\n}\n\nfunc (r *candidateRouter) bestPlan(total lnwire.MilliSatoshi,\n\tpartsLeft uint32) ([]plannedShard, error) {\n\n\tmaxParts := int(partsLeft)\n\tif maxParts < 1 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\tif maxParts > 16 {\n\t\tmaxParts = 16\n\t}\n\n\tminParts := r.minimumParts(maxParts)\n\tcounts := candidatePartCounts(minParts, maxParts)\n\n\ttype variant struct {\n\t\tscheme int\n\t\treverse bool\n\t\tlane int\n\t\treusePenalty float64\n\t}\n\n\tvariants := []variant{\n\t\t{scheme: 0, lane: 0, reusePenalty: 1.4},\n\t\t{scheme: 0, lane: 1, reusePenalty: 4.0},\n\t\t{scheme: 1, lane: 0, reusePenalty: 2.2},\n\t\t{scheme: 1, reverse: true, lane: 2, reusePenalty: 2.2},\n\t\t{scheme: 2, lane: 3, reusePenalty: 3.2},\n\t}\n\n\tvar best []plannedShard\n\tbestScore := math.Inf(1)\n\n\tfor _, parts := range counts {\n\t\tfor _, variant := range variants {\n\t\t\tif parts == 1 &&\n\t\t\t\t(variant.scheme != 0 || variant.lane > 2) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tplan, score, err := r.constructPlan(\n\t\t\t\ttotal, parts, variant.scheme,\n\t\t\t\tvariant.reverse, variant.lane,\n\t\t\t\tvariant.reusePenalty,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif score < bestScore {\n\t\t\t\tbest = plan\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(best) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc planTotal(plan []plannedShard) lnwire.MilliSatoshi {\n\tvar total lnwire.MilliSatoshi\n\tfor _, shard := range plan {\n\t\ttotal += shard.amt\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) emergencyShard(amt lnwire.MilliSatoshi,\n\tpartsLeft uint32) (*route.Route, error) {\n\n\tif partsLeft == 0 {\n\t\treturn nil, errors.New(\"no payment parts available\")\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(partsLeft)\n\tshard := (amt + divisor - 1) / divisor\n\tif shard <= 0 {\n\t\tshard = amt\n\t}\n\n\tvar best *route.Route\n\tbestScore := math.Inf(1)\n\treuse := make(map[edgeKey]uint8)\n\n\tfor lane := 0; lane < 4; lane++ {\n\t\trt, err := r.findRoute(\n\t\t\tshard, r.reserved, reuse, 0, lane,\n\t\t)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tscore := r.scoreRoute(rt, r.reserved, reuse, 0)\n\t\tif score < bestScore {\n\t\t\tbest = rt\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\tif best == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn best, nil\n}\n\nfunc (r *candidateRouter) 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\n\tif len(r.plan) > 0 && planTotal(r.plan) == amt {\n\t\tnext := r.plan[0]\n\t\tr.plan = r.plan[1:]\n\t\treturn next.route, nil\n\t}\n\tr.plan = nil\n\n\tif inFlightHtlcs >= r.spec.MaxParts {\n\t\treturn nil, errors.New(\"maximum payment parts reached\")\n\t}\n\tpartsLeft := r.spec.MaxParts - inFlightHtlcs\n\n\tplan, err := r.bestPlan(amt, partsLeft)\n\tif err == nil && len(plan) > 0 {\n\t\tnext := plan[0]\n\t\tr.plan = plan[1:]\n\t\treturn next.route, nil\n\t}\n\n\t// Greedy route-set construction can paint itself into a corner even\n\t// though its first average-sized shard is useful. Replanning after that\n\t// shard settles may expose a feasible residual allocation.\n\treturn r.emergencyShard(amt, partsLeft)\n}\n\nfunc failureOutgoingIndex(rt *route.Route,\n\tsource route.Vertex) int {\n\n\tif rt == nil {\n\t\treturn -1\n\t}\n\tif source == rt.SourcePubKey {\n\t\treturn 0\n\t}\n\n\tfor i, hop := range rt.Hops {\n\t\tif hop.PubKeyBytes == source {\n\t\t\treturn i + 1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tif rt == nil {\n\t\treturn nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i := range rt.Hops {\n\t\t\tkey, amtOver, ok := routeEdge(rt, i)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trequired := r.reserved[key] + amtOver\n\t\t\tr.recordSuccess(key, required)\n\t\t\tr.reserved[key] = required\n\n\t\t\tif r.penalty[key] > 0.20 {\n\t\t\t\tr.penalty[key] *= 0.45\n\t\t\t} else {\n\t\t\t\tdelete(r.penalty, key)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tr.failCount++\n\n\texact := routeSignature(rt)\n\tpath := routePathSignature(rt)\n\tif r.triedExact[exact] < 255 {\n\t\tr.triedExact[exact]++\n\t}\n\tif r.pathFails[path] < 255 {\n\t\tr.pathFails[path]++\n\t}\n\n\t// An off-route source explicitly carries no channel information. The\n\t// failed amount still affects future shard sizing, but no edge is blamed.\n\tfailIndex := failureOutgoingIndex(rt, result.FailureSource)\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\treturn nil\n\t}\n\n\tkey, amtOver, ok := routeEdge(rt, failIndex)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tif r.suspicions[key] < 255 {\n\t\t\tr.suspicions[key]++\n\t\t}\n\n\t\tif r.suspicions[key] == 1 {\n\t\t\tr.penalty[key] += 0.28\n\t\t} else {\n\t\t\tr.penalty[key] += 0.90\n\t\t}\n\n\t\tr.recordLiquidityFailure(\n\t\t\tkey, r.reserved[key]+amtOver,\n\t\t)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\t// Gossip-derived routes should satisfy the advertised policy. Treat\n\t\t// this as stale policy or shifted blame and keep it payment-local.\n\t\tr.penalty[key] += 1.8\n\t}\n\n\treturn nil\n}"
}
}
]
}