mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-17 13:06:14 +02:00
184 lines
No EOL
268 KiB
JSON
184 lines
No EOL
268 KiB
JSON
{
|
|
"run_id": "exp018_gepa",
|
|
"reflection_lm": "codex:gpt-5.6-sol",
|
|
"mode": "generalization",
|
|
"status": "complete",
|
|
"seed_score": 0.4507,
|
|
"best_score": 0.9828,
|
|
"iterations": [
|
|
{
|
|
"i": 0,
|
|
"candidate_score": 0.4507,
|
|
"best_score": 0.4507,
|
|
"note": "seed"
|
|
},
|
|
{
|
|
"i": 1,
|
|
"candidate_score": 0.0,
|
|
"best_score": 0.4507,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 2,
|
|
"candidate_score": 0.4109,
|
|
"best_score": 0.4507,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 3,
|
|
"candidate_score": 0.536,
|
|
"best_score": 0.536,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 4,
|
|
"candidate_score": 0.0,
|
|
"best_score": 0.536,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 5,
|
|
"candidate_score": 0.2362,
|
|
"best_score": 0.536,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 6,
|
|
"candidate_score": 0.0,
|
|
"best_score": 0.536,
|
|
"note": "accepted"
|
|
},
|
|
{
|
|
"i": 7,
|
|
"candidate_score": 0.6879,
|
|
"best_score": 0.536,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 8,
|
|
"candidate_score": 0.3658,
|
|
"best_score": 0.536,
|
|
"note": "rejected"
|
|
},
|
|
{
|
|
"i": 9,
|
|
"candidate_score": 0.0,
|
|
"best_score": 0.536,
|
|
"note": "rejected"
|
|
}
|
|
],
|
|
"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 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) usable(amt lnwire.MilliSatoshi,\n\tcheckMin bool) bool {\n\n\tif checkMin && 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\tupperBad lnwire.MilliSatoshi\n\tconf uint8\n}\n\nvar candidateSharedState = struct {\n\tsync.Mutex\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\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tshared map[candidateEdgeKey]candidateBelief\n\tcurrent map[candidateEdgeKey]candidateBelief\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[candidateEdgeKey]float64\n\tpolicyBad map[candidateEdgeKey]bool\n\n\tplan []*route.Route\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\tshared: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrent: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyBad: make(map[candidateEdgeKey]bool),\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 := 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\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: 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.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\tcandidateSharedState.Lock()\n\tfor key := range r.edges {\n\t\tif belief, ok := candidateSharedState.beliefs[key]; ok {\n\t\t\tr.shared[key] = belief\n\t\t}\n\t}\n\tcandidateSharedState.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-ratio/0.025)\n\thighMode := 0.5 / (1 + math.Exp((ratio-0.93)/0.04))\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 candidateEvidenceProbability(b candidateBelief,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\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.005\n\t}\n\n\tif b.lowerOK > 0 && b.upperBad > b.lowerOK {\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(amt-b.lowerOK) / width\n\t\tp := 0.99 - 0.98*pos\n\t\tif p < 0.01 {\n\t\t\treturn 0.01\n\t\t}\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\tif b.upperBad > 0 {\n\t\tratio := float64(amt) / float64(b.upperBad)\n\t\tp := 0.02 + 0.90*math.Exp(-ratio/0.12)\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\tif b.lowerOK > 0 {\n\t\tif capacity <= b.lowerOK {\n\t\t\treturn 0.985\n\t\t}\n\t\tdistance := float64(amt-b.lowerOK) / float64(capacity)\n\t\tp := 0.58 + 0.40*math.Exp(-distance/0.20)\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\treturn candidatePrior(amt, capacity)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\n\tif belief, ok := r.shared[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 1.5)\n\t\tif weight > 0.92 {\n\t\t\tweight = 0.92\n\t\t}\n\t\tp = p*(1-weight) + evidence*weight\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tp = 0.04*p + 0.96*evidence\n\t}\n\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 candidateUpdateBelief(b candidateBelief,\n\tamt lnwire.MilliSatoshi, passed bool) candidateBelief {\n\n\tif passed {\n\t\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t} else {\n\t\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\t\tb.lowerOK = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t}\n\n\tif b.conf < 16 {\n\t\tb.conf++\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learn(key candidateEdgeKey,\n\tcurrentAmt, sharedAmt lnwire.MilliSatoshi, passed bool) {\n\n\tr.current[key] = candidateUpdateBelief(\n\t\tr.current[key], currentAmt, passed,\n\t)\n\tr.shared[key] = candidateUpdateBelief(\n\t\tr.shared[key], sharedAmt, passed,\n\t)\n\n\tcandidateSharedState.Lock()\n\tcandidateSharedState.beliefs[key] = candidateUpdateBelief(\n\t\tcandidateSharedState.beliefs[key], sharedAmt, passed,\n\t)\n\tcandidateSharedState.Unlock()\n}\n\nfunc (r *candidateRouter) hardTotal(edge *candidateEdge) lnwire.MilliSatoshi {\n\tlimit := edge.capacity\n\n\tif edge.key.from == r.source {\n\t\tlocal := r.localBalances[edge.key.chanID]\n\t\tif local < limit {\n\t\t\tlimit = local\n\t\t}\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok &&\n\t\tbelief.upperBad > 0 {\n\n\t\tbound := belief.upperBad - 1\n\t\tif bound < 0 {\n\t\t\tbound = 0\n\t\t}\n\t\tif bound < limit {\n\t\t\tlimit = bound\n\t\t}\n\t}\n\n\treturn limit\n}\n\nfunc candidateRecommendedFromBelief(b candidateBelief,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tvar result lnwire.MilliSatoshi\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tresult = b.lowerOK + (b.upperBad-b.lowerOK)/4\n\n\tcase b.upperBad > 0:\n\t\tresult = b.upperBad * 58 / 100\n\n\tcase b.lowerOK > 0:\n\t\tresult = capacity * 88 / 100\n\t\tif b.lowerOK > result {\n\t\t\tresult = b.lowerOK\n\t\t}\n\n\tdefault:\n\t\tresult = capacity * 78 / 100\n\t}\n\n\tcapLimit := capacity * 98 / 100\n\tif result > capLimit {\n\t\tresult = capLimit\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) recommendedTotal(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\thard := r.hardTotal(edge)\n\tif edge.key.from == r.source {\n\t\treturn hard\n\t}\n\n\tvar result lnwire.MilliSatoshi\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else if belief, ok := r.shared[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else {\n\t\tresult = edge.capacity * 78 / 100\n\t}\n\n\tif result > hard {\n\t\tresult = hard\n\t}\n\treturn result\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tcost float64\n\tarriving 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\treturn q[i].cost < q[j].cost\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) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]float64) ([]*candidateEdge, 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 equals target\")\n\t}\n\n\tdist := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateQueueItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.cost > best {\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] ||\n\t\t\t\t!edge.usable(item.arriving, true) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.arriving + r.reserved[edge.key]\n\t\t\tif total > r.hardTotal(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, total)\n\t\t\triskCost := -math.Log(probability) * 900_000\n\t\t\tedgeCost := riskCost + 2_000 +\n\t\t\t\tr.edgePenalty[edge.key] + diversity[edge.key]\n\n\t\t\tsending := item.arriving\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(item.arriving)\n\t\t\t\tsending += fee\n\t\t\t\tedgeCost += float64(fee)\n\t\t\t}\n\n\t\t\tnewCost := item.cost + edgeCost\n\t\t\toldCost, exists := dist[edge.key.from]\n\t\t\tif exists && newCost >= oldCost {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = newCost\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\tcost: newCost,\n\t\t\t\tarriving: sending,\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\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.key.to\n\t}\n\n\treturn path, nil\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\nfunc candidatePathAmounts(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\tif len(path) == 0 {\n\t\treturn amounts\n\t}\n\n\tamounts[len(path)-1] = finalAmt\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\tnext := path[i+1]\n\t\tamounts[i] = amounts[i+1] + next.fee(amounts[i+1])\n\t}\n\treturn amounts\n}\n\nfunc (r *candidateRouter) pathWithin(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe, checkMin bool) bool {\n\n\tif finalAmt <= 0 || len(path) == 0 {\n\t\treturn false\n\t}\n\n\tamounts := candidatePathAmounts(path, finalAmt)\n\tfor i, edge := range path {\n\t\tif !edge.usable(amounts[i], checkMin) {\n\t\t\treturn false\n\t\t}\n\n\t\tlimit := r.hardTotal(edge)\n\t\tif safe {\n\t\t\tlimit = r.recommendedTotal(edge)\n\t\t}\n\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\t\tif total > limit {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) maxPathAmount(path []*candidateEdge,\n\twant lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe bool) lnwire.MilliSatoshi {\n\n\tif want <= 0 {\n\t\treturn 0\n\t}\n\n\tvar low lnwire.MilliSatoshi\n\thigh := want\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathWithin(path, mid, planned, safe, false) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\tif low == 0 ||\n\t\t!r.pathWithin(path, low, planned, safe, true) {\n\n\t\treturn 0\n\t}\n\treturn low\n}\n\nfunc (r *candidateRouter) pathScore(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tremaining lnwire.MilliSatoshi) float64 {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tlogProbability := 0.0\n\n\tfor i, edge := range path {\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\t\tlogProbability += math.Log(r.edgeProbability(edge, total))\n\t}\n\n\tcoverage := float64(amt) / float64(remaining)\n\tif coverage > 1 {\n\t\tcoverage = 1\n\t}\n\tfee := amounts[0] - amt\n\n\treturn 4*math.Log(coverage) + 0.8*logProbability -\n\t\tfloat64(fee)/5_000_000 - 0.03*float64(len(path))\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty path\")\n\t}\n\n\tamounts := candidatePathAmounts(path, amt)\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].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+1 < len(path) {\n\t\t\tforwardAmt = 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: forwardAmt,\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 candidateAddPlanned(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi) {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tfor i, edge := range path {\n\t\tplanned[edge.key] += amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) allocatePlan(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int,\n\tsafe bool) ([]*route.Route, bool) {\n\n\tremaining := amt\n\tplanned := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tresult := make([]*route.Route, 0, parts)\n\n\tfor len(result) < parts && remaining > 0 {\n\t\tbestIdx := -1\n\t\tvar bestAmount lnwire.MilliSatoshi\n\t\tbestScore := math.Inf(-1)\n\n\t\tfor i, path := range paths {\n\t\t\tmaxAmount := r.maxPathAmount(\n\t\t\t\tpath, remaining, planned, safe,\n\t\t\t)\n\t\t\tif maxAmount == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := r.pathScore(\n\t\t\t\tpath, maxAmount, planned, remaining,\n\t\t\t)\n\t\t\tif score > bestScore {\n\t\t\t\tbestIdx = i\n\t\t\t\tbestAmount = maxAmount\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\n\t\tif bestIdx < 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tpath := paths[bestIdx]\n\t\trt, err := r.buildRoute(path, bestAmount)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tresult = append(result, rt)\n\t\tcandidateAddPlanned(path, bestAmount, planned)\n\t\tremaining -= bestAmount\n\t}\n\n\treturn result, remaining == 0\n}\n\nfunc candidateAppendProbe(probes []lnwire.MilliSatoshi,\n\tprobe lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif probe <= 0 {\n\t\treturn probes\n\t}\n\tfor _, existing := range probes {\n\t\tif existing == probe {\n\t\t\treturn probes\n\t\t}\n\t}\n\treturn append(probes, probe)\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, error) {\n\n\tif parts < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tbase := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar probes []lnwire.MilliSatoshi\n\tprobes = candidateAppendProbe(probes, amt)\n\tprobes = candidateAppendProbe(probes, base)\n\tprobes = candidateAppendProbe(probes, base*3/4)\n\tprobes = candidateAppendProbe(probes, base/2)\n\tprobes = candidateAppendProbe(probes, base/4)\n\tprobes = candidateAppendProbe(probes, base/8)\n\n\trounds := parts + 2\n\tif rounds > 10 {\n\t\trounds = 10\n\t}\n\n\tvar paths [][]*candidateEdge\n\tfor _, probe := range probes {\n\t\tdiversity := make(map[candidateEdgeKey]float64)\n\n\t\tfor i := 0; i < rounds; i++ {\n\t\t\tpath, err := r.findPath(probe, diversity)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tduplicate := false\n\t\t\tfor _, existing := range paths {\n\t\t\t\tif candidateSamePath(existing, path) {\n\t\t\t\t\tduplicate = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !duplicate {\n\t\t\t\tpaths = append(paths, path)\n\t\t\t}\n\n\t\t\tfor _, edge := range path {\n\t\t\t\tdiversity[edge.key] += 2_500_000\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(paths) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tif plan, ok := r.allocatePlan(paths, amt, parts, true); ok {\n\t\treturn plan, nil\n\t}\n\tif plan, ok := r.allocatePlan(paths, amt, parts, false); ok {\n\t\treturn plan, nil\n\t}\n\n\treturn nil, errors.New(\"no route set can carry payment\")\n}\n\nfunc candidateDelivered(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) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\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 reached\")\n\t}\n\n\tif len(r.plan) != 0 {\n\t\tnext := r.plan[0]\n\t\tif candidateDelivered(next) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn next, nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tpartsLeft := int(maxParts - inFlightHtlcs)\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr.plan = plan[1:]\n\treturn plan[0], nil\n}\n\nfunc candidateRouteEdge(rt *route.Route,\n\tindex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n}\n\nfunc candidateRouteAmount(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 candidateFailureIndex(rt *route.Route,\n\tsource 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) learnRoutePassed(rt *route.Route,\n\tend int, reserve bool) {\n\n\tif end > len(rt.Hops) {\n\t\tend = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < end; i++ {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tamt := candidateRouteAmount(rt, i)\n\t\ttotal := amt + r.reserved[key]\n\t\tr.learn(key, total, amt, true)\n\t}\n\n\tif reserve {\n\t\tfor i := 0; i < end; i++ {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.reserved[key] += candidateRouteAmount(rt, i)\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.learnRoutePassed(rt, len(rt.Hops), true)\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tif failIndex > 0 {\n\t\tr.learnRoutePassed(rt, failIndex, false)\n\t}\n\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.edgePenalty[key] += 450_000\n\t\t}\n\t\treturn nil\n\t}\n\n\tkey := candidateRouteEdge(rt, failIndex)\n\tamt := candidateRouteAmount(rt, failIndex)\n\ttotal := amt + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learn(key, total, amt, false)\n\t\tr.edgePenalty[key] += 700_000\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBad[key] = true\n\n\tdefault:\n\t\tr.edgePenalty[key] += 1_500_000\n\t}\n\n\treturn nil\n}"
|
|
},
|
|
"stats": {
|
|
"evals_done": 150,
|
|
"distinct_candidates": 10
|
|
},
|
|
"candidates": [
|
|
{
|
|
"id": 0,
|
|
"parent": null,
|
|
"score": 0.4507,
|
|
"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": "// reflection unavailable: codex exec timed out after 600s (process group killed)"
|
|
}
|
|
},
|
|
{
|
|
"id": 2,
|
|
"parent": 0,
|
|
"score": 0.4109,
|
|
"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\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\ntype edgeKey struct {\n\tchanID uint64\n\tfrom, to route.Vertex\n}\n\ntype candidateEdge struct {\n\tkey edgeKey\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\tif e.maxHTLC != 0 && amt > e.maxHTLC {\n\t\treturn false\n\t}\n\treturn true\n}\n\ntype liquidityBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperFail lnwire.MilliSatoshi\n\testimate lnwire.MilliSatoshi\n\tconf float64\n}\n\nfunc (b *liquidityBelief) observePass(amt,\n\tcapacity lnwire.MilliSatoshi) {\n\n\tif amt > b.lowerOK {\n\t\tb.lowerOK = amt\n\t}\n\tif b.upperFail != 0 && amt >= b.upperFail {\n\t\tb.upperFail = 0\n\t}\n\n\t// With bimodal balances, passing even a moderate HTLC is evidence\n\t// favoring the high-liquidity mode.\n\thighEstimate := capacity * 9 / 10\n\tif highEstimate < amt {\n\t\thighEstimate = amt\n\t}\n\tif b.estimate < highEstimate {\n\t\tb.estimate = highEstimate\n\t}\n\n\tb.conf += 2\n\tif b.conf > 12 {\n\t\tb.conf = 12\n\t}\n}\n\nfunc (b *liquidityBelief) observeFail(amt lnwire.MilliSatoshi) {\n\tif b.lowerOK >= amt {\n\t\tb.lowerOK = 0\n\t}\n\tif b.upperFail == 0 || amt < b.upperFail {\n\t\tb.upperFail = amt\n\t}\n\n\t// A miss is strong evidence for the depleted mode. Preserve any\n\t// lower bound established by already-held shards.\n\tlowEstimate := amt / 8\n\tif lowEstimate < b.lowerOK {\n\t\tlowEstimate = b.lowerOK\n\t}\n\tif b.estimate == 0 || lowEstimate < b.estimate {\n\t\tb.estimate = lowEstimate\n\t}\n\n\tb.conf += 2.5\n\tif b.conf > 12 {\n\t\tb.conf = 12\n\t}\n}\n\ntype rememberedNetwork struct {\n\tbeliefs map[edgeKey]liquidityBelief\n\tlastNow time.Time\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\tnetworks map[uint64]*rememberedNetwork\n}{\n\tnetworks: make(map[uint64]*rememberedNetwork),\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\n\tbeliefs map[edgeKey]liquidityBelief\n\tused map[edgeKey]lnwire.MilliSatoshi\n\tpathPenalty map[edgeKey]float64\n\tpolicyBad map[edgeKey]bool\n\tfailedRoutes map[uint64]uint32\n\n\tdelivered lnwire.MilliSatoshi\n\tcommitted bool\n\tgraphSig 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\tsource: source,\n\t\tspec: spec,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tedgeByKey: make(map[edgeKey]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\tused: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpathPenalty: make(map[edgeKey]float64),\n\t\tpolicyBad: make(map[edgeKey]bool),\n\t\tfailedRoutes: make(map[uint64]uint32),\n\t\tgraphSig: hashVertex(source),\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 _, exists := r.edgeByKey[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: 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[key.to] = append(\n\t\t\t\t\tr.incomingEdges[key.to], edge,\n\t\t\t\t)\n\t\t\t\tr.edgeByKey[key] = edge\n\t\t\t\tr.graphSig ^= edgeFingerprint(edge)\n\n\t\t\t\treturn nil\n\t\t\t}, func() {},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif r.graphSig == 0 {\n\t\tr.graphSig = 1\n\t}\n\n\tnow := view.Now()\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.graphSig]\n\tif memory == nil || (!memory.lastNow.IsZero() &&\n\t\tnow.Before(memory.lastNow)) {\n\n\t\tmemory = &rememberedNetwork{\n\t\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.graphSig] = memory\n\t}\n\tmemory.lastNow = now\n\tfor key, belief := range memory.beliefs {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\n\n\treturn r, nil\n}\n\nfunc hashVertex(vertex route.Vertex) uint64 {\n\tconst offset = uint64(1469598103934665603)\n\tconst prime = uint64(1099511628211)\n\n\thash := offset\n\tfor _, value := range vertex {\n\t\thash ^= uint64(value)\n\t\thash *= prime\n\t}\n\treturn hash\n}\n\nfunc mix64(value uint64) uint64 {\n\tvalue ^= value >> 30\n\tvalue *= 0xbf58476d1ce4e5b9\n\tvalue ^= value >> 27\n\tvalue *= 0x94d049bb133111eb\n\treturn value ^ (value >> 31)\n}\n\nfunc edgeFingerprint(edge *candidateEdge) uint64 {\n\tvalue := edge.key.chanID * 0x9e3779b97f4a7c15\n\tvalue ^= hashVertex(edge.key.from)\n\tvalue ^= mix64(hashVertex(edge.key.to))\n\tvalue ^= mix64(uint64(edge.capacity))\n\treturn mix64(value)\n}\n\nfunc clampProbability(probability float64) float64 {\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn probability\n}\n\nfunc bimodalPrior(amt,\n\tcapacity lnwire.MilliSatoshi) float64 {\n\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\n\t// The first term models the small depleted-side mode. The second\n\t// models the cliff near a nearly full channel.\n\tlowMode := 0.49 * math.Exp(-ratio/0.025)\n\thighMode := 0.49 / (1 + math.Exp((ratio-0.93)/0.025))\n\n\treturn clampProbability(0.005 + lowMode + highMode)\n}\n\nfunc (r *candidateRouter) probability(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) float64 {\n\n\tif totalAmt > edge.capacity {\n\t\treturn 0.005\n\t}\n\n\tif edge.key.from == r.source {\n\t\tif r.localBalances[edge.key.chanID] >= totalAmt {\n\t\t\treturn 0.995\n\t\t}\n\t\treturn 0.005\n\t}\n\n\tprior := bimodalPrior(totalAmt, edge.capacity)\n\tbelief := r.beliefs[edge.key]\n\n\tif belief.upperFail != 0 && totalAmt >= belief.upperFail {\n\t\treturn 0.005\n\t}\n\tif belief.lowerOK != 0 && totalAmt <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.conf == 0 || belief.estimate == 0 {\n\t\treturn prior\n\t}\n\n\tscale := float64(edge.capacity) * 0.08\n\tif scale < 1_000_000 {\n\t\tscale = 1_000_000\n\t}\n\n\tevidence := 1 / (1 + math.Exp(\n\t\t(float64(totalAmt)-float64(belief.estimate))/scale,\n\t))\n\tweight := belief.conf / (belief.conf + 3)\n\n\treturn clampProbability((1-weight)*prior + weight*evidence)\n}\n\ntype dijkstraItem struct {\n\tnode route.Vertex\n\tscore float64\n\tarriving lnwire.MilliSatoshi\n\tpath []*candidateEdge\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].arriving < q[j].arriving\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(value any) {\n\titem := value.(*dijkstraItem)\n\titem.index = len(*q)\n\t*q = append(*q, item)\n}\n\nfunc (q *dijkstraQueue) Pop() any {\n\told := *q\n\tlast := len(old) - 1\n\titem := old[last]\n\told[last] = nil\n\t*q = old[:last]\n\treturn item\n}\n\nfunc prependEdge(edge *candidateEdge,\n\tpath []*candidateEdge) []*candidateEdge {\n\n\tresult := make([]*candidateEdge, len(path)+1)\n\tresult[0] = edge\n\tcopy(result[1:], path)\n\treturn result\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 := map[route.Vertex]float64{\n\t\tr.spec.Target: 0,\n\t}\n\tqueue := &dijkstraQueue{}\n\theap.Push(queue, &dijkstraItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor queue.Len() != 0 {\n\t\titem := heap.Pop(queue).(*dijkstraItem)\n\t\tbestScore, ok := best[item.node]\n\t\tif !ok || item.score > bestScore+1e-12 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif item.node == r.source {\n\t\t\trt, err := r.buildRoute(amt, item.path)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, 0, err\n\t\t\t}\n\t\t\treturn rt, item.score, nil\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.policyBad[edge.key] {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tamtOver := item.arriving\n\t\t\tif !edge.usable(amtOver) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotalExposure := amtOver + r.used[edge.key]\n\t\t\tif totalExposure > edge.capacity {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif edge.key.from == r.source &&\n\t\t\t\tr.localBalances[edge.key.chanID] < totalExposure {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.probability(edge, totalExposure)\n\t\t\trisk := -math.Log(probability)\n\t\t\trisk += r.pathPenalty[edge.key]\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\tsending += fee\n\t\t\t}\n\n\t\t\t// Reliability dominates. Fees and hop count break ties\n\t\t\t// between similarly reliable paths.\n\t\t\tscore := item.score + risk + 0.015 +\n\t\t\t\tfloat64(fee)/5_000_000\n\n\t\t\toldScore, exists := best[edge.key.from]\n\t\t\tif exists && score >= oldScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[edge.key.from] = score\n\t\t\theap.Push(queue, &dijkstraItem{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tscore: score,\n\t\t\t\tarriving: sending,\n\t\t\t\tpath: prependEdge(edge, item.path),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, 0, errors.New(\"no route found\")\n}\n\nfunc (r *candidateRouter) buildRoute(amt lnwire.MilliSatoshi,\n\tpath []*candidateEdge) (*route.Route, error) {\n\n\tconst finalCltvDelta = uint32(40)\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\tif path[0].key.from != r.source ||\n\t\tpath[len(path)-1].key.to != r.spec.Target {\n\n\t\treturn nil, errors.New(\"invalid route path\")\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\toutgoingEdge := path[i+1]\n\t\tamtOver[i] = amtOver[i+1] +\n\t\t\toutgoingEdge.fee(amtOver[i+1])\n\t\texpiryOver[i] = expiryOver[i+1] +\n\t\t\tuint32(outgoingEdge.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\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 routeEdgeAmount(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 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 (r *candidateRouter) routeEdges(rt *route.Route) (\n\t[]*candidateEdge, bool) {\n\n\tif rt == nil {\n\t\treturn nil, false\n\t}\n\n\tedges := make([]*candidateEdge, len(rt.Hops))\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\tto: hop.PubKeyBytes,\n\t\t}\n\t\tedge := r.edgeByKey[key]\n\t\tif edge == nil {\n\t\t\treturn nil, false\n\t\t}\n\t\tedges[i] = edge\n\t\tfrom = hop.PubKeyBytes\n\t}\n\n\treturn edges, true\n}\n\nfunc routeFingerprint(rt *route.Route) uint64 {\n\tif rt == nil {\n\t\treturn 0\n\t}\n\n\thash := mix64(uint64(rt.TotalAmount))\n\tfrom := rt.SourcePubKey\n\tfor _, hop := range rt.Hops {\n\t\tvalue := hop.ChannelID\n\t\tvalue ^= hashVertex(from)\n\t\tvalue ^= mix64(hashVertex(hop.PubKeyBytes))\n\t\thash ^= mix64(value)\n\t\tfrom = hop.PubKeyBytes\n\t}\n\treturn hash\n}\n\nfunc (r *candidateRouter) routeRisk(rt *route.Route) float64 {\n\tedges, ok := r.routeEdges(rt)\n\tif !ok {\n\t\treturn math.Inf(1)\n\t}\n\n\trisk := 0.0\n\tfor i, edge := range edges {\n\t\tamt := routeEdgeAmount(rt, i)\n\t\ttotal := amt + r.used[edge.key]\n\t\trisk -= math.Log(r.probability(edge, total))\n\t\trisk += r.pathPenalty[edge.key]\n\t}\n\n\tif count := r.failedRoutes[routeFingerprint(rt)]; count != 0 {\n\t\trisk += float64(count) * 0.75\n\t}\n\n\treturn risk\n}\n\nfunc minimumShard(remaining lnwire.MilliSatoshi,\n\tparts uint32) lnwire.MilliSatoshi {\n\n\tif parts <= 1 {\n\t\treturn remaining\n\t}\n\n\tdivisor := lnwire.MilliSatoshi(parts)\n\tresult := remaining / divisor\n\tif remaining%divisor != 0 {\n\t\tresult++\n\t}\n\tif result < 1 {\n\t\tresult = 1\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) chooseRoute(remaining lnwire.MilliSatoshi,\n\tpartsLeft uint32, allowSmall bool) (*route.Route, error) {\n\n\tminShard := minimumShard(remaining, partsLeft)\n\tcandidates := make([]lnwire.MilliSatoshi, 0, 16)\n\tseen := make(map[lnwire.MilliSatoshi]bool)\n\n\taddCandidate := func(amt lnwire.MilliSatoshi) {\n\t\tif amt < 1 || amt > remaining || seen[amt] {\n\t\t\treturn\n\t\t}\n\t\tseen[amt] = true\n\t\tcandidates = append(candidates, amt)\n\t}\n\n\taddCandidate(minShard)\n\taddCandidate(remaining)\n\n\tif remaining > minShard {\n\t\tspan := remaining - minShard\n\t\tfor i := int64(1); i < 12; i++ {\n\t\t\taddCandidate(minShard +\n\t\t\t\tspan*lnwire.MilliSatoshi(i)/12)\n\t\t}\n\t}\n\n\tvar bestRoute *route.Route\n\tbestQuality := math.Inf(-1)\n\n\tfor _, shard := range candidates {\n\t\trt, _, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\trisk := r.routeRisk(rt)\n\t\tprogress := math.Log(\n\t\t\tfloat64(shard) / float64(minShard),\n\t\t)\n\t\tfee := rt.TotalAmount - shard\n\t\tif fee < 0 {\n\t\t\tfee = 0\n\t\t}\n\t\tfeeRatio := float64(fee) / float64(shard)\n\n\t\tquality := -risk + 0.72*progress - 3*feeRatio\n\t\tif quality > bestQuality ||\n\t\t\t(quality == bestQuality &&\n\t\t\t\t(bestRoute == nil ||\n\t\t\t\t\tshard > deliveredAmount(bestRoute))) {\n\n\t\t\tbestQuality = quality\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute != nil || !allowSmall || partsLeft <= 1 {\n\t\tif bestRoute == nil {\n\t\t\treturn nil, errors.New(\"no route found\")\n\t\t}\n\t\treturn bestRoute, nil\n\t}\n\n\t// A failed large shard can still reveal a useful lower range.\n\t// Permit one deliberately smaller shard rather than permanently\n\t// excluding the failed corridor.\n\tlower := minShard * 35 / 100\n\tif lower < 1 {\n\t\tlower = 1\n\t}\n\n\tfor i := int64(0); i < 6; i++ {\n\t\tshard := lower\n\t\tif minShard > lower {\n\t\t\tshard += (minShard-lower)*\n\t\t\t\tlnwire.MilliSatoshi(i)/6\n\t\t}\n\t\tif shard >= minShard {\n\t\t\tcontinue\n\t\t}\n\n\t\trt, _, err := r.findRoute(shard)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\trisk := r.routeRisk(rt)\n\t\tfee := rt.TotalAmount - shard\n\t\tif fee < 0 {\n\t\t\tfee = 0\n\t\t}\n\t\tquality := -risk -\n\t\t\t0.35*math.Log(float64(minShard)/float64(shard)) -\n\t\t\t3*float64(fee)/float64(shard)\n\n\t\tif quality > bestQuality {\n\t\t\tbestQuality = quality\n\t\t\tbestRoute = rt\n\t\t}\n\t}\n\n\tif bestRoute == nil {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\treturn bestRoute, nil\n}\n\nfunc (r *candidateRouter) RequestRoute(\n\tamt 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 parts reached\")\n\t}\n\n\tpartsLeft := maxParts - inFlightHtlcs\n\treturn r.chooseRoute(amt, partsLeft, true)\n}\n\nfunc (r *candidateRouter) observeLocalPass(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[edge.key]\n\tbelief.observePass(totalAmt, edge.capacity)\n\tr.beliefs[edge.key] = belief\n}\n\nfunc (r *candidateRouter) observeLocalFail(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tbelief := r.beliefs[edge.key]\n\tbelief.observeFail(totalAmt)\n\tr.beliefs[edge.key] = belief\n}\n\nfunc (r *candidateRouter) rememberFailure(edge *candidateEdge,\n\ttotalAmt lnwire.MilliSatoshi) {\n\n\tcandidateMemory.Lock()\n\tmemory := candidateMemory.networks[r.graphSig]\n\tif memory == nil {\n\t\tmemory = &rememberedNetwork{\n\t\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.graphSig] = memory\n\t}\n\n\tbelief := memory.beliefs[edge.key]\n\tbelief.observeFail(totalAmt)\n\tmemory.beliefs[edge.key] = belief\n\tcandidateMemory.Unlock()\n}\n\nfunc subtractFloor(value,\n\tspent lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif value <= spent {\n\t\treturn 0\n\t}\n\treturn value - spent\n}\n\nfunc (r *candidateRouter) commitPayment() {\n\tif r.committed {\n\t\treturn\n\t}\n\tr.committed = true\n\n\tcandidateMemory.Lock()\n\tdefer candidateMemory.Unlock()\n\n\tmemory := candidateMemory.networks[r.graphSig]\n\tif memory == nil {\n\t\tmemory = &rememberedNetwork{\n\t\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\t}\n\t\tcandidateMemory.networks[r.graphSig] = memory\n\t}\n\n\tfor key, spent := range r.used {\n\t\tedge := r.edgeByKey[key]\n\t\tif edge == nil || spent <= 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tbefore := r.beliefs[key]\n\t\tafter := liquidityBelief{\n\t\t\tlowerOK: subtractFloor(before.lowerOK, spent),\n\t\t\tconf: before.conf,\n\t\t}\n\n\t\tif before.upperFail > spent {\n\t\t\tafter.upperFail = before.upperFail - spent\n\t\t\tafter.estimate = after.upperFail / 8\n\t\t\tif after.estimate < after.lowerOK {\n\t\t\t\tafter.estimate = after.lowerOK\n\t\t\t}\n\t\t} else {\n\t\t\tafter.estimate = subtractFloor(\n\t\t\t\tedge.capacity*9/10, spent,\n\t\t\t)\n\t\t}\n\n\t\tif after.conf < 2 {\n\t\t\tafter.conf = 2\n\t\t}\n\t\tmemory.beliefs[key] = after\n\t}\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\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) penalizeUnknownPath(\n\tedges []*candidateEdge) {\n\n\tfor _, edge := range edges {\n\t\tif edge.key.from == r.source {\n\t\t\tcontinue\n\t\t}\n\t\tr.pathPenalty[edge.key] += 0.8\n\t\tif r.pathPenalty[edge.key] > 12 {\n\t\t\tr.pathPenalty[edge.key] = 12\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(attemptID uint64,\n\trt *route.Route, result routing.SimHtlcResult) error {\n\n\t_ = attemptID\n\n\tedges, ok := r.routeEdges(rt)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, edge := range edges {\n\t\t\tshardAmt := routeEdgeAmount(rt, i)\n\t\t\ttotalAmt := r.used[edge.key] + shardAmt\n\t\t\tr.observeLocalPass(edge, totalAmt)\n\t\t\tr.used[edge.key] += shardAmt\n\n\t\t\tr.pathPenalty[edge.key] *= 0.35\n\t\t\tif r.pathPenalty[edge.key] < 0.01 {\n\t\t\t\tdelete(r.pathPenalty, edge.key)\n\t\t\t}\n\t\t}\n\n\t\tr.delivered += deliveredAmount(rt)\n\t\tif r.delivered >= r.spec.Amount {\n\t\t\tr.commitPayment()\n\t\t}\n\t\treturn nil\n\t}\n\n\tr.failedRoutes[routeFingerprint(rt)]++\n\n\tfailIndex := failureEdgeIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\t// Every channel before the failing node demonstrably forwarded\n\t// this attempt, even though the attempt did not settle.\n\tif failIndex >= 0 {\n\t\tpassCount := failIndex\n\t\tif passCount > len(edges) {\n\t\t\tpassCount = len(edges)\n\t\t}\n\t\tfor i := 0; i < passCount; i++ {\n\t\t\tedge := edges[i]\n\t\t\ttotalAmt := r.used[edge.key] +\n\t\t\t\trouteEdgeAmount(rt, i)\n\t\t\tr.observeLocalPass(edge, totalAmt)\n\t\t}\n\t}\n\n\tif failIndex < 0 || failIndex >= len(edges) {\n\t\tr.penalizeUnknownPath(edges)\n\t\treturn nil\n\t}\n\n\tfailedEdge := edges[failIndex]\n\ttotalAmt := r.used[failedEdge.key] +\n\t\trouteEdgeAmount(rt, failIndex)\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.observeLocalFail(failedEdge, totalAmt)\n\t\tr.rememberFailure(failedEdge, totalAmt)\n\t\tr.pathPenalty[failedEdge.key] += 3\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBad[failedEdge.key] = true\n\n\tdefault:\n\t\tr.pathPenalty[failedEdge.key] += 1.5\n\t}\n\n\tif r.pathPenalty[failedEdge.key] > 16 {\n\t\tr.pathPenalty[failedEdge.key] = 16\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 3,
|
|
"parent": 0,
|
|
"score": 0.536,
|
|
"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) usable(amt lnwire.MilliSatoshi,\n\tcheckMin bool) bool {\n\n\tif checkMin && 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\tupperBad lnwire.MilliSatoshi\n\tconf uint8\n}\n\nvar candidateSharedState = struct {\n\tsync.Mutex\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\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tshared map[candidateEdgeKey]candidateBelief\n\tcurrent map[candidateEdgeKey]candidateBelief\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[candidateEdgeKey]float64\n\tpolicyBad map[candidateEdgeKey]bool\n\n\tplan []*route.Route\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\tshared: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrent: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyBad: make(map[candidateEdgeKey]bool),\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 := 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\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: 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.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\tcandidateSharedState.Lock()\n\tfor key := range r.edges {\n\t\tif belief, ok := candidateSharedState.beliefs[key]; ok {\n\t\t\tr.shared[key] = belief\n\t\t}\n\t}\n\tcandidateSharedState.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-ratio/0.025)\n\thighMode := 0.5 / (1 + math.Exp((ratio-0.93)/0.04))\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 candidateEvidenceProbability(b candidateBelief,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\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.005\n\t}\n\n\tif b.lowerOK > 0 && b.upperBad > b.lowerOK {\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(amt-b.lowerOK) / width\n\t\tp := 0.99 - 0.98*pos\n\t\tif p < 0.01 {\n\t\t\treturn 0.01\n\t\t}\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\tif b.upperBad > 0 {\n\t\tratio := float64(amt) / float64(b.upperBad)\n\t\tp := 0.02 + 0.90*math.Exp(-ratio/0.12)\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\tif b.lowerOK > 0 {\n\t\tif capacity <= b.lowerOK {\n\t\t\treturn 0.985\n\t\t}\n\t\tdistance := float64(amt-b.lowerOK) / float64(capacity)\n\t\tp := 0.58 + 0.40*math.Exp(-distance/0.20)\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\treturn candidatePrior(amt, capacity)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\n\tif belief, ok := r.shared[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 1.5)\n\t\tif weight > 0.92 {\n\t\t\tweight = 0.92\n\t\t}\n\t\tp = p*(1-weight) + evidence*weight\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tp = 0.04*p + 0.96*evidence\n\t}\n\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 candidateUpdateBelief(b candidateBelief,\n\tamt lnwire.MilliSatoshi, passed bool) candidateBelief {\n\n\tif passed {\n\t\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t} else {\n\t\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\t\tb.lowerOK = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t}\n\n\tif b.conf < 16 {\n\t\tb.conf++\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learn(key candidateEdgeKey,\n\tcurrentAmt, sharedAmt lnwire.MilliSatoshi, passed bool) {\n\n\tr.current[key] = candidateUpdateBelief(\n\t\tr.current[key], currentAmt, passed,\n\t)\n\tr.shared[key] = candidateUpdateBelief(\n\t\tr.shared[key], sharedAmt, passed,\n\t)\n\n\tcandidateSharedState.Lock()\n\tcandidateSharedState.beliefs[key] = candidateUpdateBelief(\n\t\tcandidateSharedState.beliefs[key], sharedAmt, passed,\n\t)\n\tcandidateSharedState.Unlock()\n}\n\nfunc (r *candidateRouter) hardTotal(edge *candidateEdge) lnwire.MilliSatoshi {\n\tlimit := edge.capacity\n\n\tif edge.key.from == r.source {\n\t\tlocal := r.localBalances[edge.key.chanID]\n\t\tif local < limit {\n\t\t\tlimit = local\n\t\t}\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok &&\n\t\tbelief.upperBad > 0 {\n\n\t\tbound := belief.upperBad - 1\n\t\tif bound < 0 {\n\t\t\tbound = 0\n\t\t}\n\t\tif bound < limit {\n\t\t\tlimit = bound\n\t\t}\n\t}\n\n\treturn limit\n}\n\nfunc candidateRecommendedFromBelief(b candidateBelief,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tvar result lnwire.MilliSatoshi\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tresult = b.lowerOK + (b.upperBad-b.lowerOK)/4\n\n\tcase b.upperBad > 0:\n\t\tresult = b.upperBad * 58 / 100\n\n\tcase b.lowerOK > 0:\n\t\tresult = capacity * 88 / 100\n\t\tif b.lowerOK > result {\n\t\t\tresult = b.lowerOK\n\t\t}\n\n\tdefault:\n\t\tresult = capacity * 78 / 100\n\t}\n\n\tcapLimit := capacity * 98 / 100\n\tif result > capLimit {\n\t\tresult = capLimit\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) recommendedTotal(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\thard := r.hardTotal(edge)\n\tif edge.key.from == r.source {\n\t\treturn hard\n\t}\n\n\tvar result lnwire.MilliSatoshi\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else if belief, ok := r.shared[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else {\n\t\tresult = edge.capacity * 78 / 100\n\t}\n\n\tif result > hard {\n\t\tresult = hard\n\t}\n\treturn result\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tcost float64\n\tarriving 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\treturn q[i].cost < q[j].cost\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) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]float64) ([]*candidateEdge, 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 equals target\")\n\t}\n\n\tdist := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateQueueItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.cost > best {\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] ||\n\t\t\t\t!edge.usable(item.arriving, true) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.arriving + r.reserved[edge.key]\n\t\t\tif total > r.hardTotal(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, total)\n\t\t\triskCost := -math.Log(probability) * 900_000\n\t\t\tedgeCost := riskCost + 2_000 +\n\t\t\t\tr.edgePenalty[edge.key] + diversity[edge.key]\n\n\t\t\tsending := item.arriving\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(item.arriving)\n\t\t\t\tsending += fee\n\t\t\t\tedgeCost += float64(fee)\n\t\t\t}\n\n\t\t\tnewCost := item.cost + edgeCost\n\t\t\toldCost, exists := dist[edge.key.from]\n\t\t\tif exists && newCost >= oldCost {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = newCost\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\tcost: newCost,\n\t\t\t\tarriving: sending,\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\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.key.to\n\t}\n\n\treturn path, nil\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\nfunc candidatePathAmounts(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\tif len(path) == 0 {\n\t\treturn amounts\n\t}\n\n\tamounts[len(path)-1] = finalAmt\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\tnext := path[i+1]\n\t\tamounts[i] = amounts[i+1] + next.fee(amounts[i+1])\n\t}\n\treturn amounts\n}\n\nfunc (r *candidateRouter) pathWithin(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe, checkMin bool) bool {\n\n\tif finalAmt <= 0 || len(path) == 0 {\n\t\treturn false\n\t}\n\n\tamounts := candidatePathAmounts(path, finalAmt)\n\tfor i, edge := range path {\n\t\tif !edge.usable(amounts[i], checkMin) {\n\t\t\treturn false\n\t\t}\n\n\t\tlimit := r.hardTotal(edge)\n\t\tif safe {\n\t\t\tlimit = r.recommendedTotal(edge)\n\t\t}\n\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\t\tif total > limit {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) maxPathAmount(path []*candidateEdge,\n\twant lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe bool) lnwire.MilliSatoshi {\n\n\tif want <= 0 {\n\t\treturn 0\n\t}\n\n\tvar low lnwire.MilliSatoshi\n\thigh := want\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathWithin(path, mid, planned, safe, false) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\tif low == 0 ||\n\t\t!r.pathWithin(path, low, planned, safe, true) {\n\n\t\treturn 0\n\t}\n\treturn low\n}\n\nfunc (r *candidateRouter) pathScore(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tremaining lnwire.MilliSatoshi) float64 {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tlogProbability := 0.0\n\n\tfor i, edge := range path {\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\t\tlogProbability += math.Log(r.edgeProbability(edge, total))\n\t}\n\n\tcoverage := float64(amt) / float64(remaining)\n\tif coverage > 1 {\n\t\tcoverage = 1\n\t}\n\tfee := amounts[0] - amt\n\n\treturn 4*math.Log(coverage) + 0.8*logProbability -\n\t\tfloat64(fee)/5_000_000 - 0.03*float64(len(path))\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty path\")\n\t}\n\n\tamounts := candidatePathAmounts(path, amt)\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].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+1 < len(path) {\n\t\t\tforwardAmt = 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: forwardAmt,\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 candidateAddPlanned(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi) {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tfor i, edge := range path {\n\t\tplanned[edge.key] += amounts[i]\n\t}\n}\n\nfunc (r *candidateRouter) allocatePlan(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int,\n\tsafe bool) ([]*route.Route, bool) {\n\n\tremaining := amt\n\tplanned := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tresult := make([]*route.Route, 0, parts)\n\n\tfor len(result) < parts && remaining > 0 {\n\t\tbestIdx := -1\n\t\tvar bestAmount lnwire.MilliSatoshi\n\t\tbestScore := math.Inf(-1)\n\n\t\tfor i, path := range paths {\n\t\t\tmaxAmount := r.maxPathAmount(\n\t\t\t\tpath, remaining, planned, safe,\n\t\t\t)\n\t\t\tif maxAmount == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := r.pathScore(\n\t\t\t\tpath, maxAmount, planned, remaining,\n\t\t\t)\n\t\t\tif score > bestScore {\n\t\t\t\tbestIdx = i\n\t\t\t\tbestAmount = maxAmount\n\t\t\t\tbestScore = score\n\t\t\t}\n\t\t}\n\n\t\tif bestIdx < 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tpath := paths[bestIdx]\n\t\trt, err := r.buildRoute(path, bestAmount)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tresult = append(result, rt)\n\t\tcandidateAddPlanned(path, bestAmount, planned)\n\t\tremaining -= bestAmount\n\t}\n\n\treturn result, remaining == 0\n}\n\nfunc candidateAppendProbe(probes []lnwire.MilliSatoshi,\n\tprobe lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif probe <= 0 {\n\t\treturn probes\n\t}\n\tfor _, existing := range probes {\n\t\tif existing == probe {\n\t\t\treturn probes\n\t\t}\n\t}\n\treturn append(probes, probe)\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, error) {\n\n\tif parts < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tbase := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar probes []lnwire.MilliSatoshi\n\tprobes = candidateAppendProbe(probes, amt)\n\tprobes = candidateAppendProbe(probes, base)\n\tprobes = candidateAppendProbe(probes, base*3/4)\n\tprobes = candidateAppendProbe(probes, base/2)\n\tprobes = candidateAppendProbe(probes, base/4)\n\tprobes = candidateAppendProbe(probes, base/8)\n\n\trounds := parts + 2\n\tif rounds > 10 {\n\t\trounds = 10\n\t}\n\n\tvar paths [][]*candidateEdge\n\tfor _, probe := range probes {\n\t\tdiversity := make(map[candidateEdgeKey]float64)\n\n\t\tfor i := 0; i < rounds; i++ {\n\t\t\tpath, err := r.findPath(probe, diversity)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tduplicate := false\n\t\t\tfor _, existing := range paths {\n\t\t\t\tif candidateSamePath(existing, path) {\n\t\t\t\t\tduplicate = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !duplicate {\n\t\t\t\tpaths = append(paths, path)\n\t\t\t}\n\n\t\t\tfor _, edge := range path {\n\t\t\t\tdiversity[edge.key] += 2_500_000\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(paths) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tif plan, ok := r.allocatePlan(paths, amt, parts, true); ok {\n\t\treturn plan, nil\n\t}\n\tif plan, ok := r.allocatePlan(paths, amt, parts, false); ok {\n\t\treturn plan, nil\n\t}\n\n\treturn nil, errors.New(\"no route set can carry payment\")\n}\n\nfunc candidateDelivered(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) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\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 reached\")\n\t}\n\n\tif len(r.plan) != 0 {\n\t\tnext := r.plan[0]\n\t\tif candidateDelivered(next) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn next, nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tpartsLeft := int(maxParts - inFlightHtlcs)\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tr.plan = plan[1:]\n\treturn plan[0], nil\n}\n\nfunc candidateRouteEdge(rt *route.Route,\n\tindex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n}\n\nfunc candidateRouteAmount(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 candidateFailureIndex(rt *route.Route,\n\tsource 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) learnRoutePassed(rt *route.Route,\n\tend int, reserve bool) {\n\n\tif end > len(rt.Hops) {\n\t\tend = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < end; i++ {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tamt := candidateRouteAmount(rt, i)\n\t\ttotal := amt + r.reserved[key]\n\t\tr.learn(key, total, amt, true)\n\t}\n\n\tif reserve {\n\t\tfor i := 0; i < end; i++ {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.reserved[key] += candidateRouteAmount(rt, i)\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.learnRoutePassed(rt, len(rt.Hops), true)\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tif failIndex > 0 {\n\t\tr.learnRoutePassed(rt, failIndex, false)\n\t}\n\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.edgePenalty[key] += 450_000\n\t\t}\n\t\treturn nil\n\t}\n\n\tkey := candidateRouteEdge(rt, failIndex)\n\tamt := candidateRouteAmount(rt, failIndex)\n\ttotal := amt + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learn(key, total, amt, false)\n\t\tr.edgePenalty[key] += 700_000\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBad[key] = true\n\n\tdefault:\n\t\tr.edgePenalty[key] += 1_500_000\n\t}\n\n\treturn nil\n}"
|
|
},
|
|
"role": "best"
|
|
},
|
|
{
|
|
"id": 4,
|
|
"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\"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) usable(amt lnwire.MilliSatoshi,\n\tcheckMin bool) bool {\n\n\tif checkMin && 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\tupperBad lnwire.MilliSatoshi\n\tconf uint8\n}\n\nvar candidateSharedState = struct {\n\tsync.Mutex\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\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tshared map[candidateEdgeKey]candidateBelief\n\tcurrent map[candidateEdgeKey]candidateBelief\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[candidateEdgeKey]float64\n\tliquidityFail map[candidateEdgeKey]uint8\n\tpolicyBad map[candidateEdgeKey]bool\n\tfailedRoutes map[uint64]uint8\n\n\tplan []*route.Route\n\tattempts 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\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\tshared: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrent: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tliquidityFail: make(map[candidateEdgeKey]uint8),\n\t\tpolicyBad: make(map[candidateEdgeKey]bool),\n\t\tfailedRoutes: make(map[uint64]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 := 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\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: 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.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\tcandidateSharedState.Lock()\n\tfor key := range r.edges {\n\t\tif belief, ok := candidateSharedState.beliefs[key]; ok {\n\t\t\tr.shared[key] = belief\n\t\t}\n\t}\n\tcandidateSharedState.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-ratio/0.025)\n\thighMode := 0.5 / (1 + math.Exp((ratio-0.93)/0.04))\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 candidateEvidenceProbability(b candidateBelief,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\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.005\n\t}\n\n\tif b.lowerOK > 0 && b.upperBad > b.lowerOK {\n\t\twidth := float64(b.upperBad - b.lowerOK)\n\t\tpos := float64(amt-b.lowerOK) / width\n\t\tp := 0.99 - 0.98*pos\n\t\tif p < 0.01 {\n\t\t\treturn 0.01\n\t\t}\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\tif b.upperBad > 0 {\n\t\tratio := float64(amt) / float64(b.upperBad)\n\t\tp := 0.015 + 0.92*math.Exp(-ratio/0.16)\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\tif b.lowerOK > 0 {\n\t\tif capacity <= b.lowerOK {\n\t\t\treturn 0.985\n\t\t}\n\t\tdistance := float64(amt-b.lowerOK) / float64(capacity)\n\t\tp := 0.60 + 0.38*math.Exp(-distance/0.20)\n\t\tif p > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn p\n\t}\n\n\treturn candidatePrior(amt, capacity)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tp := candidatePrior(total, edge.capacity)\n\n\tif belief, ok := r.shared[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 1.75)\n\t\tif weight > 0.90 {\n\t\t\tweight = 0.90\n\t\t}\n\t\tp = p*(1-weight) + evidence*weight\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tp = 0.03*p + 0.97*evidence\n\t}\n\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 candidateUpdateBelief(b candidateBelief,\n\tamt lnwire.MilliSatoshi, passed bool) candidateBelief {\n\n\tif passed {\n\t\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t} else {\n\t\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\t\tb.lowerOK = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t}\n\n\tif b.conf < 16 {\n\t\tb.conf++\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learn(key candidateEdgeKey,\n\tcurrentAmt, sharedAmt lnwire.MilliSatoshi, passed bool) {\n\n\tr.current[key] = candidateUpdateBelief(\n\t\tr.current[key], currentAmt, passed,\n\t)\n\tr.shared[key] = candidateUpdateBelief(\n\t\tr.shared[key], sharedAmt, passed,\n\t)\n\n\tcandidateSharedState.Lock()\n\tcandidateSharedState.beliefs[key] = candidateUpdateBelief(\n\t\tcandidateSharedState.beliefs[key], sharedAmt, passed,\n\t)\n\tcandidateSharedState.Unlock()\n\n\tif passed {\n\t\tr.liquidityFail[key] = 0\n\t\tr.edgePenalty[key] *= 0.35\n\t}\n}\n\nfunc candidateRetryLimit(b candidateBelief) lnwire.MilliSatoshi {\n\tif b.upperBad <= 0 {\n\t\treturn 0\n\t}\n\n\tif b.lowerOK > 0 && b.lowerOK < b.upperBad {\n\t\tgap := b.upperBad - b.lowerOK\n\t\treturn b.lowerOK + gap*2/3\n\t}\n\n\treturn b.upperBad * 58 / 100\n}\n\nfunc (r *candidateRouter) hardTotal(edge *candidateEdge) lnwire.MilliSatoshi {\n\tlimit := edge.capacity\n\n\tif edge.key.from == r.source {\n\t\tlocal := r.localBalances[edge.key.chanID]\n\t\tif local < limit {\n\t\t\tlimit = local\n\t\t}\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok &&\n\t\tbelief.upperBad > 0 {\n\n\t\tbound := candidateRetryLimit(belief)\n\t\tif bound < limit {\n\t\t\tlimit = bound\n\t\t}\n\t}\n\n\treturn limit\n}\n\nfunc candidateRecommendedFromBelief(b candidateBelief,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tvar result lnwire.MilliSatoshi\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tresult = b.lowerOK + (b.upperBad-b.lowerOK)/3\n\n\tcase b.upperBad > 0:\n\t\tresult = b.upperBad * 58 / 100\n\n\tcase b.lowerOK > 0:\n\t\tresult = capacity * 88 / 100\n\t\tif b.lowerOK > result {\n\t\t\tresult = b.lowerOK\n\t\t}\n\n\tdefault:\n\t\tresult = capacity * 78 / 100\n\t}\n\n\tcapLimit := capacity * 98 / 100\n\tif result > capLimit {\n\t\tresult = capLimit\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) recommendedTotal(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\thard := r.hardTotal(edge)\n\tif edge.key.from == r.source {\n\t\treturn hard\n\t}\n\n\tvar result lnwire.MilliSatoshi\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else if belief, ok := r.shared[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else {\n\t\tresult = edge.capacity * 78 / 100\n\t}\n\n\tif result > hard {\n\t\tresult = hard\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) edgeBlocked(edge *candidateEdge) bool {\n\tif r.policyBad[edge.key] {\n\t\treturn true\n\t}\n\n\tfailures := r.liquidityFail[edge.key]\n\tif failures < 5 {\n\t\treturn false\n\t}\n\n\tbelief := r.current[edge.key]\n\treturn belief.lowerOK == 0\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tcost float64\n\tarriving 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\treturn q[i].cost < q[j].cost\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) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]float64,\n\tcheckMin bool) ([]*candidateEdge, 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 equals target\")\n\t}\n\n\tdist := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() != 0 {\n\t\titem := heap.Pop(pq).(*candidateQueueItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.cost > best {\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.edgeBlocked(edge) ||\n\t\t\t\t!edge.usable(item.arriving, checkMin) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttotal := item.arriving + r.reserved[edge.key]\n\t\t\tif total > r.hardTotal(edge) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, total)\n\t\t\triskCost := -math.Log(probability) * 825_000\n\t\t\tfailCost := float64(r.liquidityFail[edge.key]) *\n\t\t\t\t350_000\n\t\t\tedgeCost := riskCost + 3_000 +\n\t\t\t\tfailCost + r.edgePenalty[edge.key] +\n\t\t\t\tdiversity[edge.key]\n\n\t\t\tsending := item.arriving\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(item.arriving)\n\t\t\t\tsending += fee\n\t\t\t\tedgeCost += float64(fee)\n\t\t\t}\n\n\t\t\tnewCost := item.cost + edgeCost\n\t\t\toldCost, exists := dist[edge.key.from]\n\t\t\tif exists && newCost >= oldCost {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = newCost\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\tcost: newCost,\n\t\t\t\tarriving: sending,\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\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.key.to\n\t}\n\n\treturn path, nil\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\nfunc candidatePathAmounts(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\tif len(path) == 0 {\n\t\treturn amounts\n\t}\n\n\tamounts[len(path)-1] = finalAmt\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\tnext := path[i+1]\n\t\tamounts[i] = amounts[i+1] + next.fee(amounts[i+1])\n\t}\n\treturn amounts\n}\n\nfunc (r *candidateRouter) pathWithin(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe, checkMin bool) bool {\n\n\tif finalAmt <= 0 || len(path) == 0 {\n\t\treturn false\n\t}\n\n\tamounts := candidatePathAmounts(path, finalAmt)\n\tfor i, edge := range path {\n\t\tif r.edgeBlocked(edge) ||\n\t\t\t!edge.usable(amounts[i], checkMin) {\n\n\t\t\treturn false\n\t\t}\n\n\t\tlimit := r.hardTotal(edge)\n\t\tif safe {\n\t\t\tlimit = r.recommendedTotal(edge)\n\t\t}\n\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\t\tif total > limit {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) maxPathAmount(path []*candidateEdge,\n\twant lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe bool) lnwire.MilliSatoshi {\n\n\tif want <= 0 {\n\t\treturn 0\n\t}\n\n\tvar low lnwire.MilliSatoshi\n\thigh := want\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathWithin(path, mid, planned, safe, false) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\tif low == 0 ||\n\t\t!r.pathWithin(path, low, planned, safe, true) {\n\n\t\treturn 0\n\t}\n\n\treturn low\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty path\")\n\t}\n\n\tamounts := candidatePathAmounts(path, amt)\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = finalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].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+1 < len(path) {\n\t\t\tforwardAmt = 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: forwardAmt,\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 candidateAddPlanned(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi) {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tfor i, edge := range path {\n\t\tplanned[edge.key] += amounts[i]\n\t}\n}\n\nfunc candidateClonePlanned(\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 candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamt lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amt <= 0 {\n\t\treturn amounts\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\ntype candidatePlanPiece struct {\n\tpath []*candidateEdge\n\tamt lnwire.MilliSatoshi\n}\n\ntype candidatePlanState struct {\n\tpieces []candidatePlanPiece\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi\n\tremaining lnwire.MilliSatoshi\n\tlastPath int\n\trank float64\n}\n\nfunc (r *candidateRouter) planQuality(state *candidatePlanState,\n\toriginal lnwire.MilliSatoshi, complete bool) float64 {\n\n\tlogProbability := 0.0\n\tfor key, amount := range state.planned {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttotal := amount + r.reserved[key]\n\t\tlogProbability += math.Log(r.edgeProbability(edge, total))\n\t}\n\n\tvar fees lnwire.MilliSatoshi\n\thops := 0\n\tfor _, piece := range state.pieces {\n\t\tamounts := candidatePathAmounts(piece.path, piece.amt)\n\t\tif len(amounts) != 0 {\n\t\t\tfees += amounts[0] - piece.amt\n\t\t}\n\t\thops += len(piece.path)\n\t}\n\n\tpartCost := 0.10 * float64(len(state.pieces))\n\thopCost := 0.018 * float64(hops)\n\tfeeCost := float64(fees) / 2_500_000\n\n\tif complete {\n\t\treturn 3.0*logProbability - feeCost -\n\t\t\thopCost - partCost\n\t}\n\n\tcovered := float64(original-state.remaining) / float64(original)\n\treturn 250*covered + 1.6*logProbability -\n\t\tfeeCost - hopCost - partCost\n}\n\nfunc (r *candidateRouter) stateRoutes(\n\tstate *candidatePlanState) ([]*route.Route, error) {\n\n\tresult := make([]*route.Route, 0, len(state.pieces))\n\tfor _, piece := range state.pieces {\n\t\trt, err := r.buildRoute(piece.path, piece.amt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult = append(result, rt)\n\t}\n\treturn result, nil\n}\n\nfunc (r *candidateRouter) beamPlan(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int,\n\tsafe bool) ([]*route.Route, bool) {\n\n\tif parts < 1 {\n\t\treturn nil, false\n\t}\n\n\tbeam := []*candidatePlanState{{\n\t\tplanned: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tremaining: amt,\n\t}}\n\n\tvar best *candidatePlanState\n\tbestQuality := math.Inf(-1)\n\n\tfor depth := 0; depth < parts && len(beam) != 0; depth++ {\n\t\tvar expanded []*candidatePlanState\n\n\t\tfor _, state := range beam {\n\t\t\tslots := parts - len(state.pieces)\n\t\t\tif slots <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\taverage := (state.remaining +\n\t\t\t\tlnwire.MilliSatoshi(slots) - 1) /\n\t\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\t\tfor pathIndex := state.lastPath;\n\t\t\t\tpathIndex < len(paths); pathIndex++ {\n\n\t\t\t\tpath := paths[pathIndex]\n\t\t\t\tmaxAmount := r.maxPathAmount(\n\t\t\t\t\tpath, state.remaining,\n\t\t\t\t\tstate.planned, safe,\n\t\t\t\t)\n\t\t\t\tif maxAmount == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvar choices []lnwire.MilliSatoshi\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount,\n\t\t\t\t)\n\t\t\t\tif average <= maxAmount {\n\t\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\t\tchoices, average,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount*3/4,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount/2,\n\t\t\t\t)\n\n\t\t\t\tfor _, shard := range choices {\n\t\t\t\t\tif shard > state.remaining ||\n\t\t\t\t\t\t!r.pathWithin(\n\t\t\t\t\t\t\tpath, shard, state.planned,\n\t\t\t\t\t\t\tsafe, true,\n\t\t\t\t\t\t) {\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tplanned := candidateClonePlanned(\n\t\t\t\t\t\tstate.planned,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateAddPlanned(path, shard, planned)\n\n\t\t\t\t\tpieces := append(\n\t\t\t\t\t\t[]candidatePlanPiece(nil),\n\t\t\t\t\t\tstate.pieces...,\n\t\t\t\t\t)\n\t\t\t\t\tpieces = append(pieces, candidatePlanPiece{\n\t\t\t\t\t\tpath: path,\n\t\t\t\t\t\tamt: shard,\n\t\t\t\t\t})\n\n\t\t\t\t\tnext := &candidatePlanState{\n\t\t\t\t\t\tpieces: pieces,\n\t\t\t\t\t\tplanned: planned,\n\t\t\t\t\t\tremaining: state.remaining - shard,\n\t\t\t\t\t\tlastPath: pathIndex,\n\t\t\t\t\t}\n\n\t\t\t\t\tif next.remaining == 0 {\n\t\t\t\t\t\tquality := r.planQuality(\n\t\t\t\t\t\t\tnext, amt, true,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif quality > bestQuality {\n\t\t\t\t\t\t\tbest = next\n\t\t\t\t\t\t\tbestQuality = quality\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnext.rank = r.planQuality(\n\t\t\t\t\t\tnext, amt, false,\n\t\t\t\t\t)\n\t\t\t\t\texpanded = append(expanded, next)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(expanded, func(i, j int) bool {\n\t\t\treturn expanded[i].rank > expanded[j].rank\n\t\t})\n\t\tif len(expanded) > 72 {\n\t\t\texpanded = expanded[:72]\n\t\t}\n\t\tbeam = expanded\n\t}\n\n\tif best == nil {\n\t\treturn nil, false\n\t}\n\n\troutes, err := r.stateRoutes(best)\n\tif err != nil {\n\t\treturn nil, false\n\t}\n\treturn routes, true\n}\n\nfunc (r *candidateRouter) fallbackRoute(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int) (*route.Route, error) {\n\n\tif parts < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tneeded := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\tplanned := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\n\tvar bestPath []*candidateEdge\n\tvar bestAmount lnwire.MilliSatoshi\n\tbestScore := math.Inf(-1)\n\n\tfor _, path := range paths {\n\t\thard := r.maxPathAmount(path, amt, planned, false)\n\t\tif hard < needed {\n\t\t\tcontinue\n\t\t}\n\n\t\tsafe := r.maxPathAmount(path, amt, planned, true)\n\t\tshard := hard\n\t\tif safe >= needed {\n\t\t\tshard = safe\n\t\t}\n\n\t\tamounts := candidatePathAmounts(path, shard)\n\t\tlogProbability := 0.0\n\t\tfor i, edge := range path {\n\t\t\ttotal := amounts[i] + r.reserved[edge.key]\n\t\t\tlogProbability += math.Log(\n\t\t\t\tr.edgeProbability(edge, total),\n\t\t\t)\n\t\t}\n\n\t\tcoverage := float64(shard) / float64(amt)\n\t\tfee := amounts[0] - shard\n\t\tscore := 2.5*logProbability + 4*coverage -\n\t\t\tfloat64(fee)/2_500_000 -\n\t\t\t0.025*float64(len(path))\n\n\t\tif score > bestScore {\n\t\t\tbestScore = score\n\t\t\tbestPath = path\n\t\t\tbestAmount = shard\n\t\t}\n\t}\n\n\tif bestPath == nil {\n\t\treturn nil, errors.New(\"no route set can carry payment\")\n\t}\n\treturn r.buildRoute(bestPath, bestAmount)\n}\n\nfunc (r *candidateRouter) collectPaths(\n\tamt lnwire.MilliSatoshi, parts int) [][]*candidateEdge {\n\n\tbase := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar probes []lnwire.MilliSatoshi\n\tprobes = candidateAppendAmount(probes, amt)\n\tprobes = candidateAppendAmount(probes, base)\n\tprobes = candidateAppendAmount(probes, base*3/4)\n\tprobes = candidateAppendAmount(probes, base/2)\n\tprobes = candidateAppendAmount(probes, base/4)\n\tprobes = candidateAppendAmount(probes, base/8)\n\tprobes = candidateAppendAmount(probes, base/16)\n\tprobes = candidateAppendAmount(probes, 1)\n\n\trounds := parts + 3\n\tif rounds < 6 {\n\t\trounds = 6\n\t}\n\tif rounds > 12 {\n\t\trounds = 12\n\t}\n\n\tvar paths [][]*candidateEdge\n\tfor _, probe := range probes {\n\t\tdiversity := make(map[candidateEdgeKey]float64)\n\t\tcheckMin := probe != 1\n\n\t\tfor i := 0; i < rounds; i++ {\n\t\t\tpath, err := r.findPath(\n\t\t\t\tprobe, diversity, checkMin,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tduplicate := false\n\t\t\tfor _, existing := range paths {\n\t\t\t\tif candidateSamePath(existing, path) {\n\t\t\t\t\tduplicate = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !duplicate {\n\t\t\t\tpaths = append(paths, path)\n\t\t\t\tif len(paths) >= 96 {\n\t\t\t\t\treturn paths\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, edge := range path {\n\t\t\t\tdiversity[edge.key] += 1_800_000\n\t\t\t}\n\t\t}\n\t}\n\n\treturn paths\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, error) {\n\n\tif parts < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tpaths := r.collectPaths(amt, parts)\n\tif len(paths) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tif plan, ok := r.beamPlan(paths, amt, parts, true); ok {\n\t\treturn plan, nil\n\t}\n\tif plan, ok := r.beamPlan(paths, amt, parts, false); ok {\n\t\treturn plan, nil\n\t}\n\n\tfallback, err := r.fallbackRoute(paths, amt, parts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []*route.Route{fallback}, nil\n}\n\nfunc candidateDelivered(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 candidateRouteHash(rt *route.Route) uint64 {\n\thash := uint64(1469598103934665603)\n\tmix := func(value uint64) {\n\t\thash ^= value\n\t\thash *= 1099511628211\n\t}\n\n\tmix(uint64(rt.TotalAmount))\n\tfor i, hop := range rt.Hops {\n\t\tmix(hop.ChannelID)\n\t\tmix(uint64(candidateRouteAmount(rt, i)))\n\t}\n\treturn hash\n}\n\nfunc (r *candidateRouter) attemptLimit(maxParts uint32) int {\n\tlimit := 24 + 6*int(maxParts)\n\tif limit < 48 {\n\t\tlimit = 48\n\t}\n\tif limit > 96 {\n\t\tlimit = 96\n\t}\n\treturn limit\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\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 reached\")\n\t}\n\tif r.attempts >= r.attemptLimit(maxParts) {\n\t\treturn nil, errors.New(\"routing attempt limit reached\")\n\t}\n\n\tfor replan := 0; replan < 6; replan++ {\n\t\tvar next *route.Route\n\n\t\tif len(r.plan) != 0 {\n\t\t\tnext = r.plan[0]\n\t\t\tr.plan = r.plan[1:]\n\t\t\tif candidateDelivered(next) > amt {\n\t\t\t\tr.plan = nil\n\t\t\t\tnext = nil\n\t\t\t}\n\t\t}\n\n\t\tif next == nil {\n\t\t\tpartsLeft := int(maxParts - inFlightHtlcs)\n\t\t\tplan, err := r.makePlan(amt, partsLeft)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tnext = plan[0]\n\t\t\tr.plan = plan[1:]\n\t\t}\n\n\t\thash := candidateRouteHash(next)\n\t\tif r.failedRoutes[hash] == 0 {\n\t\t\tr.attempts++\n\t\t\treturn next, nil\n\t\t}\n\n\t\tfor i := range next.Hops {\n\t\t\tkey := candidateRouteEdge(next, i)\n\t\t\tr.edgePenalty[key] += 300_000\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\treturn nil, errors.New(\"no untried route found\")\n}\n\nfunc candidateRouteEdge(rt *route.Route,\n\tindex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n}\n\nfunc candidateRouteAmount(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 candidateFailureIndex(rt *route.Route,\n\tsource 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) learnRoutePassed(rt *route.Route,\n\tend int, reserve bool) {\n\n\tif end > len(rt.Hops) {\n\t\tend = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < end; i++ {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tamt := candidateRouteAmount(rt, i)\n\t\ttotal := amt + r.reserved[key]\n\t\tr.learn(key, total, amt, true)\n\t}\n\n\tif reserve {\n\t\tfor i := 0; i < end; i++ {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.reserved[key] += candidateRouteAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\thash := candidateRouteHash(rt)\n\n\tif result.Failure == nil {\n\t\tdelete(r.failedRoutes, hash)\n\t\tr.learnRoutePassed(rt, len(rt.Hops), true)\n\t\treturn nil\n\t}\n\n\tr.failedRoutes[hash]++\n\tr.plan = nil\n\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\n\tif failIndex > 0 {\n\t\tr.learnRoutePassed(rt, failIndex, false)\n\t}\n\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.edgePenalty[key] += 600_000\n\t\t}\n\t\treturn nil\n\t}\n\n\tkey := candidateRouteEdge(rt, failIndex)\n\tamt := candidateRouteAmount(rt, failIndex)\n\ttotal := amt + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learn(key, total, amt, false)\n\t\tif r.liquidityFail[key] < 255 {\n\t\t\tr.liquidityFail[key]++\n\t\t}\n\t\tr.edgePenalty[key] += 900_000 +\n\t\t\t350_000*float64(r.liquidityFail[key])\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBad[key] = true\n\n\tdefault:\n\t\tr.edgePenalty[key] += 1_750_000\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 5,
|
|
"parent": 1,
|
|
"score": 0.2362,
|
|
"accepted": true,
|
|
"frontier": true,
|
|
"params": {
|
|
"source": "package main\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateFinalCltvDelta = 40\n\tcandidateMaxAttempts = 72\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,\n\tcheckMin bool) bool {\n\n\tif amt <= 0 {\n\t\treturn false\n\t}\n\tif checkMin && 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\tupperBad lnwire.MilliSatoshi\n\tconf uint8\n}\n\nvar candidateSharedState = struct {\n\tsync.Mutex\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\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tshared map[candidateEdgeKey]candidateBelief\n\tcurrent map[candidateEdgeKey]candidateBelief\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[candidateEdgeKey]float64\n\tpolicyBad map[candidateEdgeKey]bool\n\n\tplan []*route.Route\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\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\tshared: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrent: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyBad: make(map[candidateEdgeKey]bool),\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 := 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\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: 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\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\tcandidateSharedState.Lock()\n\tfor key := range r.edges {\n\t\tif belief, ok := candidateSharedState.beliefs[key]; ok {\n\t\t\tr.shared[key] = belief\n\t\t}\n\t}\n\tcandidateSharedState.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-ratio/0.025)\n\thighMode := 0.5 / (1 + math.Exp((ratio-0.93)/0.04))\n\tprobability := lowMode + highMode\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn probability\n}\n\nfunc candidateEvidenceProbability(b candidateBelief,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\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.005\n\t}\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tposition := float64(amt-b.lowerOK) /\n\t\t\tfloat64(b.upperBad-b.lowerOK)\n\t\tprobability := 0.99 - 0.98*position\n\t\tif probability < 0.01 {\n\t\t\treturn 0.01\n\t\t}\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\n\tcase b.upperBad > 0:\n\t\tratio := float64(amt) / float64(b.upperBad)\n\t\tprobability := 0.02 + 0.90*math.Exp(-ratio/0.12)\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\n\tcase b.lowerOK > 0:\n\t\tif capacity <= b.lowerOK {\n\t\t\treturn 0.985\n\t\t}\n\t\tdistance := float64(amt-b.lowerOK) / float64(capacity)\n\t\tprobability := 0.58 + 0.40*math.Exp(-distance/0.20)\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\t}\n\n\treturn candidatePrior(amt, capacity)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprobability := candidatePrior(total, edge.capacity)\n\n\tif belief, ok := r.shared[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 2.0)\n\t\tif weight > 0.86 {\n\t\t\tweight = 0.86\n\t\t}\n\t\tprobability = probability*(1-weight) + evidence*weight\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tprobability = 0.06*probability + 0.94*evidence\n\t}\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn probability\n}\n\nfunc candidateUpdateBelief(b candidateBelief,\n\tamt lnwire.MilliSatoshi, passed bool) candidateBelief {\n\n\tif passed {\n\t\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t} else {\n\t\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\t\tb.lowerOK = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t}\n\n\tif b.conf < 16 {\n\t\tb.conf++\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learn(key candidateEdgeKey,\n\tcurrentAmt, sharedAmt lnwire.MilliSatoshi, passed bool) {\n\n\tr.current[key] = candidateUpdateBelief(\n\t\tr.current[key], currentAmt, passed,\n\t)\n\tr.shared[key] = candidateUpdateBelief(\n\t\tr.shared[key], sharedAmt, passed,\n\t)\n\n\tcandidateSharedState.Lock()\n\tcandidateSharedState.beliefs[key] = candidateUpdateBelief(\n\t\tcandidateSharedState.beliefs[key], sharedAmt, passed,\n\t)\n\tcandidateSharedState.Unlock()\n}\n\nfunc (r *candidateRouter) hardTotal(edge *candidateEdge) lnwire.MilliSatoshi {\n\tlimit := edge.capacity\n\n\tif edge.key.from == r.source {\n\t\tlocal := r.localBalances[edge.key.chanID]\n\t\tif local < limit {\n\t\t\tlimit = local\n\t\t}\n\t}\n\n\tbelief, ok := r.current[edge.key]\n\tif !ok || belief.upperBad == 0 {\n\t\treturn limit\n\t}\n\n\tvar retryLimit lnwire.MilliSatoshi\n\tif belief.lowerOK > 0 && belief.upperBad > belief.lowerOK {\n\t\tretryLimit = belief.lowerOK +\n\t\t\t(belief.upperBad-belief.lowerOK)*42/100\n\t} else {\n\t\tretryLimit = belief.upperBad * 60 / 100\n\t}\n\n\tif retryLimit < limit {\n\t\tlimit = retryLimit\n\t}\n\treturn limit\n}\n\nfunc candidateRecommendedFromBelief(b candidateBelief,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tvar result lnwire.MilliSatoshi\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tresult = b.lowerOK + (b.upperBad-b.lowerOK)*30/100\n\n\tcase b.upperBad > 0:\n\t\tresult = b.upperBad * 55 / 100\n\n\tcase b.lowerOK > 0:\n\t\tresult = capacity * 88 / 100\n\t\tif b.lowerOK > result {\n\t\t\tresult = b.lowerOK\n\t\t}\n\n\tdefault:\n\t\tresult = capacity * 78 / 100\n\t}\n\n\tcapLimit := capacity * 98 / 100\n\tif result > capLimit {\n\t\tresult = capLimit\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) recommendedTotal(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\thard := r.hardTotal(edge)\n\tif edge.key.from == r.source {\n\t\treturn hard\n\t}\n\n\tvar result lnwire.MilliSatoshi\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else if belief, ok := r.shared[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else {\n\t\tresult = edge.capacity * 78 / 100\n\t}\n\n\tif result > hard {\n\t\tresult = hard\n\t}\n\treturn result\n}\n\ntype candidateQueueItem struct {\n\tnode route.Vertex\n\tcost float64\n\tarriving 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\treturn q[i].cost < q[j].cost\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) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]float64,\n\triskScale float64) ([]*candidateEdge, 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 equals target\")\n\t}\n\n\tdist := map[route.Vertex]float64{r.spec.Target: 0}\n\tnext := make(map[route.Vertex]*candidateEdge)\n\tpq := &candidateQueue{}\n\theap.Push(pq, &candidateQueueItem{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t})\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidateQueueItem)\n\t\tbest, ok := dist[item.node]\n\t\tif !ok || item.cost > best {\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] ||\n\t\t\t\t!edge.usable(item.arriving, true) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlimit := r.hardTotal(edge)\n\t\t\ttotal := item.arriving + r.reserved[edge.key]\n\t\t\tif total > limit || limit <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, total)\n\t\t\theadroomCost := 180_000 *\n\t\t\t\tfloat64(total) / float64(limit)\n\t\t\tedgeCost := -math.Log(probability)*riskScale +\n\t\t\t\theadroomCost + 3_000 +\n\t\t\t\tr.edgePenalty[edge.key] + diversity[edge.key]\n\n\t\t\tsending := item.arriving\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(item.arriving)\n\t\t\t\tsending += fee\n\t\t\t\tedgeCost += float64(fee)\n\t\t\t}\n\n\t\t\tnewCost := item.cost + edgeCost\n\t\t\toldCost, exists := dist[edge.key.from]\n\t\t\tif exists && newCost >= oldCost {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdist[edge.key.from] = newCost\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\tcost: newCost,\n\t\t\t\tarriving: sending,\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\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.key.to\n\t}\n\n\treturn path, nil\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\nfunc candidatePathAmounts(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\tif len(path) == 0 {\n\t\treturn amounts\n\t}\n\n\tamounts[len(path)-1] = finalAmt\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\tnext := path[i+1]\n\t\tamounts[i] = amounts[i+1] + next.fee(amounts[i+1])\n\t}\n\treturn amounts\n}\n\nfunc (r *candidateRouter) pathWithin(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe, checkMin bool) bool {\n\n\tif finalAmt <= 0 || len(path) == 0 {\n\t\treturn false\n\t}\n\n\tamounts := candidatePathAmounts(path, finalAmt)\n\tfor i, edge := range path {\n\t\tif !edge.usable(amounts[i], checkMin) {\n\t\t\treturn false\n\t\t}\n\n\t\tlimit := r.hardTotal(edge)\n\t\tif safe {\n\t\t\tlimit = r.recommendedTotal(edge)\n\t\t}\n\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\t\tif total > limit {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) maxPathAmount(path []*candidateEdge,\n\twant lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe bool) lnwire.MilliSatoshi {\n\n\tif want <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := want\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathWithin(path, mid, planned, safe, false) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\tif low == 0 ||\n\t\t!r.pathWithin(path, low, planned, safe, true) {\n\n\t\treturn 0\n\t}\n\treturn low\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty path\")\n\t}\n\n\tamounts := candidatePathAmounts(path, amt)\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = candidateFinalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].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(candidateFinalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforwardAmt = 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: forwardAmt,\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 candidateAddPlanned(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi) {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tfor i, edge := range path {\n\t\tplanned[edge.key] += amounts[i]\n\t}\n}\n\nfunc candidateClonePlanned(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\ntype candidateAllocation struct {\n\troutes []*route.Route\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi\n\tremaining lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\thops int\n\trank float64\n}\n\nfunc (r *candidateRouter) allocationQuality(\n\tstate *candidateAllocation) float64 {\n\n\tquality := -float64(state.fee)/8_000_000 -\n\t\t0.025*float64(state.hops) -\n\t\t0.10*float64(len(state.routes))\n\n\tfor key, amount := range state.planned {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ttotal := amount + r.reserved[key]\n\t\tquality += math.Log(r.edgeProbability(edge, total))\n\t\tquality -= r.edgePenalty[key] / 1_500_000\n\t}\n\n\treturn quality\n}\n\nfunc (r *candidateRouter) allocationRank(state *candidateAllocation,\n\toriginal lnwire.MilliSatoshi) float64 {\n\n\tcoverage := float64(original-state.remaining) / float64(original)\n\treturn 24*coverage + 0.60*r.allocationQuality(state)\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamount lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amount <= 0 {\n\t\treturn amounts\n\t}\n\tfor _, existing := range amounts {\n\t\tif existing == amount {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amount)\n}\n\nfunc (r *candidateRouter) allocatePlan(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int,\n\tsafe bool) ([]*route.Route, bool) {\n\n\tinitial := &candidateAllocation{\n\t\tplanned: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tremaining: amt,\n\t}\n\tbeam := []*candidateAllocation{initial}\n\n\tvar best *candidateAllocation\n\tbestQuality := math.Inf(-1)\n\n\tfor depth := 0; depth < parts && len(beam) > 0; depth++ {\n\t\tvar next []*candidateAllocation\n\n\t\tfor _, state := range beam {\n\t\t\tslots := parts - len(state.routes)\n\t\t\tif slots <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfair := (state.remaining +\n\t\t\t\tlnwire.MilliSatoshi(slots) - 1) /\n\t\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\t\tfor _, path := range paths {\n\t\t\t\tmaxAmount := r.maxPathAmount(\n\t\t\t\t\tpath, state.remaining,\n\t\t\t\t\tstate.planned, safe,\n\t\t\t\t)\n\t\t\t\tif maxAmount == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvar choices []lnwire.MilliSatoshi\n\t\t\t\tchoices = candidateAppendAmount(choices, maxAmount)\n\n\t\t\t\tfairAmount := fair\n\t\t\t\tif fairAmount > maxAmount {\n\t\t\t\t\tfairAmount = maxAmount\n\t\t\t\t}\n\t\t\t\tchoices = candidateAppendAmount(choices, fairAmount)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount*3/4,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount/2,\n\t\t\t\t)\n\n\t\t\t\tif maxAmount >= state.remaining {\n\t\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\t\tchoices, state.remaining,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tfor _, amount := range choices {\n\t\t\t\t\tif amount > state.remaining ||\n\t\t\t\t\t\t!r.pathWithin(\n\t\t\t\t\t\t\tpath, amount, state.planned,\n\t\t\t\t\t\t\tsafe, true,\n\t\t\t\t\t\t) {\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\trt, err := r.buildRoute(path, amount)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tplanned := candidateClonePlanned(\n\t\t\t\t\t\tstate.planned,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateAddPlanned(path, amount, planned)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\t[]*route.Route(nil), state.routes...,\n\t\t\t\t\t)\n\t\t\t\t\troutes = append(routes, rt)\n\n\t\t\t\t\tnextState := &candidateAllocation{\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\tplanned: planned,\n\t\t\t\t\t\tremaining: state.remaining - amount,\n\t\t\t\t\t\tfee: state.fee +\n\t\t\t\t\t\t\t(rt.TotalAmount - amount),\n\t\t\t\t\t\thops: state.hops + len(path),\n\t\t\t\t\t}\n\n\t\t\t\t\tif nextState.remaining == 0 {\n\t\t\t\t\t\tquality := r.allocationQuality(\n\t\t\t\t\t\t\tnextState,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif quality > bestQuality {\n\t\t\t\t\t\t\tbest = nextState\n\t\t\t\t\t\t\tbestQuality = quality\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnextState.rank = r.allocationRank(\n\t\t\t\t\t\tnextState, amt,\n\t\t\t\t\t)\n\t\t\t\t\tnext = append(next, nextState)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(next, func(i, j int) bool {\n\t\t\treturn next[i].rank > next[j].rank\n\t\t})\n\t\tif len(next) > 48 {\n\t\t\tnext = next[:48]\n\t\t}\n\t\tbeam = next\n\t}\n\n\tif best == nil {\n\t\treturn nil, false\n\t}\n\treturn best.routes, true\n}\n\nfunc candidateAppendProbe(probes []lnwire.MilliSatoshi,\n\tprobe lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif probe <= 0 {\n\t\treturn probes\n\t}\n\tfor _, existing := range probes {\n\t\tif existing == probe {\n\t\t\treturn probes\n\t\t}\n\t}\n\treturn append(probes, probe)\n}\n\nfunc (r *candidateRouter) discoverPaths(\n\tamt lnwire.MilliSatoshi, parts int) [][]*candidateEdge {\n\n\tbase := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar probes []lnwire.MilliSatoshi\n\tprobes = candidateAppendProbe(probes, amt)\n\tprobes = candidateAppendProbe(probes, base)\n\tprobes = candidateAppendProbe(probes, base*3/4)\n\tprobes = candidateAppendProbe(probes, base/2)\n\tprobes = candidateAppendProbe(probes, base/3)\n\tprobes = candidateAppendProbe(probes, base/4)\n\tprobes = candidateAppendProbe(probes, base/8)\n\tprobes = candidateAppendProbe(probes, base/16)\n\tprobes = candidateAppendProbe(probes, base/32)\n\n\tvar paths [][]*candidateEdge\n\n\tfor _, probe := range probes {\n\t\tdiversity := make(map[candidateEdgeKey]float64)\n\t\tadded := 0\n\n\t\tfor round := 0; round < 12 && added < 7; round++ {\n\t\t\triskScale := 850_000.0\n\t\t\tswitch round % 3 {\n\t\t\tcase 0:\n\t\t\t\triskScale = 1_200_000\n\t\t\tcase 1:\n\t\t\t\triskScale = 500_000\n\t\t\t}\n\n\t\t\tpath, err := r.findPath(\n\t\t\t\tprobe, diversity, riskScale,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tduplicate := false\n\t\t\tfor _, existing := range paths {\n\t\t\t\tif candidateSamePath(existing, path) {\n\t\t\t\t\tduplicate = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !duplicate {\n\t\t\t\tpaths = append(paths, path)\n\t\t\t\tadded++\n\t\t\t}\n\n\t\t\tfor _, edge := range path {\n\t\t\t\tdiversity[edge.key] += 2_200_000\n\t\t\t}\n\t\t}\n\t}\n\n\treturn paths\n}\n\nfunc (r *candidateRouter) fallbackScore(path []*candidateEdge,\n\tamt, paymentAmt lnwire.MilliSatoshi) float64 {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tlogProbability := 0.0\n\tpenalty := 0.0\n\n\tfor i, edge := range path {\n\t\ttotal := amounts[i] + r.reserved[edge.key]\n\t\tlogProbability += math.Log(r.edgeProbability(edge, total))\n\t\tpenalty += r.edgePenalty[edge.key] / 1_500_000\n\t}\n\n\tcoverage := float64(amt) / float64(paymentAmt)\n\tfee := amounts[0] - amt\n\n\treturn 4.2*math.Log(coverage) + logProbability - penalty -\n\t\tfloat64(fee)/8_000_000 - 0.03*float64(len(path))\n}\n\nfunc (r *candidateRouter) bestFallback(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int) *route.Route {\n\n\tplanned := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tfair := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tbestScore := math.Inf(-1)\n\tvar best *route.Route\n\n\tfor _, path := range paths {\n\t\thardMax := r.maxPathAmount(path, amt, planned, false)\n\t\tsafeMax := r.maxPathAmount(path, amt, planned, true)\n\n\t\tvar choices []lnwire.MilliSatoshi\n\t\tchoices = candidateAppendAmount(choices, safeMax)\n\t\tchoices = candidateAppendAmount(choices, hardMax)\n\n\t\tfairAmount := fair\n\t\tif fairAmount > hardMax {\n\t\t\tfairAmount = hardMax\n\t\t}\n\t\tchoices = candidateAppendAmount(choices, fairAmount)\n\t\tchoices = candidateAppendAmount(choices, hardMax*3/4)\n\t\tchoices = candidateAppendAmount(choices, hardMax/2)\n\n\t\tfor _, amount := range choices {\n\t\t\tif amount <= 0 ||\n\t\t\t\t!r.pathWithin(\n\t\t\t\t\tpath, amount, planned, false, true,\n\t\t\t\t) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := r.fallbackScore(path, amount, amt)\n\t\t\tif score <= bestScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trt, err := r.buildRoute(path, amount)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest = rt\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\treturn best\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, error) {\n\n\tif parts < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tpaths := r.discoverPaths(amt, parts)\n\tif len(paths) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tif plan, ok := r.allocatePlan(paths, amt, parts, true); ok {\n\t\treturn plan, nil\n\t}\n\tif plan, ok := r.allocatePlan(paths, amt, parts, false); ok {\n\t\treturn plan, nil\n\t}\n\n\tfallback := r.bestFallback(paths, amt, parts)\n\tif fallback != nil {\n\t\treturn []*route.Route{fallback}, nil\n\t}\n\n\treturn nil, errors.New(\"no route set can carry payment\")\n}\n\nfunc candidateDelivered(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) takeRoute(rt *route.Route) *route.Route {\n\tr.attempts++\n\treturn 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(\"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 reached\")\n\t}\n\n\tif len(r.plan) > 0 {\n\t\tnext := r.plan[0]\n\t\tif candidateDelivered(next) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.takeRoute(next), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tif r.attempts >= candidateMaxAttempts {\n\t\treturn nil, errors.New(\"routing attempts exhausted\")\n\t}\n\n\tpartsLeft := int(maxParts - inFlightHtlcs)\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(plan) == 0 {\n\t\treturn nil, errors.New(\"empty route plan\")\n\t}\n\n\tr.plan = plan[1:]\n\treturn r.takeRoute(plan[0]), nil\n}\n\nfunc candidateRouteEdge(rt *route.Route,\n\tindex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n}\n\nfunc candidateRouteAmount(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 candidateFailureIndex(rt *route.Route,\n\tsource 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) learnRoutePassed(rt *route.Route,\n\tend int, reserve bool) {\n\n\tif end > len(rt.Hops) {\n\t\tend = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < end; i++ {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tamt := candidateRouteAmount(rt, i)\n\t\ttotal := amt + r.reserved[key]\n\t\tr.learn(key, total, amt, true)\n\t}\n\n\tif reserve {\n\t\tfor i := 0; i < end; i++ {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.reserved[key] += candidateRouteAmount(rt, i)\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.learnRoutePassed(rt, len(rt.Hops), true)\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex > 0 {\n\t\tr.learnRoutePassed(rt, failIndex, false)\n\t}\n\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.edgePenalty[key] += 550_000\n\t\t}\n\t\treturn nil\n\t}\n\n\tkey := candidateRouteEdge(rt, failIndex)\n\tamt := candidateRouteAmount(rt, failIndex)\n\ttotal := amt + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learn(key, total, amt, false)\n\t\tr.edgePenalty[key] += 450_000\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBad[key] = true\n\n\tdefault:\n\t\tr.edgePenalty[key] += 1_200_000\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 6,
|
|
"parent": 0,
|
|
"score": 0.0,
|
|
"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\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateFinalCltvDelta = 40\n\tcandidateMaxAttempts = 72\n\tcandidateMaxLabels = 8\n\tcandidateMaxPaths = 48\n\tcandidateBeamWidth = 96\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,\n\tcheckMin bool) bool {\n\n\tif amt <= 0 || amt > e.capacity {\n\t\treturn false\n\t}\n\tif checkMin && 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 true\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf uint8\n}\n\nvar candidateSharedState = struct {\n\tsync.Mutex\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\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tshared map[candidateEdgeKey]candidateBelief\n\tcurrent map[candidateEdgeKey]candidateBelief\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[candidateEdgeKey]float64\n\tpolicyBad map[candidateEdgeKey]bool\n\n\tplan []*route.Route\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\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\tshared: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrent: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyBad: make(map[candidateEdgeKey]bool),\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 := 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\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\tcandidateSharedState.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := candidateSharedState.beliefs[key]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif belief.lowerOK > edge.capacity {\n\t\t\tbelief.lowerOK = edge.capacity\n\t\t}\n\t\tif belief.upperBad > edge.capacity {\n\t\t\tbelief.upperBad = edge.capacity\n\t\t}\n\t\tr.shared[key] = belief\n\t}\n\tcandidateSharedState.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-ratio/0.025)\n\thighMode := 0.5 / (1 + math.Exp((ratio-0.93)/0.04))\n\tprobability := lowMode + highMode\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn probability\n}\n\nfunc candidateEvidenceProbability(b candidateBelief,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\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.005\n\t}\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tposition := float64(amt-b.lowerOK) /\n\t\t\tfloat64(b.upperBad-b.lowerOK)\n\t\tprobability := 0.99 - 0.98*position\n\t\tif probability < 0.01 {\n\t\t\treturn 0.01\n\t\t}\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\n\tcase b.upperBad > 0:\n\t\tratio := float64(amt) / float64(b.upperBad)\n\t\tprobability := 0.01 + 0.91*math.Exp(-ratio/0.22)\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\n\tcase b.lowerOK > 0:\n\t\tif capacity <= b.lowerOK {\n\t\t\treturn 0.985\n\t\t}\n\t\tdistance := float64(amt-b.lowerOK) / float64(capacity)\n\t\tprobability := 0.61 + 0.37*math.Exp(-distance/0.20)\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\t}\n\n\treturn candidatePrior(amt, capacity)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprobability := candidatePrior(total, edge.capacity)\n\n\tif belief, ok := r.shared[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 4.0)\n\t\tif weight > 0.70 {\n\t\t\tweight = 0.70\n\t\t}\n\t\tprobability = probability*(1-weight) + evidence*weight\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 0.45)\n\t\tif weight > 0.97 {\n\t\t\tweight = 0.97\n\t\t}\n\t\tprobability = probability*(1-weight) + evidence*weight\n\t}\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn probability\n}\n\nfunc candidateUpdateBelief(b candidateBelief,\n\tamt lnwire.MilliSatoshi, passed bool) candidateBelief {\n\n\tif amt <= 0 {\n\t\treturn b\n\t}\n\n\tif passed {\n\t\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t} else {\n\t\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\t\tb.lowerOK = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t}\n\n\tif b.conf < 16 {\n\t\tb.conf++\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learn(key candidateEdgeKey,\n\tcurrentAmt, sharedAmt lnwire.MilliSatoshi, passed bool) {\n\n\tr.current[key] = candidateUpdateBelief(\n\t\tr.current[key], currentAmt, passed,\n\t)\n\n\tif passed {\n\t\tr.edgePenalty[key] *= 0.35\n\t}\n\n\t// A failure while another atomic shard is held only establishes a\n\t// bound on their combined amount. Do not turn that contingent bound\n\t// into a cross-payment standalone failure.\n\tif !passed && currentAmt != sharedAmt {\n\t\treturn\n\t}\n\n\tcandidateSharedState.Lock()\n\tupdated := candidateUpdateBelief(\n\t\tcandidateSharedState.beliefs[key], sharedAmt, passed,\n\t)\n\tcandidateSharedState.beliefs[key] = updated\n\tcandidateSharedState.Unlock()\n\n\tr.shared[key] = updated\n}\n\nfunc (r *candidateRouter) addPenalty(key candidateEdgeKey, amount float64) {\n\tr.edgePenalty[key] += amount\n\tif r.edgePenalty[key] > 1_500_000 {\n\t\tr.edgePenalty[key] = 1_500_000\n\t}\n}\n\nfunc (r *candidateRouter) hardTotal(edge *candidateEdge) lnwire.MilliSatoshi {\n\tlimit := edge.capacity\n\n\tif edge.key.from == r.source {\n\t\tlocal := r.localBalances[edge.key.chanID]\n\t\tif local < limit {\n\t\t\tlimit = local\n\t\t}\n\t}\n\n\tbelief, ok := r.current[edge.key]\n\tif !ok || belief.upperBad == 0 {\n\t\treturn limit\n\t}\n\n\tvar retryLimit lnwire.MilliSatoshi\n\tif belief.lowerOK > 0 && belief.upperBad > belief.lowerOK {\n\t\tretryLimit = belief.lowerOK +\n\t\t\t(belief.upperBad-belief.lowerOK)*55/100\n\t} else {\n\t\tretryLimit = belief.upperBad * 70 / 100\n\t}\n\n\tif retryLimit < limit {\n\t\tlimit = retryLimit\n\t}\n\treturn limit\n}\n\nfunc candidateRecommendedFromBelief(b candidateBelief,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tvar result lnwire.MilliSatoshi\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tresult = b.lowerOK + (b.upperBad-b.lowerOK)*30/100\n\n\tcase b.upperBad > 0:\n\t\tresult = b.upperBad * 28 / 100\n\n\tcase b.lowerOK > 0:\n\t\tresult = capacity * 90 / 100\n\t\tif b.lowerOK > result {\n\t\t\tresult = b.lowerOK\n\t\t}\n\n\tdefault:\n\t\tresult = capacity * 72 / 100\n\t}\n\n\tcapLimit := capacity * 98 / 100\n\tif result > capLimit {\n\t\tresult = capLimit\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) recommendedTotal(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\thard := r.hardTotal(edge)\n\tif edge.key.from == r.source {\n\t\treturn hard\n\t}\n\n\tvar result lnwire.MilliSatoshi\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else if belief, ok := r.shared[edge.key]; ok {\n\t\tresult = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else {\n\t\tresult = edge.capacity * 72 / 100\n\t}\n\n\tif result > hard {\n\t\tresult = hard\n\t}\n\treturn result\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tcost float64\n\tarriving lnwire.MilliSatoshi\n\tpath []*candidateEdge\n\talive bool\n}\n\ntype candidateQueue []*candidatePathLabel\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\tif q[i].cost == q[j].cost {\n\t\treturn q[i].arriving < q[j].arriving\n\t}\n\treturn q[i].cost < q[j].cost\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.(*candidatePathLabel))\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 candidateDominates(a, b *candidatePathLabel) bool {\n\treturn a.cost <= b.cost && a.arriving <= b.arriving\n}\n\nfunc candidatePathHasVertex(path []*candidateEdge,\n\tvertex route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == vertex || edge.key.to == vertex {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(\n\tlabels map[route.Vertex][]*candidatePathLabel,\n\tpq *candidateQueue, label *candidatePathLabel) {\n\n\texisting := labels[label.node]\n\tfor _, old := range existing {\n\t\tif old.alive && candidateDominates(old, label) {\n\t\t\tlabel.alive = false\n\t\t\treturn\n\t\t}\n\t}\n\n\tkept := make([]*candidatePathLabel, 0, len(existing)+1)\n\tfor _, old := range existing {\n\t\tif !old.alive {\n\t\t\tcontinue\n\t\t}\n\t\tif candidateDominates(label, old) {\n\t\t\told.alive = false\n\t\t\tcontinue\n\t\t}\n\t\tkept = append(kept, old)\n\t}\n\n\tlabel.alive = true\n\tkept = append(kept, label)\n\tsort.Slice(kept, func(i, j int) bool {\n\t\tif kept[i].cost == kept[j].cost {\n\t\t\treturn kept[i].arriving < kept[j].arriving\n\t\t}\n\t\treturn kept[i].cost < kept[j].cost\n\t})\n\n\tif len(kept) > candidateMaxLabels {\n\t\tfor _, dropped := range kept[candidateMaxLabels:] {\n\t\t\tdropped.alive = false\n\t\t}\n\t\tkept = kept[:candidateMaxLabels]\n\t}\n\tlabels[label.node] = kept\n\n\tif label.alive {\n\t\theap.Push(pq, label)\n\t}\n}\n\nfunc (r *candidateRouter) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]float64,\n\triskScale float64) ([]*candidateEdge, 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 equals target\")\n\t}\n\n\tlabels := make(map[route.Vertex][]*candidatePathLabel)\n\tpq := &candidateQueue{}\n\tinitial := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t\talive: true,\n\t}\n\tlabels[r.spec.Target] = []*candidatePathLabel{initial}\n\theap.Push(pq, initial)\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidatePathLabel)\n\t\tif !item.alive {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\treturn item.path, nil\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.policyBad[edge.key] ||\n\t\t\t\t!edge.usable(item.arriving, true) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == item.node ||\n\t\t\t\tcandidatePathHasVertex(item.path, edge.key.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlimit := r.hardTotal(edge)\n\t\t\ttotal := item.arriving + r.reserved[edge.key]\n\t\t\tif limit <= 0 || total > limit {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, total)\n\t\t\tutilization := float64(total) / float64(limit)\n\t\t\theadroomCost := 125_000 * utilization *\n\t\t\t\tutilization * utilization\n\n\t\t\tedgeCost := -math.Log(probability)*riskScale +\n\t\t\t\theadroomCost + 42_000 +\n\t\t\t\tr.edgePenalty[edge.key] + diversity[edge.key]\n\n\t\t\tsending := item.arriving\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(item.arriving)\n\t\t\t\tsending += fee\n\t\t\t\tedgeCost += float64(fee)\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = edge\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tcandidateInsertLabel(labels, pq, &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tcost: item.cost + edgeCost,\n\t\t\t\tarriving: sending,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\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\nfunc candidatePathAmounts(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\tif len(path) == 0 {\n\t\treturn amounts\n\t}\n\n\tamounts[len(path)-1] = finalAmt\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tpath[i+1].fee(amounts[i+1])\n\t}\n\treturn amounts\n}\n\nfunc (r *candidateRouter) pathWithin(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe, checkMin bool) bool {\n\n\tif finalAmt <= 0 || len(path) == 0 {\n\t\treturn false\n\t}\n\n\tamounts := candidatePathAmounts(path, finalAmt)\n\tfor i, edge := range path {\n\t\tif !edge.usable(amounts[i], checkMin) {\n\t\t\treturn false\n\t\t}\n\n\t\tlimit := r.hardTotal(edge)\n\t\tif safe {\n\t\t\tlimit = r.recommendedTotal(edge)\n\t\t}\n\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\t\tif limit <= 0 || total > limit {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) maxPathAmount(path []*candidateEdge,\n\twant lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe bool) lnwire.MilliSatoshi {\n\n\tif want <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := want\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathWithin(path, mid, planned, safe, false) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\tif low == 0 || !r.pathWithin(path, low, planned, safe, true) {\n\t\treturn 0\n\t}\n\treturn low\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty path\")\n\t}\n\n\tamounts := candidatePathAmounts(path, amt)\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = candidateFinalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].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(candidateFinalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforwardAmt = 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: forwardAmt,\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 candidateAddPlanned(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi) {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tfor i, edge := range path {\n\t\tplanned[edge.key] += amounts[i]\n\t}\n}\n\nfunc candidateClonePlanned(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\ntype candidateAllocation struct {\n\troutes []*route.Route\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi\n\tremaining lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\thops int\n\trank float64\n}\n\nfunc (r *candidateRouter) allocationQuality(\n\tstate *candidateAllocation) float64 {\n\n\tquality := -float64(state.fee)/12_000_000 -\n\t\t0.025*float64(state.hops) -\n\t\t0.08*float64(len(state.routes))\n\n\tfor key, amount := range state.planned {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ttotal := amount + r.reserved[key]\n\t\tquality += math.Log(r.edgeProbability(edge, total))\n\t\tquality -= r.edgePenalty[key] / 900_000\n\t}\n\n\treturn quality\n}\n\nfunc (r *candidateRouter) allocationRank(state *candidateAllocation,\n\toriginal lnwire.MilliSatoshi) float64 {\n\n\tcoverage := float64(original-state.remaining) / float64(original)\n\treturn 60*coverage + 0.15*r.allocationQuality(state)\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamount lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amount <= 0 {\n\t\treturn amounts\n\t}\n\tfor _, existing := range amounts {\n\t\tif existing == amount {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amount)\n}\n\nfunc (r *candidateRouter) routeReliability(rt *route.Route) float64 {\n\tresult := 0.0\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttotal := candidateRouteAmount(rt, i) + r.reserved[key]\n\t\tresult += math.Log(r.edgeProbability(edge, total))\n\t\tresult -= r.edgePenalty[key] / 900_000\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) orderPlan(routes []*route.Route) {\n\tsort.SliceStable(routes, func(i, j int) bool {\n\t\tleft := r.routeReliability(routes[i])\n\t\tright := r.routeReliability(routes[j])\n\t\tif left == right {\n\t\t\treturn candidateDelivered(routes[i]) >\n\t\t\t\tcandidateDelivered(routes[j])\n\t\t}\n\t\treturn left < right\n\t})\n}\n\nfunc (r *candidateRouter) allocatePlan(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int,\n\tsafe bool) ([]*route.Route, bool) {\n\n\tinitial := &candidateAllocation{\n\t\tplanned: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tremaining: amt,\n\t}\n\tbeam := []*candidateAllocation{initial}\n\n\tvar best *candidateAllocation\n\tbestQuality := math.Inf(-1)\n\n\tfor depth := 0; depth < parts && len(beam) > 0; depth++ {\n\t\tvar next []*candidateAllocation\n\n\t\tfor _, state := range beam {\n\t\t\tslots := parts - len(state.routes)\n\t\t\tif slots <= 0 || state.remaining <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfair := (state.remaining +\n\t\t\t\tlnwire.MilliSatoshi(slots) - 1) /\n\t\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\t\tfor _, path := range paths {\n\t\t\t\tmaxAmount := r.maxPathAmount(\n\t\t\t\t\tpath, state.remaining,\n\t\t\t\t\tstate.planned, safe,\n\t\t\t\t)\n\t\t\t\tif maxAmount == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvar choices []lnwire.MilliSatoshi\n\t\t\t\tif maxAmount >= state.remaining {\n\t\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\t\tchoices, state.remaining,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tchoices = candidateAppendAmount(choices, maxAmount)\n\n\t\t\t\tfairAmount := fair\n\t\t\t\tif fairAmount > maxAmount {\n\t\t\t\t\tfairAmount = maxAmount\n\t\t\t\t}\n\t\t\t\tchoices = candidateAppendAmount(choices, fairAmount)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount*3/4,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount/2,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount/3,\n\t\t\t\t)\n\n\t\t\t\tfor _, amount := range choices {\n\t\t\t\t\tif amount > state.remaining ||\n\t\t\t\t\t\t!r.pathWithin(\n\t\t\t\t\t\t\tpath, amount, state.planned,\n\t\t\t\t\t\t\tsafe, true,\n\t\t\t\t\t\t) {\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\trt, err := r.buildRoute(path, amount)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tplanned := candidateClonePlanned(\n\t\t\t\t\t\tstate.planned,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateAddPlanned(path, amount, planned)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\t[]*route.Route(nil), state.routes...,\n\t\t\t\t\t)\n\t\t\t\t\troutes = append(routes, rt)\n\n\t\t\t\t\tnextState := &candidateAllocation{\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\tplanned: planned,\n\t\t\t\t\t\tremaining: state.remaining - amount,\n\t\t\t\t\t\tfee: state.fee +\n\t\t\t\t\t\t\t(rt.TotalAmount - amount),\n\t\t\t\t\t\thops: state.hops + len(path),\n\t\t\t\t\t}\n\n\t\t\t\t\tif nextState.remaining == 0 {\n\t\t\t\t\t\tquality := r.allocationQuality(\n\t\t\t\t\t\t\tnextState,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif quality > bestQuality {\n\t\t\t\t\t\t\tbest = nextState\n\t\t\t\t\t\t\tbestQuality = quality\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif len(nextState.routes) >= parts {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnextState.rank = r.allocationRank(\n\t\t\t\t\t\tnextState, amt,\n\t\t\t\t\t)\n\t\t\t\t\tnext = append(next, nextState)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(next, func(i, j int) bool {\n\t\t\treturn next[i].rank > next[j].rank\n\t\t})\n\t\tif len(next) > candidateBeamWidth {\n\t\t\tnext = next[:candidateBeamWidth]\n\t\t}\n\t\tbeam = next\n\t}\n\n\tif best == nil {\n\t\treturn nil, false\n\t}\n\n\tr.orderPlan(best.routes)\n\treturn best.routes, true\n}\n\nfunc candidateAppendProbe(probes []lnwire.MilliSatoshi,\n\tprobe lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif probe <= 0 {\n\t\treturn probes\n\t}\n\tfor _, existing := range probes {\n\t\tif existing == probe {\n\t\t\treturn probes\n\t\t}\n\t}\n\treturn append(probes, probe)\n}\n\nfunc (r *candidateRouter) discoverPaths(\n\tamt lnwire.MilliSatoshi, parts int) [][]*candidateEdge {\n\n\tbase := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar probes []lnwire.MilliSatoshi\n\tprobes = candidateAppendProbe(probes, amt)\n\tprobes = candidateAppendProbe(probes, base)\n\tprobes = candidateAppendProbe(probes, base*3/4)\n\tprobes = candidateAppendProbe(probes, base/2)\n\tprobes = candidateAppendProbe(probes, base/4)\n\tprobes = candidateAppendProbe(probes, base/8)\n\tprobes = candidateAppendProbe(probes, base/16)\n\tprobes = candidateAppendProbe(probes, base/64)\n\n\triskScales := []float64{\n\t\t1_350_000,\n\t\t800_000,\n\t\t420_000,\n\t\t220_000,\n\t}\n\n\tvar paths [][]*candidateEdge\n\tfor _, probe := range probes {\n\t\tdiversity := make(map[candidateEdgeKey]float64)\n\n\t\tfor round := 0; round < 8; round++ {\n\t\t\tpath, err := r.findPath(\n\t\t\t\tprobe, diversity,\n\t\t\t\triskScales[round%len(riskScales)],\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tduplicate := false\n\t\t\tfor _, existing := range paths {\n\t\t\t\tif candidateSamePath(existing, path) {\n\t\t\t\t\tduplicate = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !duplicate {\n\t\t\t\tpaths = append(paths, path)\n\t\t\t\tif len(paths) >= candidateMaxPaths {\n\t\t\t\t\treturn paths\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor i, edge := range path {\n\t\t\t\tdiversity[edge.key] += 300_000\n\t\t\t\tif i == 0 || i == len(path)-1 {\n\t\t\t\t\tdiversity[edge.key] += 220_000\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn paths\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, error) {\n\n\tif parts < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tpaths := r.discoverPaths(amt, parts)\n\tif len(paths) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tif plan, ok := r.allocatePlan(paths, amt, parts, true); ok {\n\t\treturn plan, nil\n\t}\n\tif plan, ok := r.allocatePlan(paths, amt, parts, false); ok {\n\t\treturn plan, nil\n\t}\n\n\treturn nil, errors.New(\"no route set can carry payment\")\n}\n\nfunc candidateDelivered(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) takeRoute(rt *route.Route) *route.Route {\n\tr.attempts++\n\treturn 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(\"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 reached\")\n\t}\n\tif r.attempts >= candidateMaxAttempts {\n\t\treturn nil, errors.New(\"routing attempts exhausted\")\n\t}\n\n\tif len(r.plan) > 0 {\n\t\tnext := r.plan[0]\n\t\tif candidateDelivered(next) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.takeRoute(next), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tpartsLeft := int(maxParts - inFlightHtlcs)\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(plan) == 0 {\n\t\treturn nil, errors.New(\"empty route plan\")\n\t}\n\n\tr.plan = plan[1:]\n\treturn r.takeRoute(plan[0]), nil\n}\n\nfunc candidateRouteEdge(rt *route.Route,\n\tindex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n}\n\nfunc candidateRouteAmount(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 candidateFailureIndex(rt *route.Route,\n\tsource 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) learnRoutePassed(rt *route.Route,\n\tend int, reserve bool) {\n\n\tif end > len(rt.Hops) {\n\t\tend = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < end; i++ {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tamt := candidateRouteAmount(rt, i)\n\t\ttotal := amt + r.reserved[key]\n\t\tr.learn(key, total, amt, true)\n\t}\n\n\tif reserve {\n\t\tfor i := 0; i < end; i++ {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.reserved[key] += candidateRouteAmount(rt, i)\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.learnRoutePassed(rt, len(rt.Hops), true)\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex > 0 {\n\t\tr.learnRoutePassed(rt, failIndex, false)\n\t}\n\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tr.addPenalty(candidateRouteEdge(rt, i), 260_000)\n\t\t}\n\t\treturn nil\n\t}\n\n\tkey := candidateRouteEdge(rt, failIndex)\n\tamt := candidateRouteAmount(rt, failIndex)\n\ttotal := amt + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learn(key, total, amt, false)\n\t\tr.addPenalty(key, 170_000)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBad[key] = true\n\n\tdefault:\n\t\tr.addPenalty(key, 750_000)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 7,
|
|
"parent": 1,
|
|
"score": 0.6879,
|
|
"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 edgeKey struct {\n\tchanID uint64\n\tfrom route.Vertex\n}\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) key() edgeKey {\n\treturn edgeKey{chanID: e.chanID, from: e.from}\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\testimate lnwire.MilliSatoshi\n\tconf float64\n}\n\nvar candidateMemory = struct {\n\tsync.Mutex\n\trefs map[uintptr]routing.SimNetworkView\n\tbeliefs map[uintptr]map[edgeKey]liquidityBelief\n}{\n\trefs: make(map[uintptr]routing.SimNetworkView),\n\tbeliefs: make(map[uintptr]map[edgeKey]liquidityBelief),\n}\n\nfunc viewScope(view routing.SimNetworkView) uintptr {\n\tvalue := reflect.ValueOf(view)\n\tswitch value.Kind() {\n\tcase reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr,\n\t\treflect.Slice, reflect.UnsafePointer:\n\n\t\tif value.IsNil() {\n\t\t\treturn 0\n\t\t}\n\t\treturn value.Pointer()\n\tdefault:\n\t\treturn 0\n\t}\n}\n\nfunc evolveBelief(b liquidityBelief, capacity,\n\tobserved lnwire.MilliSatoshi, passed bool) liquidityBelief {\n\n\tif observed < 0 {\n\t\tobserved = 0\n\t}\n\tif observed > capacity {\n\t\tobserved = capacity\n\t}\n\n\tpriorEstimate := capacity * 3 / 4\n\tif b.conf == 0 {\n\t\tb.estimate = priorEstimate\n\t}\n\n\tif passed {\n\t\tif observed > b.lowerOK {\n\t\t\tb.lowerOK = observed\n\t\t}\n\t\tif b.upperBad != 0 && observed >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t}\n\n\t\ttarget := observed + (capacity-observed)*4/5\n\t\tb.estimate = (b.estimate + 3*target) / 4\n\t} else {\n\t\tif b.upperBad == 0 || observed < b.upperBad {\n\t\t\tb.upperBad = observed\n\t\t}\n\t\tif b.lowerOK >= observed {\n\t\t\tb.lowerOK = observed / 4\n\t\t}\n\n\t\ttarget := observed / 4\n\t\tlowMode := capacity / 12\n\t\tif target > lowMode {\n\t\t\ttarget = lowMode\n\t\t}\n\t\tb.estimate = (b.estimate + 4*target) / 5\n\t}\n\n\tif b.estimate < b.lowerOK {\n\t\tb.estimate = b.lowerOK\n\t}\n\tif b.upperBad != 0 && b.estimate >= b.upperBad {\n\t\tb.estimate = b.upperBad * 2 / 3\n\t}\n\tif b.estimate > capacity {\n\t\tb.estimate = capacity\n\t}\n\n\tb.conf++\n\tif b.conf > 8 {\n\t\tb.conf = 8\n\t}\n\n\treturn b\n}\n\ntype candidateRouter struct {\n\tsource route.Vertex\n\tspec *routing.SimPaymentSpec\n\tscope uintptr\n\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\tbeliefs map[edgeKey]liquidityBelief\n\n\tlocalFailures map[edgeKey]lnwire.MilliSatoshi\n\tbadPolicy map[edgeKey]bool\n\treserved map[edgeKey]lnwire.MilliSatoshi\n\tpenalty map[edgeKey]float64\n\n\tplan []*route.Route\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\tscope := viewScope(view)\n\tr := &candidateRouter{\n\t\tsource: source,\n\t\tspec: spec,\n\t\tscope: scope,\n\t\tincomingEdges: make(map[route.Vertex][]*candidateEdge),\n\t\tlocalBalances: localBalances,\n\t\tbeliefs: make(map[edgeKey]liquidityBelief),\n\t\tlocalFailures: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tbadPolicy: make(map[edgeKey]bool),\n\t\treserved: make(map[edgeKey]lnwire.MilliSatoshi),\n\t\tpenalty: make(map[edgeKey]float64),\n\t}\n\n\tcandidateMemory.Lock()\n\tif scope != 0 {\n\t\tcandidateMemory.refs[scope] = view\n\t}\n\tfor key, belief := range candidateMemory.beliefs[scope] {\n\t\tr.beliefs[key] = belief\n\t}\n\tcandidateMemory.Unlock()\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\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: 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}, 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 (r *candidateRouter) observe(edge edgeKey,\n\tcapacity, amount lnwire.MilliSatoshi, passed bool) {\n\n\tr.beliefs[edge] = evolveBelief(\n\t\tr.beliefs[edge], capacity, amount, passed,\n\t)\n\n\tcandidateMemory.Lock()\n\tnetworkBeliefs := candidateMemory.beliefs[r.scope]\n\tif networkBeliefs == nil {\n\t\tnetworkBeliefs = make(map[edgeKey]liquidityBelief)\n\t\tcandidateMemory.beliefs[r.scope] = networkBeliefs\n\t}\n\tnetworkBeliefs[edge] = evolveBelief(\n\t\tnetworkBeliefs[edge], capacity, amount, passed,\n\t)\n\tcandidateMemory.Unlock()\n}\n\nfunc bimodalPrior(amount, capacity lnwire.MilliSatoshi) float64 {\n\tif amount <= 0 {\n\t\treturn 0.985\n\t}\n\tif capacity <= 0 || amount >= capacity {\n\t\treturn 0.005\n\t}\n\n\tx := float64(amount) / float64(capacity)\n\tlowMode := math.Exp(-x / 0.035)\n\thighMode := 1 / (1 + math.Exp((x-0.86)/0.045))\n\tprobability := 0.48*lowMode + 0.50*highMode + 0.005\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn probability\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\trequired lnwire.MilliSatoshi) float64 {\n\n\tif edge.from == r.source {\n\t\tif required <= r.localBalances[edge.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprior := bimodalPrior(required, edge.capacity)\n\tbelief, ok := r.beliefs[edge.key()]\n\tif !ok || belief.conf == 0 {\n\t\treturn prior\n\t}\n\n\tif belief.lowerOK != 0 && required <= belief.lowerOK {\n\t\treturn 0.995\n\t}\n\tif belief.upperBad != 0 && required >= belief.upperBad {\n\t\treturn 0.005\n\t}\n\n\tscale := float64(edge.capacity) * 0.06\n\tif scale < 1 {\n\t\tscale = 1\n\t}\n\tpointProbability := 1 / (1 + math.Exp(\n\t\t(float64(required)-float64(belief.estimate))/scale,\n\t))\n\tweight := belief.conf / (belief.conf + 1.5)\n\tprobability := (1-weight)*prior + weight*pointProbability\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn probability\n}\n\nfunc (r *candidateRouter) estimatedLimit(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tif edge.from == r.source {\n\t\tlimit := r.localBalances[edge.chanID]\n\t\tif limit > edge.capacity {\n\t\t\tlimit = edge.capacity\n\t\t}\n\t\treturn limit\n\t}\n\n\tbelief, ok := r.beliefs[edge.key()]\n\tif !ok || belief.conf == 0 {\n\t\treturn edge.capacity * 78 / 100\n\t}\n\n\tlimit := belief.estimate\n\tif limit < belief.lowerOK {\n\t\tlimit = belief.lowerOK\n\t}\n\tif belief.upperBad != 0 && limit >= belief.upperBad {\n\t\tlimit = belief.upperBad * 2 / 3\n\t}\n\tif limit > edge.capacity {\n\t\tlimit = edge.capacity\n\t}\n\treturn limit\n}\n\ntype searchItem struct {\n\tnode route.Vertex\n\tamount lnwire.MilliSatoshi\n\tscore float64\n}\n\ntype searchQueue []*searchItem\n\nfunc (q searchQueue) Len() int { return len(q) }\nfunc (q searchQueue) 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}\nfunc (q searchQueue) Swap(i, j int) {\n\tq[i], q[j] = q[j], q[i]\n}\nfunc (q *searchQueue) Push(value any) {\n\t*q = append(*q, value.(*searchItem))\n}\nfunc (q *searchQueue) 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) locallyAllowed(edge *candidateEdge,\n\tamount lnwire.MilliSatoshi,\n\treserve map[edgeKey]lnwire.MilliSatoshi) bool {\n\n\tif !edge.usable(amount) || r.badPolicy[edge.key()] {\n\t\treturn false\n\t}\n\n\tkey := edge.key()\n\ttotal := amount + reserve[key]\n\tif total > edge.capacity {\n\t\treturn false\n\t}\n\n\tif edge.from == r.source &&\n\t\ttotal > r.localBalances[edge.chanID] {\n\n\t\treturn false\n\t}\n\n\tif failed, ok := r.localFailures[key]; ok {\n\t\tretryCeiling := failed * 7 / 10\n\t\tif total >= retryCeiling {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) findPath(amount lnwire.MilliSatoshi,\n\treserve map[edgeKey]lnwire.MilliSatoshi,\n\tdiversity map[edgeKey]float64) ([]*candidateEdge, error) {\n\n\tif amount <= 0 {\n\t\treturn nil, errors.New(\"invalid route amount\")\n\t}\n\n\tbest := make(map[route.Vertex]float64)\n\tarrivingAt := make(map[route.Vertex]lnwire.MilliSatoshi)\n\tnext := make(map[route.Vertex]*candidateEdge)\n\n\tbest[r.spec.Target] = 0\n\tarrivingAt[r.spec.Target] = amount\n\n\tqueue := &searchQueue{}\n\theap.Push(queue, &searchItem{\n\t\tnode: r.spec.Target,\n\t\tamount: amount,\n\t})\n\n\tfeeDenominator := float64(amount)\n\tif feeDenominator < 1 {\n\t\tfeeDenominator = 1\n\t}\n\n\tfor queue.Len() != 0 {\n\t\titem := heap.Pop(queue).(*searchItem)\n\t\tcurrentBest, ok := best[item.node]\n\t\tif !ok || item.score > currentBest+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\tamountOver := item.amount\n\t\t\tif !r.locallyAllowed(edge, amountOver, reserve) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tkey := edge.key()\n\t\t\trequired := amountOver + reserve[key]\n\t\t\tprobability := r.edgeProbability(edge, required)\n\n\t\t\tsending := amountOver\n\t\t\tfeeCost := 0.0\n\t\t\tif edge.from != r.source {\n\t\t\t\tfee := edge.fee(amountOver)\n\t\t\t\tsending += fee\n\t\t\t\tfeeCost = 12 * float64(fee) / feeDenominator\n\t\t\t}\n\n\t\t\tscore := item.score - math.Log(probability) +\n\t\t\t\tfeeCost + 0.012 + r.penalty[key]\n\t\t\tif diversity != nil {\n\t\t\t\tscore += diversity[key]\n\t\t\t}\n\n\t\t\tprevious, exists := best[edge.from]\n\t\t\tif exists && score >= previous {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbest[edge.from] = score\n\t\t\tarrivingAt[edge.from] = sending\n\t\t\tnext[edge.from] = edge\n\t\t\theap.Push(queue, &searchItem{\n\t\t\t\tnode: edge.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 := best[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\treturn path, nil\n}\n\nfunc pathAmounts(path []*candidateEdge,\n\tfinalAmount lnwire.MilliSatoshi) ([]lnwire.MilliSatoshi, bool) {\n\n\tif len(path) == 0 || finalAmount <= 0 {\n\t\treturn nil, false\n\t}\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\tamounts[len(path)-1] = finalAmount\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\tnextEdge := path[i+1]\n\t\tamounts[i] = amounts[i+1] + nextEdge.fee(amounts[i+1])\n\t}\n\treturn amounts, true\n}\n\nfunc pathMinimum(path []*candidateEdge) lnwire.MilliSatoshi {\n\tminimum := lnwire.MilliSatoshi(1)\n\tfor _, edge := range path {\n\t\tif edge.minHTLC > minimum {\n\t\t\tminimum = edge.minHTLC\n\t\t}\n\t}\n\treturn minimum\n}\n\nfunc (r *candidateRouter) pathFeasible(path []*candidateEdge,\n\tfinalAmount lnwire.MilliSatoshi,\n\treserve map[edgeKey]lnwire.MilliSatoshi,\n\tuseEstimate bool) bool {\n\n\tamounts, ok := pathAmounts(path, finalAmount)\n\tif !ok {\n\t\treturn false\n\t}\n\n\tfor i, edge := range path {\n\t\tif !r.locallyAllowed(edge, amounts[i], reserve) {\n\t\t\treturn false\n\t\t}\n\t\tif useEstimate {\n\t\t\ttotal := amounts[i] + reserve[edge.key()]\n\t\t\tif total > r.estimatedLimit(edge) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) pathMaximum(path []*candidateEdge,\n\tmaximum lnwire.MilliSatoshi,\n\treserve map[edgeKey]lnwire.MilliSatoshi,\n\tuseEstimate bool) lnwire.MilliSatoshi {\n\n\tminimum := pathMinimum(path)\n\tif maximum < minimum {\n\t\treturn 0\n\t}\n\tif r.pathFeasible(path, maximum, reserve, useEstimate) {\n\t\treturn maximum\n\t}\n\tif !r.pathFeasible(path, minimum, reserve, useEstimate) {\n\t\treturn 0\n\t}\n\n\tlow, high := minimum, maximum\n\tfor low < high {\n\t\tmiddle := low + (high-low+1)/2\n\t\tif r.pathFeasible(path, middle, reserve, useEstimate) {\n\t\t\tlow = middle\n\t\t} else {\n\t\t\thigh = middle - 1\n\t\t}\n\t}\n\treturn low\n}\n\nfunc (r *candidateRouter) pathProbability(path []*candidateEdge,\n\tfinalAmount lnwire.MilliSatoshi,\n\treserve map[edgeKey]lnwire.MilliSatoshi) float64 {\n\n\tamounts, ok := pathAmounts(path, finalAmount)\n\tif !ok {\n\t\treturn 0\n\t}\n\n\tlogProbability := 0.0\n\tfor i, edge := range path {\n\t\trequired := amounts[i] + reserve[edge.key()]\n\t\tlogProbability += math.Log(\n\t\t\tr.edgeProbability(edge, required),\n\t\t)\n\t}\n\tif logProbability < -700 {\n\t\treturn 0\n\t}\n\treturn math.Exp(logProbability)\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tfinalAmount lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tamounts, ok := pathAmounts(path, finalAmount)\n\tif !ok {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = finalCltvDelta\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].timeLockDelta)\n\t}\n\n\thops := make([]*route.Hop, len(path))\n\tfor i, edge := range path {\n\t\tamountToForward := finalAmount\n\t\toutgoingExpiry := uint32(finalCltvDelta)\n\t\tif i < len(path)-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.to,\n\t\t\tChannelID: edge.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 copyReservations(\n\tsource map[edgeKey]lnwire.MilliSatoshi) map[edgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[edgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc reservePath(reserve map[edgeKey]lnwire.MilliSatoshi,\n\tpath []*candidateEdge, finalAmount lnwire.MilliSatoshi) {\n\n\tamounts, ok := pathAmounts(path, finalAmount)\n\tif !ok {\n\t\treturn\n\t}\n\tfor i, edge := range path {\n\t\treserve[edge.key()] += amounts[i]\n\t}\n}\n\nfunc pathSignature(path []*candidateEdge) string {\n\tsignature := \"\"\n\tfor _, edge := range path {\n\t\tsignature += fmt.Sprintf(\n\t\t\t\"%d:%x|\", edge.chanID, edge.from[:],\n\t\t)\n\t}\n\treturn signature\n}\n\ntype plannedPath struct {\n\tpath []*candidateEdge\n\tminimum lnwire.MilliSatoshi\n}\n\nfunc (r *candidateRouter) discoverPaths(total lnwire.MilliSatoshi,\n\tparts int, fullPath []*candidateEdge) []plannedPath {\n\n\twanted := parts * 3\n\tif wanted < parts+2 {\n\t\twanted = parts + 2\n\t}\n\tif wanted > 14 {\n\t\twanted = 14\n\t}\n\n\tdiversity := make(map[edgeKey]float64)\n\tknown := make(map[string]bool)\n\tpaths := make([]plannedPath, 0, wanted)\n\n\taddPath := func(path []*candidateEdge) bool {\n\t\tsignature := pathSignature(path)\n\t\tif known[signature] {\n\t\t\tfor _, edge := range path {\n\t\t\t\tdiversity[edge.key()] += 8\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\n\t\tif r.pathMaximum(\n\t\t\tpath, total, r.reserved, false,\n\t\t) < pathMinimum(path) {\n\n\t\t\treturn false\n\t\t}\n\n\t\tknown[signature] = true\n\t\tpaths = append(paths, plannedPath{\n\t\t\tpath: path,\n\t\t\tminimum: pathMinimum(path),\n\t\t})\n\t\tfor _, edge := range path {\n\t\t\tdiversity[edge.key()] += 6\n\t\t}\n\t\treturn true\n\t}\n\n\tif len(fullPath) != 0 {\n\t\taddPath(fullPath)\n\t}\n\n\tbaseProbe := (total + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\tif baseProbe < 1 {\n\t\tbaseProbe = 1\n\t}\n\n\tfor attempt := 0; attempt < wanted*4 && len(paths) < wanted;\n\t\tattempt++ {\n\n\t\tprobe := baseProbe\n\t\tswitch attempt % 3 {\n\t\tcase 1:\n\t\t\tprobe = baseProbe / 2\n\t\tcase 2:\n\t\t\tprobe = baseProbe * 2\n\t\t\tif probe > total {\n\t\t\t\tprobe = total\n\t\t\t}\n\t\t}\n\t\tif probe < 1 {\n\t\t\tprobe = 1\n\t\t}\n\n\t\tfor {\n\t\t\tpath, err := r.findPath(\n\t\t\t\tprobe, r.reserved, diversity,\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\taddPath(path)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif probe <= 1 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tnextProbe := probe * 2 / 3\n\t\t\tif nextProbe >= probe {\n\t\t\t\tnextProbe = probe - 1\n\t\t\t}\n\t\t\tif nextProbe < 1 {\n\t\t\t\tnextProbe = 1\n\t\t\t}\n\t\t\tprobe = nextProbe\n\t\t}\n\t}\n\n\treturn paths\n}\n\nfunc addSaturating(current, add,\n\tlimit lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tif current >= limit || add >= limit-current {\n\t\treturn limit\n\t}\n\treturn current + add\n}\n\nfunc (r *candidateRouter) buildRouteSet(total lnwire.MilliSatoshi,\n\tcandidates []plannedPath) []*route.Route {\n\n\treserve := copyReservations(r.reserved)\n\tremaining := total\n\troutes := make([]*route.Route, 0, len(candidates))\n\n\tfor index, candidate := range candidates {\n\t\tpublicMax := r.pathMaximum(\n\t\t\tcandidate.path, remaining, reserve, false,\n\t\t)\n\t\tif publicMax < candidate.minimum {\n\t\t\treturn nil\n\t\t}\n\n\t\tfutureMinimum := lnwire.MilliSatoshi(0)\n\t\tfuturePublic := lnwire.MilliSatoshi(0)\n\t\tweightSum := 0.0\n\n\t\tcurrentSafe := r.pathMaximum(\n\t\t\tcandidate.path, remaining, reserve, true,\n\t\t)\n\t\tif currentSafe < candidate.minimum {\n\t\t\tcurrentSafe = publicMax / 3\n\t\t\tif currentSafe < candidate.minimum {\n\t\t\t\tcurrentSafe = candidate.minimum\n\t\t\t}\n\t\t}\n\t\tweightSum += float64(currentSafe)\n\n\t\tfor _, future := range candidates[index+1:] {\n\t\t\tfutureMinimum = addSaturating(\n\t\t\t\tfutureMinimum, future.minimum, remaining,\n\t\t\t)\n\n\t\t\tfutureMax := r.pathMaximum(\n\t\t\t\tfuture.path, remaining, reserve, false,\n\t\t\t)\n\t\t\tfuturePublic = addSaturating(\n\t\t\t\tfuturePublic, futureMax, remaining,\n\t\t\t)\n\n\t\t\tfutureSafe := r.pathMaximum(\n\t\t\t\tfuture.path, remaining, reserve, true,\n\t\t\t)\n\t\t\tif futureSafe < future.minimum {\n\t\t\t\tfutureSafe = futureMax / 3\n\t\t\t\tif futureSafe < future.minimum {\n\t\t\t\t\tfutureSafe = future.minimum\n\t\t\t\t}\n\t\t\t}\n\t\t\tweightSum += float64(futureSafe)\n\t\t}\n\n\t\tlower := candidate.minimum\n\t\tif remaining > futurePublic {\n\t\t\trequiredNow := remaining - futurePublic\n\t\t\tif requiredNow > lower {\n\t\t\t\tlower = requiredNow\n\t\t\t}\n\t\t}\n\n\t\tif remaining <= futureMinimum {\n\t\t\treturn nil\n\t\t}\n\t\tupper := remaining - futureMinimum\n\t\tif upper > publicMax {\n\t\t\tupper = publicMax\n\t\t}\n\t\tif lower > upper {\n\t\t\treturn nil\n\t\t}\n\n\t\tallocation := lower\n\t\tif weightSum > 0 {\n\t\t\tproportional := lnwire.MilliSatoshi(\n\t\t\t\tfloat64(remaining) *\n\t\t\t\t\tfloat64(currentSafe) / weightSum,\n\t\t\t)\n\t\t\tif proportional > allocation {\n\t\t\t\tallocation = proportional\n\t\t\t}\n\t\t}\n\t\tif allocation > upper {\n\t\t\tallocation = upper\n\t\t}\n\n\t\trt, err := r.buildRoute(candidate.path, allocation)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\troutes = append(routes, rt)\n\t\treservePath(reserve, candidate.path, allocation)\n\t\tremaining -= allocation\n\t}\n\n\tif remaining != 0 {\n\t\treturn nil\n\t}\n\treturn routes\n}\n\nfunc (r *candidateRouter) fallbackShard(total lnwire.MilliSatoshi,\n\tparts int) []*route.Route {\n\n\tif parts < 1 {\n\t\treturn nil\n\t}\n\n\tprobe := total\n\tif parts > 1 {\n\t\tprobe = (total + lnwire.MilliSatoshi(parts) - 1) /\n\t\t\tlnwire.MilliSatoshi(parts)\n\t}\n\n\tfor {\n\t\tpath, err := r.findPath(probe, r.reserved, nil)\n\t\tif err == nil {\n\t\t\trt, buildErr := r.buildRoute(path, probe)\n\t\t\tif buildErr == nil {\n\t\t\t\treturn []*route.Route{rt}\n\t\t\t}\n\t\t}\n\n\t\tif parts == 1 || probe <= 1 {\n\t\t\treturn nil\n\t\t}\n\n\t\tnextProbe := probe * 2 / 3\n\t\tif nextProbe >= probe {\n\t\t\tnextProbe = probe - 1\n\t\t}\n\t\tif nextProbe < 1 {\n\t\t\tnextProbe = 1\n\t\t}\n\t\tprobe = nextProbe\n\t}\n}\n\nfunc (r *candidateRouter) makePlan(total lnwire.MilliSatoshi,\n\tparts int) []*route.Route {\n\n\tif parts < 1 {\n\t\treturn nil\n\t}\n\tif parts > 6 {\n\t\tparts = 6\n\t}\n\n\tfullPath, fullErr := r.findPath(total, r.reserved, nil)\n\tif fullErr == nil {\n\t\tprobability := r.pathProbability(\n\t\t\tfullPath, total, r.reserved,\n\t\t)\n\t\tif parts == 1 || probability >= 0.90 ||\n\t\t\t(total <= 2_000_000 && probability >= 0.70) {\n\n\t\t\trt, err := r.buildRoute(fullPath, total)\n\t\t\tif err == nil {\n\t\t\t\treturn []*route.Route{rt}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfullPath = nil\n\t}\n\n\tif parts == 1 {\n\t\treturn nil\n\t}\n\n\tpaths := r.discoverPaths(total, parts, fullPath)\n\tmaxParts := parts\n\tif maxParts > len(paths) {\n\t\tmaxParts = len(paths)\n\t}\n\n\tfor count := maxParts; count >= 2; count-- {\n\t\tstarts := len(paths)\n\t\tif starts > 8 {\n\t\t\tstarts = 8\n\t\t}\n\n\t\tfor start := 0; start < starts; start++ {\n\t\t\tselected := make([]plannedPath, 0, count)\n\t\t\tfor offset := 0; offset < len(paths) &&\n\t\t\t\tlen(selected) < count; offset++ {\n\n\t\t\t\tindex := (start + offset) % len(paths)\n\t\t\t\tselected = append(selected, paths[index])\n\t\t\t}\n\n\t\t\troutes := r.buildRouteSet(total, selected)\n\t\t\tif len(routes) != 0 {\n\t\t\t\treturn routes\n\t\t\t}\n\t\t}\n\t}\n\n\treturn r.fallbackShard(total, parts)\n}\n\nfunc routeFinalAmount(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 routeDirectedEdges(rt *route.Route) ([]edgeKey,\n\t[]lnwire.MilliSatoshi) {\n\n\tif rt == nil || len(rt.Hops) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tkeys := make([]edgeKey, len(rt.Hops))\n\tamounts := make([]lnwire.MilliSatoshi, len(rt.Hops))\n\tfrom := rt.SourcePubKey\n\n\tfor i, hop := range rt.Hops {\n\t\tkeys[i] = edgeKey{chanID: hop.ChannelID, from: from}\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) routeStillAllowed(rt *route.Route) bool {\n\tkeys, amounts := routeDirectedEdges(rt)\n\tfor i, key := range keys {\n\t\tif r.badPolicy[key] {\n\t\t\treturn false\n\t\t}\n\n\t\ttotal := amounts[i] + r.reserved[key]\n\t\tif failed, ok := r.localFailures[key]; ok &&\n\t\t\ttotal >= failed*7/10 {\n\n\t\t\treturn false\n\t\t}\n\n\t\tif total > r.localBalances[key.chanID] &&\n\t\t\tkey.from == r.source {\n\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (r *candidateRouter) RequestRoute(amt lnwire.MilliSatoshi,\n\tinFlightHtlcs uint32) (*route.Route, error) {\n\n\tif amt <= 0 {\n\t\treturn nil, errors.New(\"payment already complete\")\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(\"no MPP slots remaining\")\n\t}\n\tparts := int(maxParts - inFlightHtlcs)\n\n\tfor {\n\t\tfor len(r.plan) != 0 {\n\t\t\trt := r.plan[0]\n\t\t\tr.plan = r.plan[1:]\n\n\t\t\tfinalAmount := routeFinalAmount(rt)\n\t\t\tif finalAmount == 0 || finalAmount > amt {\n\t\t\t\tr.plan = nil\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif !r.routeStillAllowed(rt) {\n\t\t\t\tr.plan = nil\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn rt, nil\n\t\t}\n\n\t\tr.plan = r.makePlan(amt, parts)\n\t\tif len(r.plan) == 0 {\n\t\t\treturn nil, errors.New(\"no route plan found\")\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) edgeCapacity(key edgeKey) lnwire.MilliSatoshi {\n\tfor _, edges := range r.incomingEdges {\n\t\tfor _, edge := range edges {\n\t\t\tif edge.key() == key {\n\t\t\t\treturn edge.capacity\n\t\t\t}\n\t\t}\n\t}\n\treturn 0\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tkeys, amounts := routeDirectedEdges(rt)\n\tif len(keys) == 0 {\n\t\treturn nil\n\t}\n\n\tif result.Failure == nil {\n\t\tfor i, key := range keys {\n\t\t\ttotal := amounts[i] + r.reserved[key]\n\t\t\tr.observe(\n\t\t\t\tkey, r.edgeCapacity(key), total, true,\n\t\t\t)\n\t\t\tr.reserved[key] += amounts[i]\n\t\t}\n\t\treturn nil\n\t}\n\n\tfailIndex := -1\n\tif result.FailureSource == rt.SourcePubKey {\n\t\tfailIndex = 0\n\t} else {\n\t\tfor i, hop := range rt.Hops {\n\t\t\tif hop.PubKeyBytes == result.FailureSource {\n\t\t\t\tfailIndex = i + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tsuccessCount := failIndex\n\tif successCount < 0 {\n\t\tsuccessCount = 0\n\t}\n\tif successCount > len(keys) {\n\t\tsuccessCount = len(keys)\n\t}\n\n\tfor i := 0; i < successCount; i++ {\n\t\ttotal := amounts[i] + r.reserved[keys[i]]\n\t\tr.observe(\n\t\t\tkeys[i], r.edgeCapacity(keys[i]), total, true,\n\t\t)\n\t}\n\n\tif failIndex < 0 || failIndex >= len(keys) {\n\t\tfor _, key := range keys {\n\t\t\tr.penalty[key] += 0.15\n\t\t}\n\t\tr.plan = nil\n\t\treturn nil\n\t}\n\n\tfailedKey := keys[failIndex]\n\tfailedAmount := amounts[failIndex] + r.reserved[failedKey]\n\tr.penalty[failedKey] += 5\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tprevious, ok := r.localFailures[failedKey]\n\t\tif !ok || failedAmount < previous {\n\t\t\tr.localFailures[failedKey] = failedAmount\n\t\t}\n\t\tr.observe(\n\t\t\tfailedKey, r.edgeCapacity(failedKey),\n\t\t\tfailedAmount, false,\n\t\t)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.badPolicy[failedKey] = true\n\n\tdefault:\n\t\tr.penalty[failedKey] += 2\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 8,
|
|
"parent": 2,
|
|
"score": 0.3658,
|
|
"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\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateFinalCltvDelta = 40\n\tcandidateMaxAttempts = 72\n\tcandidateMaxLabels = 10\n\tcandidateMaxPaths = 56\n\tcandidateBeamWidth = 192\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,\n\tcheckMin bool) bool {\n\n\tif amt <= 0 || amt > e.capacity {\n\t\treturn false\n\t}\n\tif checkMin && 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 true\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf uint8\n}\n\nvar candidateSharedState = struct {\n\tsync.Mutex\n\tbeliefs map[candidateEdgeKey]candidateBelief\n}{\n\tbeliefs: make(map[candidateEdgeKey]candidateBelief),\n}\n\ntype candidateLiquidityFailure struct {\n\tamount lnwire.MilliSatoshi\n\tcount uint8\n}\n\ntype candidateLimitMode uint8\n\nconst (\n\tcandidateSafeLimit candidateLimitMode = iota\n\tcandidateRetryLimit\n\tcandidatePhysicalLimit\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\tshared map[candidateEdgeKey]candidateBelief\n\tcurrent map[candidateEdgeKey]candidateBelief\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[candidateEdgeKey]float64\n\tliquidityFailed map[candidateEdgeKey]candidateLiquidityFailure\n\tpolicyBad map[candidateEdgeKey]bool\n\n\tplan []*route.Route\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\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\tshared: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrent: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tliquidityFailed: make(map[candidateEdgeKey]candidateLiquidityFailure),\n\t\tpolicyBad: make(map[candidateEdgeKey]bool),\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 := 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\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\tcandidateSharedState.Lock()\n\tfor key, edge := range r.edges {\n\t\tbelief, ok := candidateSharedState.beliefs[key]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif belief.lowerOK > edge.capacity {\n\t\t\tbelief.lowerOK = edge.capacity\n\t\t}\n\t\tif belief.upperBad > edge.capacity {\n\t\t\tbelief.upperBad = edge.capacity\n\t\t}\n\t\tr.shared[key] = belief\n\t}\n\tcandidateSharedState.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-ratio/0.025)\n\thighMode := 0.5 / (1 + math.Exp((ratio-0.93)/0.04))\n\tprobability := lowMode + highMode\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn probability\n}\n\nfunc candidateEvidenceProbability(b candidateBelief,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\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.005\n\t}\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tposition := float64(amt-b.lowerOK) /\n\t\t\tfloat64(b.upperBad-b.lowerOK)\n\t\tprobability := 0.99 - 0.98*position\n\n\t\tif probability < 0.01 {\n\t\t\treturn 0.01\n\t\t}\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\n\tcase b.upperBad > 0:\n\t\tratio := float64(amt) / float64(b.upperBad)\n\t\tprobability := 0.01 + 0.92*math.Exp(-ratio/0.20)\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\n\tcase b.lowerOK > 0:\n\t\tif capacity <= b.lowerOK {\n\t\t\treturn 0.985\n\t\t}\n\n\t\tdistance := float64(amt-b.lowerOK) / float64(capacity)\n\t\tprobability := 0.62 + 0.36*math.Exp(-distance/0.20)\n\t\tif probability > 0.99 {\n\t\t\treturn 0.99\n\t\t}\n\t\treturn probability\n\t}\n\n\treturn candidatePrior(amt, capacity)\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprobability := candidatePrior(total, edge.capacity)\n\n\tif belief, ok := r.shared[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 4.0)\n\t\tif weight > 0.70 {\n\t\t\tweight = 0.70\n\t\t}\n\t\tprobability = probability*(1-weight) + evidence*weight\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 0.40)\n\t\tif weight > 0.98 {\n\t\t\tweight = 0.98\n\t\t}\n\t\tprobability = probability*(1-weight) + evidence*weight\n\t}\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.995 {\n\t\treturn 0.995\n\t}\n\treturn probability\n}\n\nfunc candidateUpdateBelief(b candidateBelief,\n\tamt lnwire.MilliSatoshi, passed bool) candidateBelief {\n\n\tif amt <= 0 {\n\t\treturn b\n\t}\n\n\tif passed {\n\t\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t} else {\n\t\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\t\tb.lowerOK = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t}\n\n\tif b.conf < 16 {\n\t\tb.conf++\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learn(key candidateEdgeKey,\n\tcurrentAmt, sharedAmt lnwire.MilliSatoshi, passed bool) {\n\n\tr.current[key] = candidateUpdateBelief(\n\t\tr.current[key], currentAmt, passed,\n\t)\n\n\tif passed {\n\t\tr.edgePenalty[key] *= 0.35\n\n\t\tfailed, ok := r.liquidityFailed[key]\n\t\tif ok {\n\t\t\tswitch {\n\t\t\tcase currentAmt >= failed.amount:\n\t\t\t\tdelete(r.liquidityFailed, key)\n\n\t\t\tcase failed.count > 1:\n\t\t\t\tfailed.count--\n\t\t\t\tr.liquidityFailed[key] = failed\n\t\t\t}\n\t\t}\n\t}\n\n\t// A failed sibling in an atomic payment establishes a bound on the\n\t// aggregate held amount, not on the standalone shard amount.\n\tif !passed && currentAmt != sharedAmt {\n\t\treturn\n\t}\n\n\tcandidateSharedState.Lock()\n\tupdated := candidateUpdateBelief(\n\t\tcandidateSharedState.beliefs[key], sharedAmt, passed,\n\t)\n\tcandidateSharedState.beliefs[key] = updated\n\tcandidateSharedState.Unlock()\n\n\tr.shared[key] = updated\n}\n\nfunc (r *candidateRouter) markLiquidityFailure(key candidateEdgeKey,\n\tamount lnwire.MilliSatoshi) {\n\n\tif amount <= 0 {\n\t\treturn\n\t}\n\n\tfailed, ok := r.liquidityFailed[key]\n\tif !ok || amount < failed.amount {\n\t\tfailed.amount = amount\n\t}\n\tif failed.count < 8 {\n\t\tfailed.count++\n\t}\n\tr.liquidityFailed[key] = failed\n}\n\nfunc (r *candidateRouter) failurePenalty(key candidateEdgeKey,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tfailed, ok := r.liquidityFailed[key]\n\tif !ok || failed.amount <= 0 || total <= 0 {\n\t\treturn 0\n\t}\n\n\tratio := float64(total) / float64(failed.amount)\n\tcount := float64(failed.count)\n\n\tswitch {\n\tcase ratio >= 0.95:\n\t\tpenalty := 360_000 * count\n\t\tif penalty > 1_500_000 {\n\t\t\treturn 1_500_000\n\t\t}\n\t\treturn penalty\n\n\tcase ratio >= 0.70:\n\t\treturn 145_000 * count\n\n\tcase ratio >= 0.45:\n\t\treturn 55_000 * count\n\n\tcase ratio >= 0.25:\n\t\treturn 15_000 * count\n\n\tdefault:\n\t\treturn 0\n\t}\n}\n\nfunc (r *candidateRouter) addPenalty(key candidateEdgeKey, amount float64) {\n\tr.edgePenalty[key] += amount\n\tif r.edgePenalty[key] > 1_500_000 {\n\t\tr.edgePenalty[key] = 1_500_000\n\t}\n}\n\nfunc (r *candidateRouter) physicalTotal(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\tlimit := edge.capacity\n\tif edge.key.from == r.source {\n\t\tlocal := r.localBalances[edge.key.chanID]\n\t\tif local < limit {\n\t\t\tlimit = local\n\t\t}\n\t}\n\treturn limit\n}\n\nfunc candidateRecommendedFromBelief(b candidateBelief,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tvar result lnwire.MilliSatoshi\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tresult = b.lowerOK + (b.upperBad-b.lowerOK)*28/100\n\n\tcase b.upperBad > 0:\n\t\tresult = b.upperBad * 30 / 100\n\n\tcase b.lowerOK > 0:\n\t\tresult = capacity * 88 / 100\n\t\tif b.lowerOK > result {\n\t\t\tresult = b.lowerOK\n\t\t}\n\n\tdefault:\n\t\tresult = capacity * 86 / 100\n\t}\n\n\tcapLimit := capacity * 98 / 100\n\tif result > capLimit {\n\t\tresult = capLimit\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) planningLimit(edge *candidateEdge,\n\tmode candidateLimitMode) lnwire.MilliSatoshi {\n\n\tphysical := r.physicalTotal(edge)\n\tif physical <= 0 || mode == candidatePhysicalLimit ||\n\t\tedge.key.from == r.source {\n\n\t\treturn physical\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tvar limit lnwire.MilliSatoshi\n\n\t\tif mode == candidateSafeLimit {\n\t\t\tlimit = candidateRecommendedFromBelief(\n\t\t\t\tbelief, edge.capacity,\n\t\t\t)\n\t\t} else {\n\t\t\tswitch {\n\t\t\tcase belief.lowerOK > 0 &&\n\t\t\t\tbelief.upperBad > belief.lowerOK:\n\n\t\t\t\tlimit = belief.lowerOK +\n\t\t\t\t\t(belief.upperBad-belief.lowerOK)*62/100\n\n\t\t\tcase belief.upperBad > 0:\n\t\t\t\tlimit = belief.upperBad * 62 / 100\n\n\t\t\tcase belief.lowerOK > 0:\n\t\t\t\tlimit = edge.capacity * 92 / 100\n\t\t\t\tif belief.lowerOK > limit {\n\t\t\t\t\tlimit = belief.lowerOK\n\t\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\tlimit = edge.capacity * 90 / 100\n\t\t\t}\n\t\t}\n\n\t\tif limit < physical {\n\t\t\treturn limit\n\t\t}\n\t\treturn physical\n\t}\n\n\tif mode == candidateSafeLimit {\n\t\tif belief, ok := r.shared[edge.key]; ok {\n\t\t\tlimit := candidateRecommendedFromBelief(\n\t\t\t\tbelief, edge.capacity,\n\t\t\t)\n\t\t\tif limit < physical {\n\t\t\t\treturn limit\n\t\t\t}\n\t\t\treturn physical\n\t\t}\n\n\t\tlimit := edge.capacity * 86 / 100\n\t\tif limit < physical {\n\t\t\treturn limit\n\t\t}\n\t}\n\n\treturn physical\n}\n\ntype candidatePathLabel struct {\n\tnode route.Vertex\n\tcost float64\n\tarriving lnwire.MilliSatoshi\n\tpath []*candidateEdge\n\talive bool\n}\n\ntype candidateQueue []*candidatePathLabel\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\tif q[i].cost == q[j].cost {\n\t\treturn q[i].arriving < q[j].arriving\n\t}\n\treturn q[i].cost < q[j].cost\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.(*candidatePathLabel))\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 candidateDominates(a, b *candidatePathLabel) bool {\n\treturn a.cost <= b.cost && a.arriving <= b.arriving\n}\n\nfunc candidatePathHasVertex(path []*candidateEdge,\n\tvertex route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == vertex || edge.key.to == vertex {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateInsertLabel(\n\tlabels map[route.Vertex][]*candidatePathLabel,\n\tpq *candidateQueue, label *candidatePathLabel) {\n\n\texisting := labels[label.node]\n\tfor _, old := range existing {\n\t\tif old.alive && candidateDominates(old, label) {\n\t\t\tlabel.alive = false\n\t\t\treturn\n\t\t}\n\t}\n\n\tkept := make([]*candidatePathLabel, 0, len(existing)+1)\n\tfor _, old := range existing {\n\t\tif !old.alive {\n\t\t\tcontinue\n\t\t}\n\t\tif candidateDominates(label, old) {\n\t\t\told.alive = false\n\t\t\tcontinue\n\t\t}\n\t\tkept = append(kept, old)\n\t}\n\n\tlabel.alive = true\n\tkept = append(kept, label)\n\tsort.Slice(kept, func(i, j int) bool {\n\t\tif kept[i].cost == kept[j].cost {\n\t\t\treturn kept[i].arriving < kept[j].arriving\n\t\t}\n\t\treturn kept[i].cost < kept[j].cost\n\t})\n\n\tif len(kept) > candidateMaxLabels {\n\t\tfor _, dropped := range kept[candidateMaxLabels:] {\n\t\t\tdropped.alive = false\n\t\t}\n\t\tkept = kept[:candidateMaxLabels]\n\t}\n\tlabels[label.node] = kept\n\n\tif label.alive {\n\t\theap.Push(pq, label)\n\t}\n}\n\nfunc (r *candidateRouter) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]float64,\n\triskScale float64) ([]*candidateEdge, 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 equals target\")\n\t}\n\n\tlabels := make(map[route.Vertex][]*candidatePathLabel)\n\tpq := &candidateQueue{}\n\tinitial := &candidatePathLabel{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t\talive: true,\n\t}\n\tlabels[r.spec.Target] = []*candidatePathLabel{initial}\n\theap.Push(pq, initial)\n\n\tfor pq.Len() > 0 {\n\t\titem := heap.Pop(pq).(*candidatePathLabel)\n\t\tif !item.alive {\n\t\t\tcontinue\n\t\t}\n\t\tif item.node == r.source {\n\t\t\treturn item.path, nil\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.policyBad[edge.key] ||\n\t\t\t\t!edge.usable(item.arriving, true) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif edge.key.from == item.node ||\n\t\t\t\tcandidatePathHasVertex(item.path, edge.key.from) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlimit := r.physicalTotal(edge)\n\t\t\ttotal := item.arriving + r.reserved[edge.key]\n\t\t\tif limit <= 0 || total > limit {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, total)\n\t\t\tutilization := float64(total) / float64(limit)\n\t\t\theadroomCost := 115_000 * utilization *\n\t\t\t\tutilization * utilization\n\n\t\t\tedgeCost := -math.Log(probability)*riskScale +\n\t\t\t\theadroomCost + 72_000 +\n\t\t\t\tr.edgePenalty[edge.key] +\n\t\t\t\tr.failurePenalty(edge.key, total) +\n\t\t\t\tdiversity[edge.key]\n\n\t\t\tsending := item.arriving\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(item.arriving)\n\t\t\t\tsending += fee\n\t\t\t\tedgeCost += float64(fee)\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = edge\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tcandidateInsertLabel(labels, pq, &candidatePathLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tcost: item.cost + edgeCost,\n\t\t\t\tarriving: sending,\n\t\t\t\tpath: path,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\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\nfunc candidatePathAmounts(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\tif len(path) == 0 {\n\t\treturn amounts\n\t}\n\n\tamounts[len(path)-1] = finalAmt\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tpath[i+1].fee(amounts[i+1])\n\t}\n\treturn amounts\n}\n\nfunc (r *candidateRouter) pathWithin(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tmode candidateLimitMode, checkMin bool) bool {\n\n\tif finalAmt <= 0 || len(path) == 0 {\n\t\treturn false\n\t}\n\n\tamounts := candidatePathAmounts(path, finalAmt)\n\tfor i, edge := range path {\n\t\tif !edge.usable(amounts[i], checkMin) {\n\t\t\treturn false\n\t\t}\n\n\t\tlimit := r.planningLimit(edge, mode)\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\n\t\tif limit <= 0 || total > limit {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) maxPathAmount(path []*candidateEdge,\n\twant lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tmode candidateLimitMode) lnwire.MilliSatoshi {\n\n\tif want <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := want\n\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathWithin(path, mid, planned, mode, false) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\tif low == 0 ||\n\t\t!r.pathWithin(path, low, planned, mode, true) {\n\n\t\treturn 0\n\t}\n\treturn low\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 {\n\t\treturn nil, errors.New(\"empty path\")\n\t}\n\n\tamounts := candidatePathAmounts(path, amt)\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = candidateFinalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].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(candidateFinalCltvDelta)\n\n\t\tif i+1 < len(path) {\n\t\t\tforwardAmt = 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: forwardAmt,\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 candidateAddPlanned(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi) {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tfor i, edge := range path {\n\t\tplanned[edge.key] += amounts[i]\n\t}\n}\n\nfunc candidateClonePlanned(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\ntype candidateAllocation struct {\n\troutes []*route.Route\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi\n\tremaining lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\thops int\n\trank float64\n}\n\nfunc (r *candidateRouter) allocationQuality(\n\tstate *candidateAllocation) float64 {\n\n\tquality := -float64(state.fee)/10_000_000 -\n\t\t0.035*float64(state.hops) -\n\t\t0.10*float64(len(state.routes))\n\n\tfor key, amount := range state.planned {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ttotal := amount + r.reserved[key]\n\t\tquality += math.Log(r.edgeProbability(edge, total))\n\t\tquality -= r.edgePenalty[key] / 850_000\n\t\tquality -= r.failurePenalty(key, total) / 700_000\n\t}\n\n\treturn quality\n}\n\nfunc (r *candidateRouter) allocationRank(state *candidateAllocation,\n\toriginal lnwire.MilliSatoshi) float64 {\n\n\tcoverage := float64(original-state.remaining) / float64(original)\n\treturn 80*coverage + 0.25*r.allocationQuality(state)\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamount lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amount <= 0 {\n\t\treturn amounts\n\t}\n\tfor _, existing := range amounts {\n\t\tif existing == amount {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amount)\n}\n\nfunc candidateDelivered(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 candidateRouteEdge(rt *route.Route,\n\tindex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n}\n\nfunc candidateRouteAmount(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) routePlanRisk(rt *route.Route,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi) float64 {\n\n\tresult := 0.0\n\tfor i := range rt.Hops {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ttotal := planned[key] + r.reserved[key]\n\t\tif total <= 0 {\n\t\t\ttotal = candidateRouteAmount(rt, i) + r.reserved[key]\n\t\t}\n\n\t\tresult += math.Log(r.edgeProbability(edge, total))\n\t\tresult -= r.edgePenalty[key] / 850_000\n\t\tresult -= r.failurePenalty(key, total) / 700_000\n\t}\n\n\treturn result\n}\n\nfunc (r *candidateRouter) orderPlan(routes []*route.Route,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi) {\n\n\t// Try the most fragile, largest commitments first. If they fail,\n\t// replanning happens before robust sibling routes occupy part slots.\n\tsort.SliceStable(routes, func(i, j int) bool {\n\t\tleft := r.routePlanRisk(routes[i], planned)\n\t\tright := r.routePlanRisk(routes[j], planned)\n\n\t\tif left == right {\n\t\t\treturn candidateDelivered(routes[i]) >\n\t\t\t\tcandidateDelivered(routes[j])\n\t\t}\n\t\treturn left < right\n\t})\n}\n\nfunc candidateTopCapacity(capacities []lnwire.MilliSatoshi,\n\tcount int) lnwire.MilliSatoshi {\n\n\tif count <= 0 || len(capacities) == 0 {\n\t\treturn 0\n\t}\n\n\tcopyCaps := append([]lnwire.MilliSatoshi(nil), capacities...)\n\tsort.Slice(copyCaps, func(i, j int) bool {\n\t\treturn copyCaps[i] > copyCaps[j]\n\t})\n\n\tif count > len(copyCaps) {\n\t\tcount = len(copyCaps)\n\t}\n\n\tvar total lnwire.MilliSatoshi\n\tfor _, capacity := range copyCaps[:count] {\n\t\ttotal += capacity\n\t}\n\treturn total\n}\n\nfunc (r *candidateRouter) allocatePlan(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int,\n\tmode candidateLimitMode) ([]*route.Route, bool) {\n\n\tinitial := &candidateAllocation{\n\t\tplanned: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tremaining: amt,\n\t}\n\tbeam := []*candidateAllocation{initial}\n\n\tvar best *candidateAllocation\n\tbestQuality := math.Inf(-1)\n\n\tfor depth := 0; depth < parts && len(beam) > 0; depth++ {\n\t\tvar next []*candidateAllocation\n\n\t\tfor _, state := range beam {\n\t\t\tslots := parts - len(state.routes)\n\t\t\tif slots <= 0 || state.remaining <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmaxima := make([]lnwire.MilliSatoshi, len(paths))\n\t\t\tfor i, path := range paths {\n\t\t\t\tmaxima[i] = r.maxPathAmount(\n\t\t\t\t\tpath, state.remaining,\n\t\t\t\t\tstate.planned, mode,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif candidateTopCapacity(maxima, slots) <\n\t\t\t\tstate.remaining {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfair := (state.remaining +\n\t\t\t\tlnwire.MilliSatoshi(slots) - 1) /\n\t\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\t\tfutureCapacity := candidateTopCapacity(\n\t\t\t\tmaxima, slots-1,\n\t\t\t)\n\t\t\tvar needed lnwire.MilliSatoshi\n\t\t\tif futureCapacity < state.remaining {\n\t\t\t\tneeded = state.remaining - futureCapacity\n\t\t\t}\n\n\t\t\tfor pathIndex, path := range paths {\n\t\t\t\tmaxAmount := maxima[pathIndex]\n\t\t\t\tif maxAmount <= 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvar choices []lnwire.MilliSatoshi\n\t\t\t\tif maxAmount >= state.remaining {\n\t\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\t\tchoices, state.remaining,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tchoices = candidateAppendAmount(choices, maxAmount)\n\n\t\t\t\tfairAmount := fair\n\t\t\t\tif fairAmount > maxAmount {\n\t\t\t\t\tfairAmount = maxAmount\n\t\t\t\t}\n\t\t\t\tchoices = candidateAppendAmount(choices, fairAmount)\n\n\t\t\t\tneededAmount := needed\n\t\t\t\tif neededAmount > maxAmount {\n\t\t\t\t\tneededAmount = maxAmount\n\t\t\t\t}\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, neededAmount,\n\t\t\t\t)\n\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount*9/10,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount*2/3,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount/2,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount/3,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount/4,\n\t\t\t\t)\n\n\t\t\t\tfor _, amount := range choices {\n\t\t\t\t\tif amount <= 0 ||\n\t\t\t\t\t\tamount > state.remaining ||\n\t\t\t\t\t\t!r.pathWithin(\n\t\t\t\t\t\t\tpath, amount,\n\t\t\t\t\t\t\tstate.planned, mode, true,\n\t\t\t\t\t\t) {\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\trt, err := r.buildRoute(path, amount)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tplanned := candidateClonePlanned(\n\t\t\t\t\t\tstate.planned,\n\t\t\t\t\t)\n\t\t\t\t\tcandidateAddPlanned(path, amount, planned)\n\n\t\t\t\t\troutes := append(\n\t\t\t\t\t\t[]*route.Route(nil),\n\t\t\t\t\t\tstate.routes...,\n\t\t\t\t\t)\n\t\t\t\t\troutes = append(routes, rt)\n\n\t\t\t\t\tnextState := &candidateAllocation{\n\t\t\t\t\t\troutes: routes,\n\t\t\t\t\t\tplanned: planned,\n\t\t\t\t\t\tremaining: state.remaining - amount,\n\t\t\t\t\t\tfee: state.fee +\n\t\t\t\t\t\t\t(rt.TotalAmount - amount),\n\t\t\t\t\t\thops: state.hops + len(path),\n\t\t\t\t\t}\n\n\t\t\t\t\tif nextState.remaining == 0 {\n\t\t\t\t\t\tquality := r.allocationQuality(\n\t\t\t\t\t\t\tnextState,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif quality > bestQuality {\n\t\t\t\t\t\t\tbest = nextState\n\t\t\t\t\t\t\tbestQuality = quality\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif len(nextState.routes) >= parts {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnextState.rank = r.allocationRank(\n\t\t\t\t\t\tnextState, amt,\n\t\t\t\t\t)\n\t\t\t\t\tnext = append(next, nextState)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(next, func(i, j int) bool {\n\t\t\treturn next[i].rank > next[j].rank\n\t\t})\n\t\tif len(next) > candidateBeamWidth {\n\t\t\tnext = next[:candidateBeamWidth]\n\t\t}\n\t\tbeam = next\n\t}\n\n\tif best == nil {\n\t\treturn nil, false\n\t}\n\n\tr.orderPlan(best.routes, best.planned)\n\treturn best.routes, true\n}\n\nfunc candidateAppendProbe(probes []lnwire.MilliSatoshi,\n\tprobe lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif probe <= 0 {\n\t\treturn probes\n\t}\n\tfor _, existing := range probes {\n\t\tif existing == probe {\n\t\t\treturn probes\n\t\t}\n\t}\n\treturn append(probes, probe)\n}\n\nfunc candidateCloneDiversity(\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\nfunc (r *candidateRouter) discoverPaths(\n\tamt lnwire.MilliSatoshi, parts int) [][]*candidateEdge {\n\n\tbase := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar probes []lnwire.MilliSatoshi\n\tprobes = candidateAppendProbe(probes, amt)\n\tprobes = candidateAppendProbe(probes, base)\n\tprobes = candidateAppendProbe(probes, base*3/2)\n\tprobes = candidateAppendProbe(probes, base*3/4)\n\tprobes = candidateAppendProbe(probes, base/2)\n\tprobes = candidateAppendProbe(probes, base/3)\n\tprobes = candidateAppendProbe(probes, base/4)\n\tprobes = candidateAppendProbe(probes, base/8)\n\tprobes = candidateAppendProbe(probes, base/16)\n\tprobes = candidateAppendProbe(probes, base/64)\n\n\triskScales := []float64{\n\t\t1_500_000,\n\t\t950_000,\n\t\t600_000,\n\t\t360_000,\n\t\t210_000,\n\t}\n\n\tglobalDiversity := make(map[candidateEdgeKey]float64)\n\tvar paths [][]*candidateEdge\n\n\tfor _, probe := range probes {\n\t\tdiversity := candidateCloneDiversity(globalDiversity)\n\n\t\tfor round := 0; round < 9; round++ {\n\t\t\tpath, err := r.findPath(\n\t\t\t\tprobe, diversity,\n\t\t\t\triskScales[round%len(riskScales)],\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tduplicate := false\n\t\t\tfor _, existing := range paths {\n\t\t\t\tif candidateSamePath(existing, path) {\n\t\t\t\t\tduplicate = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !duplicate {\n\t\t\t\tpaths = append(paths, path)\n\t\t\t\tfor i, edge := range path {\n\t\t\t\t\tglobalDiversity[edge.key] += 28_000\n\t\t\t\t\tif i == 0 || i == len(path)-1 {\n\t\t\t\t\t\tglobalDiversity[edge.key] += 24_000\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif len(paths) >= candidateMaxPaths {\n\t\t\t\t\treturn paths\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor i, edge := range path {\n\t\t\t\tdiversity[edge.key] += 320_000\n\t\t\t\tif i == 0 || i == len(path)-1 {\n\t\t\t\t\tdiversity[edge.key] += 260_000\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn paths\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, error) {\n\n\tif parts < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tpaths := r.discoverPaths(amt, parts)\n\tif len(paths) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tif plan, ok := r.allocatePlan(\n\t\tpaths, amt, parts, candidateSafeLimit,\n\t); ok {\n\t\treturn plan, nil\n\t}\n\n\tif plan, ok := r.allocatePlan(\n\t\tpaths, amt, parts, candidateRetryLimit,\n\t); ok {\n\t\treturn plan, nil\n\t}\n\n\t// Evidence is deliberately soft at the final stage. A stale or\n\t// amount-contingent failure must not turn into terminal abandonment\n\t// when gossip capacity still admits a complete route set.\n\tif plan, ok := r.allocatePlan(\n\t\tpaths, amt, parts, candidatePhysicalLimit,\n\t); ok {\n\t\treturn plan, nil\n\t}\n\n\treturn nil, errors.New(\"no route set can carry payment\")\n}\n\nfunc (r *candidateRouter) takeRoute(rt *route.Route) *route.Route {\n\tr.attempts++\n\treturn 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(\"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 reached\")\n\t}\n\tif r.attempts >= candidateMaxAttempts {\n\t\treturn nil, errors.New(\"routing attempts exhausted\")\n\t}\n\n\tif len(r.plan) > 0 {\n\t\tnext := r.plan[0]\n\t\tif candidateDelivered(next) <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.takeRoute(next), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tpartsLeft := int(maxParts - inFlightHtlcs)\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(plan) == 0 {\n\t\treturn nil, errors.New(\"empty route plan\")\n\t}\n\n\tr.plan = plan[1:]\n\treturn r.takeRoute(plan[0]), nil\n}\n\nfunc candidateFailureIndex(rt *route.Route,\n\tsource 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) learnRoutePassed(rt *route.Route,\n\tend int, reserve bool) {\n\n\tif end > len(rt.Hops) {\n\t\tend = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < end; i++ {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tamt := candidateRouteAmount(rt, i)\n\t\ttotal := amt + r.reserved[key]\n\t\tr.learn(key, total, amt, true)\n\t}\n\n\tif reserve {\n\t\tfor i := 0; i < end; i++ {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.reserved[key] += candidateRouteAmount(rt, i)\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.learnRoutePassed(rt, len(rt.Hops), true)\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex > 0 {\n\t\tr.learnRoutePassed(rt, failIndex, false)\n\t}\n\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tr.addPenalty(candidateRouteEdge(rt, i), 120_000)\n\t\t}\n\t\treturn nil\n\t}\n\n\tkey := candidateRouteEdge(rt, failIndex)\n\tamt := candidateRouteAmount(rt, failIndex)\n\ttotal := amt + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\tr.learn(key, total, amt, false)\n\t\tr.markLiquidityFailure(key, total)\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBad[key] = true\n\n\tdefault:\n\t\tr.addPenalty(key, 700_000)\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
},
|
|
{
|
|
"id": 9,
|
|
"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\"math\"\n\t\"sort\"\n\t\"sync\"\n\n\tgraphdb \"github.com/lightningnetwork/lnd/graph/db\"\n\t\"github.com/lightningnetwork/lnd/lnwire\"\n\t\"github.com/lightningnetwork/lnd/routing\"\n\t\"github.com/lightningnetwork/lnd/routing/route\"\n)\n\nconst (\n\tcandidateFinalCltvDelta = 40\n\tcandidateMaxAttempts = 64\n\tcandidateBeamWidth = 32\n\tcandidateMaxLabels = 4\n\tcandidateMaxPaths = 56\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,\n\tcheckMin bool) bool {\n\n\tif amt <= 0 || amt > e.capacity {\n\t\treturn false\n\t}\n\tif checkMin && amt < e.minHTLC {\n\t\treturn false\n\t}\n\treturn e.maxHTLC == 0 || amt <= e.maxHTLC\n}\n\ntype candidateBelief struct {\n\tlowerOK lnwire.MilliSatoshi\n\tupperBad lnwire.MilliSatoshi\n\tconf uint8\n}\n\nvar candidateSharedState = struct {\n\tsync.Mutex\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\n\tincomingEdges map[route.Vertex][]*candidateEdge\n\tedges map[candidateEdgeKey]*candidateEdge\n\tlocalBalances map[uint64]lnwire.MilliSatoshi\n\n\tshared map[candidateEdgeKey]candidateBelief\n\tcurrent map[candidateEdgeKey]candidateBelief\n\n\treserved map[candidateEdgeKey]lnwire.MilliSatoshi\n\tedgePenalty map[candidateEdgeKey]float64\n\tpolicyBad map[candidateEdgeKey]bool\n\n\tpathPool [][]*candidateEdge\n\trefreshes int\n\tplan []*route.Route\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\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\tshared: make(map[candidateEdgeKey]candidateBelief),\n\t\tcurrent: make(map[candidateEdgeKey]candidateBelief),\n\t\treserved: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tedgePenalty: make(map[candidateEdgeKey]float64),\n\t\tpolicyBad: make(map[candidateEdgeKey]bool),\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 := 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\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\tcandidateSharedState.Lock()\n\tfor key := range r.edges {\n\t\tif belief, ok := candidateSharedState.beliefs[key]; ok {\n\t\t\tr.shared[key] = belief\n\t\t}\n\t}\n\tcandidateSharedState.Unlock()\n\n\treturn r, nil\n}\n\nfunc candidatePrior(amt, capacity lnwire.MilliSatoshi) float64 {\n\tif capacity <= 0 || amt > capacity {\n\t\treturn 0.005\n\t}\n\n\tratio := float64(amt) / float64(capacity)\n\tlowMode := 0.5 * math.Exp(-ratio/0.025)\n\thighMode := 0.5 / (1 + math.Exp((ratio-0.93)/0.04))\n\tprobability := lowMode + highMode\n\n\tif probability < 0.005 {\n\t\treturn 0.005\n\t}\n\tif probability > 0.985 {\n\t\treturn 0.985\n\t}\n\treturn probability\n}\n\nfunc candidateEvidenceProbability(b candidateBelief,\n\tamt, capacity lnwire.MilliSatoshi) float64 {\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.005\n\t}\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tposition := float64(amt-b.lowerOK) /\n\t\t\tfloat64(b.upperBad-b.lowerOK)\n\t\tprobability := 0.99 - 0.98*position\n\t\treturn math.Max(0.01, math.Min(0.99, probability))\n\n\tcase b.upperBad > 0:\n\t\tratio := float64(amt) / float64(b.upperBad)\n\t\tprobability := 0.02 + 0.91*math.Exp(-ratio/0.13)\n\t\treturn math.Min(0.99, probability)\n\n\tcase b.lowerOK > 0:\n\t\tif capacity <= b.lowerOK {\n\t\t\treturn 0.985\n\t\t}\n\t\tdistance := float64(amt-b.lowerOK) / float64(capacity)\n\t\tprobability := 0.60 + 0.38*math.Exp(-distance/0.22)\n\t\treturn math.Min(0.99, probability)\n\n\tdefault:\n\t\treturn candidatePrior(amt, capacity)\n\t}\n}\n\nfunc candidateUpdateBelief(b candidateBelief,\n\tamt lnwire.MilliSatoshi, passed bool) candidateBelief {\n\n\tif passed {\n\t\tif b.upperBad > 0 && amt >= b.upperBad {\n\t\t\tb.upperBad = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif amt > b.lowerOK {\n\t\t\tb.lowerOK = amt\n\t\t}\n\t} else {\n\t\tif b.lowerOK > 0 && amt <= b.lowerOK {\n\t\t\tb.lowerOK = 0\n\t\t\tb.conf /= 2\n\t\t}\n\t\tif b.upperBad == 0 || amt < b.upperBad {\n\t\t\tb.upperBad = amt\n\t\t}\n\t}\n\n\tif b.conf < 16 {\n\t\tb.conf++\n\t}\n\treturn b\n}\n\nfunc (r *candidateRouter) learn(key candidateEdgeKey,\n\tcurrentAmt, sharedAmt lnwire.MilliSatoshi, passed bool) {\n\n\tr.current[key] = candidateUpdateBelief(\n\t\tr.current[key], currentAmt, passed,\n\t)\n\tr.shared[key] = candidateUpdateBelief(\n\t\tr.shared[key], sharedAmt, passed,\n\t)\n\n\tcandidateSharedState.Lock()\n\tcandidateSharedState.beliefs[key] = candidateUpdateBelief(\n\t\tcandidateSharedState.beliefs[key], sharedAmt, passed,\n\t)\n\tcandidateSharedState.Unlock()\n}\n\nfunc (r *candidateRouter) edgeProbability(edge *candidateEdge,\n\ttotal lnwire.MilliSatoshi) float64 {\n\n\tif edge.key.from == r.source {\n\t\tif total <= r.localBalances[edge.key.chanID] {\n\t\t\treturn 0.999\n\t\t}\n\t\treturn 0.001\n\t}\n\n\tprobability := candidatePrior(total, edge.capacity)\n\n\tif belief, ok := r.shared[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tweight := float64(belief.conf) /\n\t\t\t(float64(belief.conf) + 2.5)\n\t\tif weight > 0.82 {\n\t\t\tweight = 0.82\n\t\t}\n\t\tprobability = probability*(1-weight) + evidence*weight\n\t}\n\n\tif belief, ok := r.current[edge.key]; ok {\n\t\tevidence := candidateEvidenceProbability(\n\t\t\tbelief, total, edge.capacity,\n\t\t)\n\t\tprobability = 0.05*probability + 0.95*evidence\n\t}\n\n\treturn math.Max(0.005, math.Min(0.995, probability))\n}\n\nfunc (r *candidateRouter) hardTotal(edge *candidateEdge) lnwire.MilliSatoshi {\n\tlimit := edge.capacity\n\tif edge.key.from == r.source {\n\t\tif local := r.localBalances[edge.key.chanID]; local < limit {\n\t\t\tlimit = local\n\t\t}\n\t}\n\n\tbelief, ok := r.current[edge.key]\n\tif !ok || belief.upperBad == 0 {\n\t\treturn limit\n\t}\n\n\tvar retryLimit lnwire.MilliSatoshi\n\tif belief.lowerOK > 0 && belief.upperBad > belief.lowerOK {\n\t\tretryLimit = belief.lowerOK +\n\t\t\t(belief.upperBad-belief.lowerOK)*58/100\n\t} else {\n\t\tretryLimit = belief.upperBad * 58 / 100\n\t}\n\n\tif retryLimit < limit {\n\t\tlimit = retryLimit\n\t}\n\treturn limit\n}\n\nfunc candidateRecommendedFromBelief(b candidateBelief,\n\tcapacity lnwire.MilliSatoshi) lnwire.MilliSatoshi {\n\n\tvar result lnwire.MilliSatoshi\n\n\tswitch {\n\tcase b.lowerOK > 0 && b.upperBad > b.lowerOK:\n\t\tresult = b.lowerOK + (b.upperBad-b.lowerOK)*36/100\n\n\tcase b.upperBad > 0:\n\t\tresult = b.upperBad * 52 / 100\n\n\tcase b.lowerOK > 0:\n\t\tresult = capacity * 90 / 100\n\t\tif b.lowerOK > result {\n\t\t\tresult = b.lowerOK\n\t\t}\n\n\tdefault:\n\t\tresult = capacity * 82 / 100\n\t}\n\n\tcapLimit := capacity * 98 / 100\n\tif result > capLimit {\n\t\tresult = capLimit\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) recommendedTotal(\n\tedge *candidateEdge) lnwire.MilliSatoshi {\n\n\thard := r.hardTotal(edge)\n\tif edge.key.from == r.source {\n\t\treturn hard\n\t}\n\n\tvar recommended lnwire.MilliSatoshi\n\tif belief, ok := r.current[edge.key]; ok {\n\t\trecommended = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else if belief, ok := r.shared[edge.key]; ok {\n\t\trecommended = candidateRecommendedFromBelief(\n\t\t\tbelief, edge.capacity,\n\t\t)\n\t} else {\n\t\trecommended = edge.capacity * 82 / 100\n\t}\n\n\tif recommended > hard {\n\t\trecommended = hard\n\t}\n\treturn recommended\n}\n\ntype candidateLabel struct {\n\tnode route.Vertex\n\tcost float64\n\tarriving lnwire.MilliSatoshi\n\tpath []*candidateEdge\n\tactive bool\n}\n\ntype candidateQueue []*candidateLabel\n\nfunc (q candidateQueue) Len() int {\n\treturn len(q)\n}\n\nfunc (q candidateQueue) Less(i, j int) bool {\n\treturn q[i].cost < q[j].cost\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.(*candidateLabel))\n}\n\nfunc (q *candidateQueue) Pop() any {\n\told := *q\n\titem := old[len(old)-1]\n\t*q = old[:len(old)-1]\n\treturn item\n}\n\nfunc candidatePathContains(path []*candidateEdge,\n\tnode route.Vertex) bool {\n\n\tfor _, edge := range path {\n\t\tif edge.key.from == node || edge.key.to == node {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc candidateAdmitLabel(\n\tlabels map[route.Vertex][]*candidateLabel,\n\titem *candidateLabel) bool {\n\n\tcurrent := labels[item.node]\n\tfor _, old := range current {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif old.cost <= item.cost &&\n\t\t\told.arriving <= item.arriving {\n\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfiltered := make([]*candidateLabel, 0, len(current)+1)\n\tfor _, old := range current {\n\t\tif !old.active {\n\t\t\tcontinue\n\t\t}\n\t\tif item.cost <= old.cost &&\n\t\t\titem.arriving <= old.arriving {\n\n\t\t\told.active = false\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, old)\n\t}\n\n\titem.active = true\n\tfiltered = append(filtered, item)\n\n\tif len(filtered) > candidateMaxLabels {\n\t\tminCost, minAmount := 0, 0\n\t\tfor i := 1; i < len(filtered); i++ {\n\t\t\tif filtered[i].cost < filtered[minCost].cost {\n\t\t\t\tminCost = i\n\t\t\t}\n\t\t\tif filtered[i].arriving <\n\t\t\t\tfiltered[minAmount].arriving {\n\n\t\t\t\tminAmount = i\n\t\t\t}\n\t\t}\n\n\t\tworst := -1\n\t\tfor i := range filtered {\n\t\t\tif i == minCost || i == minAmount {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif worst < 0 ||\n\t\t\t\tfiltered[i].cost > filtered[worst].cost {\n\n\t\t\t\tworst = i\n\t\t\t}\n\t\t}\n\t\tif worst < 0 {\n\t\t\tworst = len(filtered) - 1\n\t\t}\n\n\t\tfiltered[worst].active = false\n\t\tfiltered = append(\n\t\t\tfiltered[:worst], filtered[worst+1:]...,\n\t\t)\n\t}\n\n\tlabels[item.node] = filtered\n\treturn item.active\n}\n\nfunc (r *candidateRouter) findPath(amt lnwire.MilliSatoshi,\n\tdiversity map[candidateEdgeKey]float64,\n\triskScale float64) ([]*candidateEdge, 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 equals target\")\n\t}\n\n\tlabels := make(map[route.Vertex][]*candidateLabel)\n\tpq := &candidateQueue{}\n\tinitial := &candidateLabel{\n\t\tnode: r.spec.Target,\n\t\tarriving: amt,\n\t\tactive: true,\n\t}\n\tlabels[r.spec.Target] = []*candidateLabel{initial}\n\theap.Push(pq, initial)\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\treturn item.path, nil\n\t\t}\n\n\t\tfor _, edge := range r.incomingEdges[item.node] {\n\t\t\tif r.policyBad[edge.key] ||\n\t\t\t\tcandidatePathContains(item.path, edge.key.from) ||\n\t\t\t\t!edge.usable(item.arriving, true) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlimit := r.hardTotal(edge)\n\t\t\ttotal := item.arriving + r.reserved[edge.key]\n\t\t\tif limit <= 0 || total > limit {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprobability := r.edgeProbability(edge, total)\n\t\t\theadroom := float64(total) / float64(limit)\n\t\t\tedgeCost := -math.Log(probability)*riskScale +\n\t\t\t\t110_000*headroom*headroom + 2_500 +\n\t\t\t\tr.edgePenalty[edge.key] + diversity[edge.key]\n\n\t\t\tsending := item.arriving\n\t\t\tif edge.key.from != r.source {\n\t\t\t\tfee := edge.fee(item.arriving)\n\t\t\t\tsending += fee\n\t\t\t\tedgeCost += float64(fee)\n\t\t\t}\n\n\t\t\tpath := make([]*candidateEdge, len(item.path)+1)\n\t\t\tpath[0] = edge\n\t\t\tcopy(path[1:], item.path)\n\n\t\t\tnext := &candidateLabel{\n\t\t\t\tnode: edge.key.from,\n\t\t\t\tcost: item.cost + edgeCost,\n\t\t\t\tarriving: sending,\n\t\t\t\tpath: path,\n\t\t\t}\n\t\t\tif !candidateAdmitLabel(labels, next) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\theap.Push(pq, next)\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no route found\")\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\nfunc candidatePathAmounts(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tamounts := make([]lnwire.MilliSatoshi, len(path))\n\tif len(path) == 0 {\n\t\treturn amounts\n\t}\n\n\tamounts[len(path)-1] = finalAmt\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\tamounts[i] = amounts[i+1] +\n\t\t\tpath[i+1].fee(amounts[i+1])\n\t}\n\treturn amounts\n}\n\nfunc (r *candidateRouter) pathWithin(path []*candidateEdge,\n\tfinalAmt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe, checkMin bool) bool {\n\n\tif finalAmt <= 0 || len(path) == 0 {\n\t\treturn false\n\t}\n\n\tamounts := candidatePathAmounts(path, finalAmt)\n\tfor i, edge := range path {\n\t\tif r.policyBad[edge.key] ||\n\t\t\t!edge.usable(amounts[i], checkMin) {\n\n\t\t\treturn false\n\t\t}\n\n\t\tlimit := r.hardTotal(edge)\n\t\tif safe {\n\t\t\tlimit = r.recommendedTotal(edge)\n\t\t}\n\n\t\ttotal := amounts[i] + r.reserved[edge.key] +\n\t\t\tplanned[edge.key]\n\t\tif limit <= 0 || total > limit {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (r *candidateRouter) maxPathAmount(path []*candidateEdge,\n\twant lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tsafe bool) lnwire.MilliSatoshi {\n\n\tif want <= 0 {\n\t\treturn 0\n\t}\n\n\tlow := lnwire.MilliSatoshi(0)\n\thigh := want\n\tfor low < high {\n\t\tmid := low + (high-low+1)/2\n\t\tif r.pathWithin(path, mid, planned, safe, false) {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid - 1\n\t\t}\n\t}\n\n\tif low == 0 ||\n\t\t!r.pathWithin(path, low, planned, safe, true) {\n\n\t\treturn 0\n\t}\n\treturn low\n}\n\nfunc (r *candidateRouter) buildRoute(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi) (*route.Route, error) {\n\n\tif len(path) == 0 || amt <= 0 {\n\t\treturn nil, errors.New(\"empty route\")\n\t}\n\n\tamounts := candidatePathAmounts(path, amt)\n\texpiries := make([]uint32, len(path))\n\texpiries[len(path)-1] = candidateFinalCltvDelta\n\n\tfor i := len(path) - 2; i >= 0; i-- {\n\t\texpiries[i] = expiries[i+1] +\n\t\t\tuint32(path[i+1].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(candidateFinalCltvDelta)\n\t\tif i+1 < len(path) {\n\t\t\tforwardAmt = 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: forwardAmt,\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 candidateCloneAmounts(\n\tsource map[candidateEdgeKey]lnwire.MilliSatoshi,\n) map[candidateEdgeKey]lnwire.MilliSatoshi {\n\n\tresult := make(map[candidateEdgeKey]lnwire.MilliSatoshi, len(source))\n\tfor key, amount := range source {\n\t\tresult[key] = amount\n\t}\n\treturn result\n}\n\nfunc candidateCloneCounts(\n\tsource map[candidateEdgeKey]uint8) map[candidateEdgeKey]uint8 {\n\n\tresult := make(map[candidateEdgeKey]uint8, len(source))\n\tfor key, count := range source {\n\t\tresult[key] = count\n\t}\n\treturn result\n}\n\nfunc candidateAddPlanned(path []*candidateEdge,\n\tamt lnwire.MilliSatoshi,\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi,\n\tused map[candidateEdgeKey]uint8) {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tfor i, edge := range path {\n\t\tplanned[edge.key] += amounts[i]\n\t\tused[edge.key]++\n\t}\n}\n\nfunc candidateAppendAmount(amounts []lnwire.MilliSatoshi,\n\tamount lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif amount <= 0 {\n\t\treturn amounts\n\t}\n\tfor _, existing := range amounts {\n\t\tif existing == amount {\n\t\t\treturn amounts\n\t\t}\n\t}\n\treturn append(amounts, amount)\n}\n\ntype candidateLeg struct {\n\tpath []*candidateEdge\n\tamt lnwire.MilliSatoshi\n\troute *route.Route\n}\n\ntype candidateAllocation struct {\n\tlegs []candidateLeg\n\tplanned map[candidateEdgeKey]lnwire.MilliSatoshi\n\tused map[candidateEdgeKey]uint8\n\tremaining lnwire.MilliSatoshi\n\tfee lnwire.MilliSatoshi\n\thops int\n\trank float64\n}\n\nfunc (r *candidateRouter) allocationQuality(\n\tstate *candidateAllocation) float64 {\n\n\tquality := -float64(state.fee)/20_000_000 -\n\t\t0.018*float64(state.hops) -\n\t\t0.08*float64(len(state.legs))\n\n\tfor key, amount := range state.planned {\n\t\tedge := r.edges[key]\n\t\tif edge == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ttotal := amount + r.reserved[key]\n\t\tquality += math.Log(r.edgeProbability(edge, total))\n\t\tquality -= r.edgePenalty[key] / 2_000_000\n\n\t\tif count := state.used[key]; count > 1 &&\n\t\t\tkey.from != r.source {\n\n\t\t\tquality -= 0.20 * float64(count-1)\n\t\t}\n\t}\n\n\treturn quality\n}\n\nfunc (r *candidateRouter) allocationRank(state *candidateAllocation,\n\toriginal lnwire.MilliSatoshi) float64 {\n\n\tcoverage := float64(original-state.remaining) / float64(original)\n\treturn 30*coverage + 0.55*r.allocationQuality(state)\n}\n\nfunc (r *candidateRouter) orderAllocation(\n\tstate *candidateAllocation) []*route.Route {\n\n\ttype scoredLeg struct {\n\t\tleg candidateLeg\n\t\tscore float64\n\t}\n\n\tscored := make([]scoredLeg, 0, len(state.legs))\n\tfor _, leg := range state.legs {\n\t\tamounts := candidatePathAmounts(leg.path, leg.amt)\n\t\tscore := 0.0\n\t\tfor i, edge := range leg.path {\n\t\t\ttotal := state.planned[edge.key] +\n\t\t\t\tr.reserved[edge.key]\n\t\t\tscore += math.Log(r.edgeProbability(edge, total))\n\t\t\tscore -= r.edgePenalty[edge.key] / 2_000_000\n\t\t\tif amounts[i] > r.recommendedTotal(edge) {\n\t\t\t\tscore -= 0.15\n\t\t\t}\n\t\t}\n\t\tscored = append(scored, scoredLeg{\n\t\t\tleg: leg,\n\t\t\tscore: score,\n\t\t})\n\t}\n\n\tsort.SliceStable(scored, func(i, j int) bool {\n\t\tdifference := scored[i].score - scored[j].score\n\t\tif math.Abs(difference) > 0.05 {\n\t\t\treturn scored[i].score < scored[j].score\n\t\t}\n\t\treturn scored[i].leg.amt > scored[j].leg.amt\n\t})\n\n\tresult := make([]*route.Route, len(scored))\n\tfor i := range scored {\n\t\tresult[i] = scored[i].leg.route\n\t}\n\treturn result\n}\n\nfunc (r *candidateRouter) allocatePlan(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int,\n\tsafe bool) ([]*route.Route, bool) {\n\n\tinitial := &candidateAllocation{\n\t\tplanned: make(map[candidateEdgeKey]lnwire.MilliSatoshi),\n\t\tused: make(map[candidateEdgeKey]uint8),\n\t\tremaining: amt,\n\t}\n\tbeam := []*candidateAllocation{initial}\n\n\tvar best *candidateAllocation\n\tbestQuality := math.Inf(-1)\n\n\tfor depth := 0; depth < parts && len(beam) != 0; depth++ {\n\t\tvar next []*candidateAllocation\n\n\t\tfor _, state := range beam {\n\t\t\tslots := parts - len(state.legs)\n\t\t\tif slots <= 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfair := (state.remaining +\n\t\t\t\tlnwire.MilliSatoshi(slots) - 1) /\n\t\t\t\tlnwire.MilliSatoshi(slots)\n\n\t\t\tfor _, path := range paths {\n\t\t\t\tmaxAmount := r.maxPathAmount(\n\t\t\t\t\tpath, state.remaining,\n\t\t\t\t\tstate.planned, safe,\n\t\t\t\t)\n\t\t\t\tif maxAmount == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvar choices []lnwire.MilliSatoshi\n\t\t\t\tchoices = candidateAppendAmount(choices, maxAmount)\n\n\t\t\t\tfairAmount := fair\n\t\t\t\tif fairAmount > maxAmount {\n\t\t\t\t\tfairAmount = maxAmount\n\t\t\t\t}\n\t\t\t\tchoices = candidateAppendAmount(choices, fairAmount)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount*4/5,\n\t\t\t\t)\n\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\tchoices, maxAmount*3/5,\n\t\t\t\t)\n\n\t\t\t\tif maxAmount >= state.remaining {\n\t\t\t\t\tchoices = candidateAppendAmount(\n\t\t\t\t\t\tchoices, state.remaining,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tfor _, amount := range choices {\n\t\t\t\t\tif amount > state.remaining ||\n\t\t\t\t\t\t!r.pathWithin(\n\t\t\t\t\t\t\tpath, amount, state.planned,\n\t\t\t\t\t\t\tsafe, true,\n\t\t\t\t\t\t) {\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\trt, err := r.buildRoute(path, amount)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tplanned := candidateCloneAmounts(\n\t\t\t\t\t\tstate.planned,\n\t\t\t\t\t)\n\t\t\t\t\tused := candidateCloneCounts(state.used)\n\t\t\t\t\tcandidateAddPlanned(\n\t\t\t\t\t\tpath, amount, planned, used,\n\t\t\t\t\t)\n\n\t\t\t\t\tlegs := append(\n\t\t\t\t\t\t[]candidateLeg(nil), state.legs...,\n\t\t\t\t\t)\n\t\t\t\t\tlegs = append(legs, candidateLeg{\n\t\t\t\t\t\tpath: path,\n\t\t\t\t\t\tamt: amount,\n\t\t\t\t\t\troute: rt,\n\t\t\t\t\t})\n\n\t\t\t\t\tnextState := &candidateAllocation{\n\t\t\t\t\t\tlegs: legs,\n\t\t\t\t\t\tplanned: planned,\n\t\t\t\t\t\tused: used,\n\t\t\t\t\t\tremaining: state.remaining - amount,\n\t\t\t\t\t\tfee: state.fee +\n\t\t\t\t\t\t\t(rt.TotalAmount - amount),\n\t\t\t\t\t\thops: state.hops + len(path),\n\t\t\t\t\t}\n\n\t\t\t\t\tif nextState.remaining == 0 {\n\t\t\t\t\t\tquality := r.allocationQuality(\n\t\t\t\t\t\t\tnextState,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif quality > bestQuality {\n\t\t\t\t\t\t\tbest = nextState\n\t\t\t\t\t\t\tbestQuality = quality\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tnextState.rank = r.allocationRank(\n\t\t\t\t\t\tnextState, amt,\n\t\t\t\t\t)\n\t\t\t\t\tnext = append(next, nextState)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(next, func(i, j int) bool {\n\t\t\treturn next[i].rank > next[j].rank\n\t\t})\n\t\tif len(next) > candidateBeamWidth {\n\t\t\tnext = next[:candidateBeamWidth]\n\t\t}\n\t\tbeam = next\n\t}\n\n\tif best == nil {\n\t\treturn nil, false\n\t}\n\treturn r.orderAllocation(best), true\n}\n\nfunc candidateAppendProbe(probes []lnwire.MilliSatoshi,\n\tprobe lnwire.MilliSatoshi) []lnwire.MilliSatoshi {\n\n\tif probe <= 0 {\n\t\treturn probes\n\t}\n\tfor _, existing := range probes {\n\t\tif existing == probe {\n\t\t\treturn probes\n\t\t}\n\t}\n\treturn append(probes, probe)\n}\n\nfunc (r *candidateRouter) discoverPaths(\n\tamt lnwire.MilliSatoshi, parts int) [][]*candidateEdge {\n\n\tbase := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\n\tvar probes []lnwire.MilliSatoshi\n\tprobes = candidateAppendProbe(probes, amt)\n\tprobes = candidateAppendProbe(probes, base*3/2)\n\tprobes = candidateAppendProbe(probes, base)\n\tprobes = candidateAppendProbe(probes, base*2/3)\n\tprobes = candidateAppendProbe(probes, base/3)\n\tprobes = candidateAppendProbe(probes, base/6)\n\n\tvar paths [][]*candidateEdge\n\n\tfor _, probe := range probes {\n\t\tif probe > amt {\n\t\t\tprobe = amt\n\t\t}\n\n\t\tdiversity := make(map[candidateEdgeKey]float64)\n\t\tfor round := 0; round < 8; round++ {\n\t\t\triskScale := 750_000.0\n\t\t\tswitch round % 3 {\n\t\t\tcase 0:\n\t\t\t\triskScale = 1_250_000\n\t\t\tcase 2:\n\t\t\t\triskScale = 420_000\n\t\t\t}\n\n\t\t\tpath, err := r.findPath(\n\t\t\t\tprobe, diversity, riskScale,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tduplicate := false\n\t\t\tfor _, existing := range paths {\n\t\t\t\tif candidateSamePath(existing, path) {\n\t\t\t\t\tduplicate = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !duplicate {\n\t\t\t\tpaths = append(paths, path)\n\t\t\t}\n\n\t\t\tfor _, edge := range path {\n\t\t\t\tdiversity[edge.key] += 1_900_000\n\t\t\t}\n\t\t}\n\t}\n\n\treturn paths\n}\n\nfunc (r *candidateRouter) mergePaths(paths [][]*candidateEdge) {\n\tfor _, path := range paths {\n\t\tduplicate := false\n\t\tfor _, existing := range r.pathPool {\n\t\t\tif candidateSamePath(existing, path) {\n\t\t\t\tduplicate = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif duplicate {\n\t\t\tcontinue\n\t\t}\n\t\tr.pathPool = append(r.pathPool, path)\n\t\tif len(r.pathPool) >= candidateMaxPaths {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) fallbackScore(path []*candidateEdge,\n\tamt, paymentAmt lnwire.MilliSatoshi) float64 {\n\n\tamounts := candidatePathAmounts(path, amt)\n\tscore := 6.0 * math.Log(\n\t\tfloat64(amt) / float64(paymentAmt),\n\t)\n\n\tfor i, edge := range path {\n\t\ttotal := amounts[i] + r.reserved[edge.key]\n\t\tscore += math.Log(r.edgeProbability(edge, total))\n\t\tscore -= r.edgePenalty[edge.key] / 2_000_000\n\t}\n\n\tfee := amounts[0] - amt\n\tscore -= float64(fee) / 20_000_000\n\tscore -= 0.02 * float64(len(path))\n\treturn score\n}\n\nfunc (r *candidateRouter) bestFallback(paths [][]*candidateEdge,\n\tamt lnwire.MilliSatoshi, parts int) *route.Route {\n\n\tplanned := make(map[candidateEdgeKey]lnwire.MilliSatoshi)\n\tfair := (amt + lnwire.MilliSatoshi(parts) - 1) /\n\t\tlnwire.MilliSatoshi(parts)\n\tminCommit := fair * 45 / 100\n\n\tbestScore := math.Inf(-1)\n\tvar best *route.Route\n\n\tfor _, path := range paths {\n\t\thardMax := r.maxPathAmount(path, amt, planned, false)\n\t\tsafeMax := r.maxPathAmount(path, amt, planned, true)\n\n\t\tvar choices []lnwire.MilliSatoshi\n\t\tif parts == 1 {\n\t\t\tif hardMax >= amt {\n\t\t\t\tchoices = candidateAppendAmount(choices, amt)\n\t\t\t}\n\t\t} else {\n\t\t\tchoices = candidateAppendAmount(choices, safeMax)\n\t\t\tchoices = candidateAppendAmount(choices, hardMax)\n\n\t\t\tfairAmount := fair\n\t\t\tif fairAmount > hardMax {\n\t\t\t\tfairAmount = hardMax\n\t\t\t}\n\t\t\tchoices = candidateAppendAmount(choices, fairAmount)\n\t\t\tchoices = candidateAppendAmount(\n\t\t\t\tchoices, hardMax*3/4,\n\t\t\t)\n\t\t}\n\n\t\tfor _, amount := range choices {\n\t\t\tif amount <= 0 || amount < minCommit ||\n\t\t\t\t!r.pathWithin(\n\t\t\t\t\tpath, amount, planned, false, true,\n\t\t\t\t) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tscore := r.fallbackScore(path, amount, amt)\n\t\t\tif score <= bestScore {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trt, err := r.buildRoute(path, amount)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbest = rt\n\t\t\tbestScore = score\n\t\t}\n\t}\n\n\treturn best\n}\n\nfunc (r *candidateRouter) completePlan(\n\tamt lnwire.MilliSatoshi, parts int) ([]*route.Route, bool) {\n\n\tif plan, ok := r.allocatePlan(\n\t\tr.pathPool, amt, parts, true,\n\t); ok {\n\t\treturn plan, true\n\t}\n\n\treturn r.allocatePlan(r.pathPool, amt, parts, false)\n}\n\nfunc (r *candidateRouter) makePlan(amt lnwire.MilliSatoshi,\n\tparts int) ([]*route.Route, error) {\n\n\tif parts < 1 {\n\t\treturn nil, errors.New(\"maximum parts reached\")\n\t}\n\n\tif len(r.pathPool) == 0 {\n\t\tr.mergePaths(r.discoverPaths(amt, parts))\n\t\tr.refreshes++\n\t}\n\tif len(r.pathPool) == 0 {\n\t\treturn nil, errors.New(\"no route found\")\n\t}\n\n\tif plan, ok := r.completePlan(amt, parts); ok {\n\t\treturn plan, nil\n\t}\n\n\tif r.refreshes < 3 && len(r.pathPool) < candidateMaxPaths {\n\t\tr.mergePaths(r.discoverPaths(amt, parts))\n\t\tr.refreshes++\n\t\tif plan, ok := r.completePlan(amt, parts); ok {\n\t\t\treturn plan, nil\n\t\t}\n\t}\n\n\tif fallback := r.bestFallback(\n\t\tr.pathPool, amt, parts,\n\t); fallback != nil {\n\t\treturn []*route.Route{fallback}, nil\n\t}\n\n\treturn nil, errors.New(\"no route set can carry payment\")\n}\n\nfunc candidateDelivered(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) takeRoute(rt *route.Route) *route.Route {\n\tr.attempts++\n\treturn 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(\"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 reached\")\n\t}\n\tif r.attempts >= candidateMaxAttempts {\n\t\treturn nil, errors.New(\"routing attempts exhausted\")\n\t}\n\n\tif len(r.plan) != 0 {\n\t\tnext := r.plan[0]\n\t\tdelivered := candidateDelivered(next)\n\t\tif delivered > 0 && delivered <= amt {\n\t\t\tr.plan = r.plan[1:]\n\t\t\treturn r.takeRoute(next), nil\n\t\t}\n\t\tr.plan = nil\n\t}\n\n\tpartsLeft := int(maxParts - inFlightHtlcs)\n\tplan, err := r.makePlan(amt, partsLeft)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(plan) == 0 {\n\t\treturn nil, errors.New(\"empty route plan\")\n\t}\n\n\tr.plan = plan[1:]\n\treturn r.takeRoute(plan[0]), nil\n}\n\nfunc candidateRouteEdge(rt *route.Route,\n\tindex int) candidateEdgeKey {\n\n\tfrom := rt.SourcePubKey\n\tif index > 0 {\n\t\tfrom = rt.Hops[index-1].PubKeyBytes\n\t}\n\n\treturn candidateEdgeKey{\n\t\tchanID: rt.Hops[index].ChannelID,\n\t\tfrom: from,\n\t\tto: rt.Hops[index].PubKeyBytes,\n\t}\n}\n\nfunc candidateRouteAmount(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 candidateFailureIndex(rt *route.Route,\n\tsource 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) learnRoutePassed(rt *route.Route,\n\tend int, reserve bool) {\n\n\tif end > len(rt.Hops) {\n\t\tend = len(rt.Hops)\n\t}\n\n\tfor i := 0; i < end; i++ {\n\t\tkey := candidateRouteEdge(rt, i)\n\t\tamt := candidateRouteAmount(rt, i)\n\t\ttotal := amt + r.reserved[key]\n\t\tr.learn(key, total, amt, true)\n\t}\n\n\tif reserve {\n\t\tfor i := 0; i < end; i++ {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.reserved[key] += candidateRouteAmount(rt, i)\n\t\t}\n\t}\n}\n\nfunc (r *candidateRouter) ReportAttempt(_ uint64, rt *route.Route,\n\tresult routing.SimHtlcResult) error {\n\n\tif rt == nil {\n\t\treturn errors.New(\"nil attempted route\")\n\t}\n\n\tif result.Failure == nil {\n\t\tr.learnRoutePassed(rt, len(rt.Hops), true)\n\t\treturn nil\n\t}\n\n\tr.plan = nil\n\n\tfailIndex := candidateFailureIndex(\n\t\trt, result.FailureSource,\n\t)\n\tif failIndex > 0 {\n\t\tr.learnRoutePassed(rt, failIndex, false)\n\t}\n\n\tif failIndex < 0 || failIndex >= len(rt.Hops) {\n\t\tfor i := range rt.Hops {\n\t\t\tkey := candidateRouteEdge(rt, i)\n\t\t\tr.edgePenalty[key] += 500_000\n\t\t}\n\t\treturn nil\n\t}\n\n\tkey := candidateRouteEdge(rt, failIndex)\n\tamt := candidateRouteAmount(rt, failIndex)\n\ttotal := amt + r.reserved[key]\n\n\tswitch result.Failure.Code() {\n\tcase lnwire.CodeTemporaryChannelFailure:\n\t\t// The failure tested aggregate liquidity, including held sibling\n\t\t// shards, so retain that aggregate threshold in both beliefs.\n\t\tr.learn(key, total, total, false)\n\t\tr.edgePenalty[key] += 400_000\n\n\tcase lnwire.CodeFeeInsufficient,\n\t\tlnwire.CodeIncorrectCltvExpiry:\n\n\t\tr.policyBad[key] = true\n\n\tdefault:\n\t\tr.edgePenalty[key] += 1_100_000\n\t}\n\n\treturn nil\n}"
|
|
}
|
|
}
|
|
]
|
|
} |